matrixhasyou-site/index.html

1605 lines
74 KiB
HTML
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#070d09">
<meta name="description" content="MatrixHasYou // Ульяновск • Aspect Air 2025 29 MTB • Блог, Книги, Зал, Настольный теннис">
<title>MatrixHasYou // Ульяновск • Aspect Air 2025 29" MTB</title>
<!-- Embedded SVG Favicon (Matrix Green Glowing Bike) -->
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='%23050b07' rx='10'/%3E%3Ccircle cx='18' cy='42' r='12' fill='none' stroke='%2300ff66' stroke-width='4' filter='drop-shadow(0 0 4px %2300ff66)'/%3E%3Ccircle cx='46' cy='42' r='12' fill='none' stroke='%2300ff66' stroke-width='4' filter='drop-shadow(0 0 4px %2300ff66)'/%3E%3Cpath d='M18 42 L28 24 L40 24 L46 42 M28 24 L34 42 L46 42 M34 42 L24 42 M25 18 L33 18 M38 20 L44 14' fill='none' stroke='%2300ff66' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='34' cy='42' r='3' fill='%2338ef7d'/%3E%3C/svg%3E">
<style>
:root {
--bg-base: #070d09;
--bg-surface: #0c1710;
--bg-surface-elevated: #112217;
--bg-code: #050a07;
--border-color: #1b3824;
--border-glow: #00ff6655;
--text-main: #d1fae5;
--text-dim: #6ee7b7;
--text-muted: #3d7a55;
--accent-green: #00ff66;
--accent-green-glow: #00ff6688;
--accent-cyan: #00f2fe;
--accent-amber: #f59e0b;
--accent-red: #ef4444;
--font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--terminal-header-bg: #0e2014;
}
[data-theme="amber"] {
--bg-base: #0d0a04;
--bg-surface: #171207;
--bg-surface-elevated: #241c0c;
--bg-code: #0a0803;
--border-color: #3b2d14;
--border-glow: #ffb86c55;
--text-main: #fef3c7;
--text-dim: #fcd34d;
--text-muted: #856220;
--accent-green: #ffb86c;
--accent-green-glow: #ffb86c88;
--accent-cyan: #fbbf24;
--terminal-header-bg: #1c1509;
}
[data-theme="github-dark"] {
--bg-base: #0d1117;
--bg-surface: #161b22;
--bg-surface-elevated: #21262d;
--bg-code: #090d13;
--border-color: #30363d;
--border-glow: #58a6ff44;
--text-main: #c9d1d9;
--text-dim: #8b949e;
--text-muted: #484f58;
--accent-green: #58a6ff;
--accent-green-glow: #58a6ff66;
--accent-cyan: #39d353;
--terminal-header-bg: #1f242c;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-base);
color: var(--text-main);
font-family: var(--font-mono);
font-size: 14px;
line-height: 1.6;
overflow-x: hidden;
background-image:
radial-gradient(circle at 50% 0%, rgba(0, 255, 102, 0.05) 0%, transparent 60%),
linear-gradient(rgba(0, 255, 102, 0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 102, 0.02) 1px, transparent 1px);
background-size: 100% 100%, 32px 32px, 32px 32px;
}
/* CRT Scanline Effect */
.scanlines {
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
pointer-events: none;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%);
background-size: 100% 4px;
z-index: 999;
opacity: 0.25;
}
a {
color: var(--accent-green);
text-decoration: none;
border-bottom: 1px dashed var(--border-color);
transition: all 0.2s ease;
}
a:hover {
color: #ffffff;
border-bottom-color: var(--accent-green);
text-shadow: 0 0 8px var(--accent-green);
}
/* Matrix Rain Canvas */
#matrix-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.12;
z-index: 0;
transition: opacity 0.3s;
}
#matrix-canvas.full-mode {
opacity: 0.85;
z-index: 1000;
pointer-events: auto;
cursor: pointer;
}
/* Layout Container */
.site-wrapper {
position: relative;
z-index: 10;
max-width: 1240px;
margin: 0 auto;
padding: 20px 16px 80px 16px;
}
/* Top System Bar */
.top-system-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 6px 6px 0 0;
font-size: 12px;
color: var(--text-dim);
gap: 12px;
}
.sys-badges {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 8px;
background: var(--bg-surface-elevated);
border: 1px solid var(--border-color);
border-radius: 4px;
white-space: nowrap;
}
.controls-group {
display: flex;
gap: 8px;
align-items: center;
margin-left: auto;
}
.btn-ctrl {
background: var(--bg-surface-elevated);
color: var(--text-dim);
border: 1px solid var(--border-color);
padding: 4px 10px;
font-family: inherit;
font-size: 11.5px;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.btn-ctrl:hover {
border-color: var(--accent-green);
color: var(--accent-green);
}
.btn-ctrl.btn-accent {
background: var(--bg-surface-elevated);
color: var(--accent-green);
border-color: var(--accent-green);
font-weight: bold;
}
/* Hero / Header Section */
.terminal-window {
background: var(--bg-surface);
border: 1px solid var(--border-color);
border-top: none;
border-radius: 0 0 6px 6px;
padding: 22px;
margin-bottom: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 20px rgba(0, 255, 102, 0.05);
}
.ascii-art {
color: var(--accent-green);
font-size: 10.5px;
line-height: 1.15;
white-space: pre;
overflow-x: auto;
margin-bottom: 14px;
text-shadow: 0 0 10px var(--accent-green-glow);
filter: drop-shadow(0 0 4px rgba(0,255,102,0.3));
}
.terminal-prompt {
color: var(--text-dim);
font-size: 13px;
margin-bottom: 8px;
}
.terminal-prompt span.user { color: var(--accent-cyan); font-weight: bold; }
.terminal-prompt span.host { color: var(--accent-green); }
.terminal-prompt span.path { color: var(--accent-amber); }
.tagline {
font-size: 14px;
color: var(--text-main);
margin: 12px 0 14px 0;
padding-left: 12px;
border-left: 3px solid var(--accent-green);
line-height: 1.6;
}
.sys-specs-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px dashed var(--border-color);
font-size: 12px;
}
.spec-item {
background: var(--bg-code);
padding: 10px 14px;
border: 1px solid var(--border-color);
border-radius: 4px;
}
.spec-label {
color: var(--text-muted);
text-transform: uppercase;
font-size: 10.5px;
letter-spacing: 0.5px;
display: block;
margin-bottom: 2px;
}
.spec-val {
color: var(--text-dim);
font-weight: 600;
}
/* Navigation Bar (Tabs style) */
.nav-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 20px;
border-bottom: 2px solid var(--border-color);
padding-bottom: 2px;
}
.nav-tab {
padding: 8px 14px;
background: var(--bg-surface);
border: 1px solid var(--border-color);
border-bottom: none;
color: var(--text-dim);
border-radius: 6px 6px 0 0;
cursor: pointer;
font-size: 12.5px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.15s ease;
font-family: inherit;
white-space: nowrap;
}
.nav-tab:hover {
color: var(--text-main);
background: var(--bg-surface-elevated);
}
.nav-tab.active {
background: var(--bg-surface-elevated);
color: var(--accent-green);
border-color: var(--accent-green);
border-bottom: 2px solid var(--bg-surface-elevated);
margin-bottom: -2px;
box-shadow: 0 -3px 10px rgba(0, 255, 102, 0.1);
}
.nav-tab.admin-tab {
border-color: rgba(0, 242, 254, 0.4);
color: var(--accent-cyan);
}
.nav-tab.admin-tab.active {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
}
.nav-tab .num {
color: var(--text-muted);
font-size: 11px;
}
/* Section Container */
.tab-content {
display: none;
animation: fadeIn 0.2s ease-in-out;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
/* Markdown Post Card */
.post-card {
background: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.post-card:hover {
border-color: var(--accent-green-glow);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.post-header {
display: flex;
justify-content: space-between;
align-items: baseline;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}
.post-title {
font-size: 17.5px;
color: var(--accent-green);
font-weight: 700;
}
.post-meta {
font-size: 11px;
color: var(--text-muted);
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.post-tags {
display: flex;
gap: 6px;
margin: 8px 0 12px 0;
flex-wrap: wrap;
}
.tag {
font-size: 11px;
background: var(--bg-code);
border: 1px solid var(--border-color);
color: var(--text-dim);
padding: 2px 7px;
border-radius: 3px;
}
.tag.green { color: var(--accent-green); border-color: rgba(0, 255, 102, 0.3); }
.tag.cyan { color: var(--accent-cyan); border-color: rgba(0, 242, 254, 0.3); }
.tag.amber { color: var(--accent-amber); border-color: rgba(245, 158, 11, 0.3); }
/* Code block & Lissyara / FreeBSD styling */
.code-block {
background: var(--bg-code);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px;
margin: 12px 0;
font-size: 12px;
overflow-x: auto;
color: var(--text-main);
}
.code-header {
display: flex;
justify-content: space-between;
font-size: 11px;
color: var(--text-muted);
border-bottom: 1px solid var(--border-color);
margin: -12px -12px 8px -12px;
padding: 6px 10px;
background: var(--terminal-header-bg);
border-radius: 4px 4px 0 0;
}
.code-comment { color: var(--text-muted); font-style: italic; }
.code-cmd { color: var(--accent-green); font-weight: bold; }
.code-arg { color: var(--accent-cyan); }
/* FreeBSD lissyara alert boxes */
.alert-box {
padding: 12px 16px;
border-left: 4px solid var(--accent-green);
background: var(--bg-surface-elevated);
margin: 14px 0;
border-radius: 0 4px 4px 0;
font-size: 13px;
}
.alert-box.tip { border-left-color: var(--accent-cyan); }
.alert-box.warn { border-left-color: var(--accent-amber); }
.alert-box.danger { border-left-color: var(--accent-red); }
.alert-title { font-weight: bold; margin-bottom: 4px; display: block; font-size: 12px; text-transform: uppercase; }
/* Telemetry Table */
.telemetry-table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
font-size: 12px;
}
.telemetry-table th, .telemetry-table td {
border: 1px solid var(--border-color);
padding: 6px 10px;
text-align: left;
}
.telemetry-table th {
background: var(--terminal-header-bg);
color: var(--accent-green);
font-weight: 600;
}
.telemetry-table tr:nth-child(even) {
background: var(--bg-code);
}
/* Mobile Photography Gallery Grid */
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 18px;
margin-top: 14px;
}
.photo-card {
background: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 6px;
overflow: hidden;
display: flex;
flex-direction: column;
cursor: pointer;
transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s;
}
.photo-card:hover {
transform: translateY(-3px);
border-color: var(--accent-green);
box-shadow: 0 8px 24px rgba(0, 255, 102, 0.15);
}
.photo-thumb-wrap {
position: relative;
width: 100%;
height: 240px;
background: #000;
overflow: hidden;
}
.photo-thumb-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
.photo-card:hover .photo-thumb-wrap img {
transform: scale(1.03);
}
.photo-badge-raw {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.75);
border: 1px solid var(--accent-green);
color: var(--accent-green);
font-size: 10px;
padding: 2px 6px;
border-radius: 3px;
font-weight: bold;
letter-spacing: 0.5px;
backdrop-filter: blur(4px);
}
.photo-info {
padding: 12px;
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: space-between;
}
.photo-title {
color: var(--text-main);
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}
.photo-desc {
color: var(--text-dim);
font-size: 12px;
margin-bottom: 10px;
line-height: 1.4;
}
.photo-exif {
display: flex;
flex-wrap: wrap;
gap: 5px;
font-size: 10.5px;
color: var(--text-muted);
background: var(--bg-code);
padding: 5px 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
}
.exif-pill {
color: var(--accent-cyan);
}
/* Lightbox Modal */
.lightbox-modal {
display: none;
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background: rgba(4, 8, 5, 0.95);
z-index: 2000;
backdrop-filter: blur(8px);
align-items: center;
justify-content: center;
padding: 16px;
}
.lightbox-modal.active {
display: flex;
}
.lightbox-content {
background: var(--bg-surface);
border: 1px solid var(--accent-green);
box-shadow: 0 0 30px rgba(0, 255, 102, 0.2);
border-radius: 8px;
max-width: 960px;
width: 100%;
max-height: 92vh;
display: flex;
flex-direction: column;
overflow: hidden;
animation: modalPop 0.2s ease-out;
}
@keyframes modalPop {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.lightbox-header {
padding: 8px 14px;
background: var(--terminal-header-bg);
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
color: var(--accent-green);
font-size: 13px;
}
.lightbox-close {
background: none;
border: none;
color: var(--text-dim);
font-size: 18px;
cursor: pointer;
font-family: inherit;
}
.lightbox-close:hover {
color: var(--accent-red);
}
.lightbox-body {
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 16px;
gap: 16px;
}
@media (min-width: 768px) {
.lightbox-body { flex-direction: row; }
.lightbox-img-pane { flex: 1.5; }
.lightbox-info-pane { flex: 1; }
}
.lightbox-img-pane img {
width: 100%;
height: auto;
border-radius: 4px;
border: 1px solid var(--border-color);
}
.lightbox-info-pane {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 12.5px;
}
/* Gear Calculator */
.calculator-box {
background: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.calc-form {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 14px;
margin: 16px 0;
}
.form-group label {
display: block;
font-size: 11px;
color: var(--text-muted);
margin-bottom: 4px;
text-transform: uppercase;
}
.form-group input, .form-group select, .form-group textarea {
width: 100%;
background: var(--bg-code);
border: 1px solid var(--border-color);
color: var(--text-main);
padding: 8px 10px;
border-radius: 4px;
font-family: inherit;
font-size: 13px;
}
.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
outline: none;
border-color: var(--accent-green);
box-shadow: 0 0 8px var(--accent-green-glow);
}
.calc-results-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 10px;
margin-top: 14px;
padding: 14px;
background: var(--bg-code);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.res-stat { text-align: center; }
.res-num { font-size: 20px; font-weight: bold; color: var(--accent-green); }
.res-desc { font-size: 10.5px; color: var(--text-muted); }
/* ========================================= */
/* ADMIN PANEL & TERMINAL CMS STYLES */
/* ========================================= */
.admin-nav-bar {
display: flex;
gap: 6px;
flex-wrap: wrap;
margin-bottom: 18px;
background: var(--bg-surface);
padding: 8px;
border: 1px solid var(--border-color);
border-radius: 6px;
}
.admin-subtab-btn {
background: var(--bg-code);
border: 1px solid var(--border-color);
color: var(--text-dim);
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
font-size: 12px;
transition: all 0.15s;
}
.admin-subtab-btn:hover {
color: var(--accent-green);
border-color: var(--accent-green);
}
.admin-subtab-btn.active {
background: var(--bg-surface-elevated);
color: var(--accent-green);
border-color: var(--accent-green);
font-weight: bold;
box-shadow: 0 0 8px rgba(0, 255, 102, 0.2);
}
.admin-subtab-view {
display: none;
}
.admin-subtab-view.active {
display: block;
}
/* Markdown Toolbar */
.md-toolbar {
display: flex;
gap: 4px;
flex-wrap: wrap;
background: var(--terminal-header-bg);
border: 1px solid var(--border-color);
border-bottom: none;
border-radius: 4px 4px 0 0;
padding: 6px 8px;
}
.md-btn {
background: var(--bg-surface);
border: 1px solid var(--border-color);
color: var(--text-dim);
padding: 3px 8px;
border-radius: 3px;
font-family: inherit;
font-size: 11px;
cursor: pointer;
transition: all 0.15s;
}
.md-btn:hover {
color: var(--accent-green);
border-color: var(--accent-green);
}
/* Split Editor Layout */
.editor-split-grid {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
@media (min-width: 860px) {
.editor-split-grid {
grid-template-columns: 1fr 1fr;
}
}
.editor-pane textarea {
border-radius: 0 0 4px 4px !important;
min-height: 320px;
resize: vertical;
}
.preview-pane {
background: var(--bg-code);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 14px;
min-height: 320px;
max-height: 480px;
overflow-y: auto;
font-size: 13px;
}
.admin-grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin-bottom: 12px;
}
.admin-grid-3 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 10px;
margin-bottom: 12px;
}
.file-upload-zone {
border: 2px dashed var(--border-color);
border-radius: 6px;
padding: 16px;
text-align: center;
background: var(--bg-code);
cursor: pointer;
transition: border-color 0.2s;
margin-bottom: 12px;
}
.file-upload-zone:hover {
border-color: var(--accent-green);
}
/* Toast Notifications */
#matrix-toast-notification {
display: none;
position: fixed;
bottom: 24px;
right: 24px;
background: var(--bg-surface-elevated);
border: 1px solid var(--accent-green);
color: var(--accent-green);
padding: 10px 18px;
border-radius: 6px;
box-shadow: 0 0 20px rgba(0, 255, 102, 0.3);
z-index: 3000;
font-size: 13px;
font-weight: bold;
animation: fadeIn 0.2s ease;
}
/* Footer */
footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid var(--border-color);
font-size: 12px;
color: var(--text-muted);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.footer-easter-egg {
cursor: pointer;
user-select: none;
}
.footer-easter-egg:hover {
color: var(--accent-green);
}
/* Mobile tweaks */
@media (max-width: 600px) {
.site-wrapper { padding: 10px 8px 60px 8px; }
.ascii-art { font-size: 6.8px; }
.nav-tab { padding: 6px 10px; font-size: 12px; }
.sys-specs-grid { grid-template-columns: 1fr; }
.top-system-bar { flex-direction: column; align-items: flex-start; }
.controls-group { margin-left: 0; width: 100%; justify-content: space-between; }
.md-toolbar { overflow-x: auto; flex-wrap: nowrap; }
}
</style>
</head>
<body>
<div class="scanlines"></div>
<canvas id="matrix-canvas"></canvas>
<div class="site-wrapper">
<!-- Top System Bar -->
<div class="top-system-bar">
<div class="sys-badges">
<span class="badge">📅 <span id="sys-live-date">--.--.----</span></span>
<span class="badge">⏱️ <span id="sys-live-clock">--:--:--</span></span>
<span class="badge">🚲 Aspect Air 2025 29"</span>
<span class="badge">📍 54.3141° N, 48.4031° E (Ульяновск)</span>
</div>
<div class="controls-group">
<button class="btn-ctrl" onclick="toggleTheme()" title="Сменить тему оформления">[THEME: <span id="theme-name">MATRIX</span>]</button>
<button class="btn-ctrl" onclick="toggleMatrixRain(true)" title="Полноэкранный режим дождя матрицы">[RAIN: M]</button>
<button class="btn-ctrl btn-accent" onclick="openAdminTab()" title="Открыть панель управления">[⚡ ADMIN]</button>
</div>
</div>
<!-- Terminal Window / Hero -->
<header class="terminal-window">
<div class="ascii-art">
███╗ ███╗ █████╗ ████████╗██████╗ ██╗██╗ ██╗██╗ ██╗ █████╗ ███████╗██╗ ██╗ ██████╗ ██╗ ██╗
████╗ ████║██╔══██╗╚══██╔══╝██╔══██╗██║╚██╗██╔╝██║ ██║██╔══██╗██╔════╝╚██╗ ██╔╝██╔═══██╗██║ ██║
██╔████╔██║███████║ ██║ ██████╔╝██║ ╚███╔╝ ███████║███████║███████╗ ╚████╔╝ ██║ ██║██║ ██║
██║╚██╔╝██║██╔══██║ ██║ ██╔══██╗██║ ██╔██╗ ██╔══██║██╔══██║╚════██║ ╚██╔╝ ██║ ██║██║ ██║
██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║██║██╔╝ ██╗██║ ██║██║ ██║███████║ ██║ ╚██████╔╝╚██████╔╝
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═════╝
</div>
<div class="terminal-prompt">
<span class="user">matrixhasyou</span>@<span class="host">ulyanovsk-aspect</span>:<span class="path">~/volga/feed</span>$ ./init_rm-rf.sh --ride --photo --book
</div>
<p class="tagline">
Персональный журнал заездов по Ульяновску и берегам Волги на найнере <strong>Aspect Air 2025 29" MTB</strong>, полевые HOWTO по ремонту и ТО, книжная полка, тренировочные программы и архив мобильной RAW-фотографии.
</p>
<div class="sys-specs-grid">
<div class="spec-item">
<span class="spec-label">ТЕКУЩИЙ ВЕЛОСИПЕД</span>
<span class="spec-val">Aspect Air 2025 29" MTB (Custom)</span>
</div>
<div class="spec-item">
<span class="spec-label">ЛОКАЦИЯ</span>
<span class="spec-val">Ульяновск</span>
</div>
<div class="spec-item">
<span class="spec-label">ТРАНСМИССИЯ И ПОКРЫШКИ</span>
<span class="spec-val">1x9 Shimano CUES / Kenda Booster 29x2.25" (2.0 бар)</span>
</div>
</div>
</header>
<!-- Navigation Tabs -->
<nav class="nav-tabs" role="tablist">
<button class="nav-tab active" onclick="switchTab('notes', this)"><span class="num">[01]</span> 🚴‍♂️ ~/notes/volga_rides</button>
<button class="nav-tab" onclick="switchTab('gallery', this)"><span class="num">[02]</span> 📸 ~/gallery/mobile_raw</button>
<button class="nav-tab" onclick="switchTab('howto', this)"><span class="num">[03]</span> 🛠️ ~/docs/aspect_howto</button>
<button class="nav-tab" onclick="switchTab('calculator', this)"><span class="num">[04]</span> ⚙️ ~/tools/29er_calc</button>
<button class="nav-tab" onclick="switchTab('books', this)"><span class="num">[05]</span> 📚 ~/books/reviews</button>
<button class="nav-tab" onclick="switchTab('journal', this)"><span class="num">[06]</span> 📓 ~/journal/daily</button>
<button class="nav-tab" onclick="switchTab('workout', this)"><span class="num">[07]</span> 🏋️ ~/workout/gym_plan</button>
<button class="nav-tab" onclick="switchTab('tennis', this)"><span class="num">[08]</span> 🏓 ~/sport/table_tennis</button>
</nav>
<!-- TAB 1: CYCLING NOTES -->
<main id="tab-notes" class="tab-content active">
<div id="notes-feed-container">
<!-- Dynamically populated from MatrixStore (Category: rides) -->
</div>
</main>
<!-- TAB 2: MOBILE PHOTOGRAPHY GALLERY -->
<section id="tab-gallery" class="tab-content">
<div class="gallery-grid" id="gallery-grid-container">
<!-- Dynamically populated from MatrixStore -->
</div>
</section>
<!-- TAB 3: ASPECT AIR 29" & FREEBSD STYLE BIKE HOWTOs -->
<section id="tab-howto" class="tab-content">
<div id="howto-feed-container">
<!-- Dynamically populated from MatrixStore (Category: howto) -->
</div>
</section>
<!-- TAB 4: 29ER GEAR CALCULATOR -->
<section id="tab-calculator" class="tab-content">
<div class="calculator-box">
<div class="post-header">
<h2 class="post-title">⚙️ Interactive 29" MTB Drivetrain Calculator</h2>
<div class="post-meta">
<span>TARGET: Aspect Air 2025 29" MTB</span>
<span>FORMULA: (Chainring / Cog) * Wheel_Diameter</span>
</div>
</div>
<p>
Калькулятор передаточного отношения, шага велосипеда (Development / Gear Inches) и скорости для найнера 29" на различных передачах и каденсе.
</p>
<div class="calc-form">
<div class="form-group">
<label for="chainring">Ведущая звезда (Chainring):</label>
<input type="number" id="chainring" value="34" min="28" max="44" oninput="calculateGears()">
</div>
<div class="form-group">
<label for="cog">Задняя звезда кассеты (Cog):</label>
<input type="number" id="cog" value="11" min="10" max="52" oninput="calculateGears()">
</div>
<div class="form-group">
<label for="cadence">Каденс (RPM):</label>
<input type="number" id="cadence" value="85" min="40" max="150" oninput="calculateGears()">
</div>
<div class="form-group">
<label for="tire-size">Размер колеса / Покрышка:</label>
<select id="tire-size" onchange="calculateGears()">
<option value="2280" selected>29" x 2.25" MTB (Aspect Air 2025 — 2280 mm)</option>
<option value="2300">29" x 2.35" MTB Aggressive (2300 mm)</option>
<option value="2250">29" x 2.10" MTB XC (2250 mm)</option>
<option value="2180">27.5" x 2.25" / Gravel 700x40c (2180 mm)</option>
</select>
</div>
</div>
<div class="calc-results-grid">
<div class="res-stat">
<div class="res-num" id="res-ratio">3.09</div>
<div class="res-desc">Gear Ratio (Передаточное)</div>
</div>
<div class="res-stat">
<div class="res-num" id="res-inches">88.2"</div>
<div class="res-desc">Gear Inches (Дюймы)</div>
</div>
<div class="res-stat">
<div class="res-num" id="res-dev">7.05 m</div>
<div class="res-desc">Шаг за 1 оборот шатуна</div>
</div>
<div class="res-stat">
<div class="res-num" id="res-speed">35.9 km/h</div>
<div class="res-desc">Скорость при каденсе</div>
</div>
</div>
</div>
</section>
<!-- TAB 5: BOOKS REVIEWS -->
<section id="tab-books" class="tab-content">
<div id="books-feed-container">
<!-- Dynamically populated from MatrixStore (Category: books) -->
</div>
</section>
<!-- TAB 6: ADMIN PANEL & MOBILE POSTING CMS -->
<section id="tab-admin" class="tab-content">
<!-- Auth Lock Screen -->
<div id="admin-lock-screen" class="post-card" style="max-width: 440px; margin: 40px auto;">
<div class="post-header" style="justify-content: center;">
<h2 class="post-title">🔐 Авторизация Оператора</h2>
</div>
<form onsubmit="event.preventDefault(); submitAdminCredentials();" style="margin-top: 14px;">
<div class="form-group" style="margin-bottom: 12px;">
<label for="admin-login-input">Логин:</label>
<input type="text" id="admin-login-input" autocomplete="username" required>
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label for="admin-pass-input">Пароль:</label>
<input type="password" id="admin-pass-input" autocomplete="current-password" required>
</div>
<button type="submit" class="btn-ctrl btn-accent" style="width: 100%; justify-content: center; padding: 9px; font-size: 13px;">
[ ВОЙТИ В КОНСОЛЬ ]
</button>
</form>
</div>
<!-- Main Admin Console -->
<div id="admin-main-panel" style="display: none;">
<div class="post-card" style="border-left: 4px solid var(--accent-cyan); margin-bottom: 16px;">
<div class="post-header">
<h2 class="post-title">⚡ Matrix Control Console // ~/admin</h2>
<div class="post-meta">
<span>ОПЕРАТОР: MatrixHasYou • Ульяновск</span>
<button class="btn-ctrl" style="color: var(--accent-red);" onclick="MatrixAdmin.lockAdmin()">[ ВЫЙТИ ]</button>
</div>
</div>
<p style="font-size: 12.5px; color: var(--text-dim);">
Мобильная и десктопная панель для оформления заметок по всем разделам (заезды, книги, ежедневник, зал, теннис), загрузки RAW фото с авто-EXIF и бэкапов.
</p>
</div>
<!-- Admin Sub Navigation -->
<div class="admin-nav-bar">
<button class="admin-subtab-btn active" onclick="switchAdminTab('note-editor')">📝 Новая запись</button>
<button class="admin-subtab-btn" onclick="switchAdminTab('photo-editor')">📸 Новое фото</button>
<button class="admin-subtab-btn" onclick="switchAdminTab('manager')">📋 Публикации и Таймер</button>
<button class="admin-subtab-btn" onclick="switchAdminTab('backup')">💾 Бэкап и Синхронизация</button>
</div>
<!-- SUBTAB 1: NOTE & POST EDITOR -->
<div id="subtab-note-editor" class="admin-subtab-view active">
<div class="post-card">
<div class="post-header">
<h3 class="post-title" id="admin-note-form-title" style="font-size: 16px;">📝 Создание новой записи блога</h3>
<button class="btn-ctrl" onclick="MatrixAdmin.resetNoteForm()">[ СБРОСИТЬ ФОРМУ ]</button>
</div>
<form onsubmit="MatrixAdmin.handleSaveNote(event)">
<div class="admin-grid-2" style="margin-bottom: 12px;">
<div class="form-group">
<label for="admin-note-category">Раздел публикации *:</label>
<select id="admin-note-category">
<option value="rides">🚴‍♂️ ~/notes/volga_rides (Велозаметки)</option>
<option value="books">📚 ~/books/reviews (Книжная полка)</option>
<option value="journal">📓 ~/journal/daily (Ежедневник)</option>
<option value="workout">🏋️ ~/workout/gym_plan (Программа тренировок)</option>
<option value="tennis">🏓 ~/sport/table_tennis (Настольный теннис)</option>
</select>
</div>
<div class="form-group">
<label for="admin-note-status">Статус публикации:</label>
<select id="admin-note-status">
<option value="published">⚡ Опубликовать сразу (Live)</option>
<option value="scheduled">⏳ По таймеру (Scheduled)</option>
<option value="draft">📁 Черновик (Draft)</option>
</select>
</div>
</div>
<div class="form-group" style="margin-bottom: 12px;">
<label for="admin-note-title">Заголовок записи *:</label>
<input type="text" id="admin-note-title" placeholder="Введите заголовок записи..." required>
</div>
<div class="admin-grid-2">
<div class="form-group">
<label for="admin-note-date">Дата / Таймер публикации:</label>
<input type="datetime-local" id="admin-note-date">
</div>
<div class="form-group">
<label for="admin-note-tags">Теги (через запятую):</label>
<input type="text" id="admin-note-tags" placeholder="ulyanovsk, aspect_air, training">
</div>
</div>
<!-- Optional Telemetry Section -->
<div style="background: var(--bg-code); border: 1px solid var(--border-color); border-radius: 4px; padding: 12px; margin-bottom: 14px;">
<span style="display: block; font-size: 11px; color: var(--accent-cyan); font-weight: bold; margin-bottom: 8px; text-transform: uppercase;">📊 Параметры заезда / телеметрия (для велозаметок)</span>
<div class="admin-grid-3">
<div class="form-group">
<label for="admin-tele-dist">Дистанция:</label>
<input type="text" id="admin-tele-dist" placeholder="85.4 km">
</div>
<div class="form-group">
<label for="admin-tele-speed">Ср. Скорость:</label>
<input type="text" id="admin-tele-speed" placeholder="26.8 km/h">
</div>
<div class="form-group">
<label for="admin-tele-elev">Набор высоты:</label>
<input type="text" id="admin-tele-elev" placeholder="+520 m">
</div>
<div class="form-group">
<label for="admin-tele-watts">Мощность:</label>
<input type="text" id="admin-tele-watts" placeholder="215 W">
</div>
<div class="form-group">
<label for="admin-tele-cadence">Каденс:</label>
<input type="text" id="admin-tele-cadence" placeholder="88 rpm">
</div>
<div class="form-group">
<label for="admin-tele-hr">Пульс ср.:</label>
<input type="text" id="admin-tele-hr" placeholder="138 bpm">
</div>
</div>
</div>
<!-- Hero Image Uploader -->
<div class="form-group" style="margin-bottom: 14px;">
<label>Главное фото записи (Hero Image):</label>
<div class="file-upload-zone" onclick="document.getElementById('admin-note-file').click()">
<input type="file" id="admin-note-file" accept="image/*" style="display: none;">
<span style="color: var(--accent-green); font-size: 13px;">📷 Нажмите для выбора фото со смартфона или ПК</span>
<p style="color: var(--text-muted); font-size: 11px; margin-top: 4px;">(Автоматически сжимается и оптимизируется)</p>
</div>
<div id="admin-note-file-preview-wrap" style="display: none; margin-top: 8px; position: relative; max-width: 320px;">
<img id="admin-note-file-preview" src="" alt="Preview" style="width: 100%; border-radius: 4px; border: 1px solid var(--accent-green);">
<button type="button" class="btn-ctrl" style="color: var(--accent-red); margin-top: 4px;" onclick="removeNoteHeroImage()">[ Удалить фото ]</button>
</div>
</div>
<!-- Markdown Toolbar & Split Editor -->
<div class="form-group" style="margin-bottom: 16px;">
<label for="admin-note-content">Текст записи (Markdown) *:</label>
<div class="md-toolbar">
<button type="button" class="md-btn" onclick="insertMarkdown('**', '**', 'жирный')"><strong>B</strong></button>
<button type="button" class="md-btn" onclick="insertMarkdown('*', '*', 'курсив')"><em>I</em></button>
<button type="button" class="md-btn" onclick="insertMarkdown('## ', '', 'Заголовок секции')">H2</button>
<button type="button" class="md-btn" onclick="insertMarkdown('### ', '', 'Подзаголовок')">H3</button>
<button type="button" class="md-btn" onclick="insertMarkdown('`', '`', 'код')">Code</button>
<button type="button" class="md-btn" onclick="insertMarkdown('```bash:TERMINAL\n', '\n```', '# Команды')">Block</button>
<button type="button" class="md-btn" onclick="insertMarkdown('> [!TIP] Совет / Цитата:\n> ', '', 'Текст...')">💡 Tip</button>
<button type="button" class="md-btn" onclick="insertMarkdown('> [!WARN] Внимание:\n> ', '', 'Текст...')">⚠️ Warn</button>
<button type="button" class="md-btn" onclick="insertMarkdown('- ', '', 'Элемент списка')">• List</button>
<button type="button" class="md-btn" onclick="insertMarkdown('![Описание фото](', ')', 'https://...')">📷 Photo</button>
<button type="button" class="md-btn" onclick="insertMarkdown('[Текст ссылки](', ')', 'https://...')">🔗 Link</button>
</div>
<div class="editor-split-grid">
<div class="editor-pane">
<textarea id="admin-note-content" placeholder="Напишите текст в Markdown формате... Поддерживаются заголовки, списки, блоки кода, алерты и ссылки." required></textarea>
</div>
<div class="preview-pane" id="admin-note-preview-pane">
<em style="color: var(--text-muted);">Live Markdown превью...</em>
</div>
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="submit" class="btn-ctrl btn-accent" style="padding: 8px 24px; font-size: 13px;">[ ⚡ СОХРАНИТЬ И ОПУБЛИКОВАТЬ ]</button>
<button type="button" class="btn-ctrl" onclick="MatrixAdmin.resetNoteForm()">[ ОЧИСТИТЬ ]</button>
</div>
</form>
</div>
</div>
<!-- SUBTAB 2: RAW PHOTO SHOWCASE EDITOR -->
<div id="subtab-photo-editor" class="admin-subtab-view">
<div class="post-card">
<div class="post-header">
<h3 class="post-title" id="admin-photo-form-title" style="font-size: 16px;">📸 Загрузка мобильного RAW фото</h3>
<button class="btn-ctrl" onclick="MatrixAdmin.resetPhotoForm()">[ СБРОСИТЬ ФОРМУ ]</button>
</div>
<form onsubmit="MatrixAdmin.handleSavePhoto(event)">
<div class="form-group" style="margin-bottom: 14px;">
<label>Файл фотографии *:</label>
<div class="file-upload-zone" onclick="document.getElementById('admin-photo-file').click()">
<input type="file" id="admin-photo-file" accept="image/*" style="display: none;">
<span style="color: var(--accent-cyan); font-size: 14px;">📸 Снять на камеру смартфона или выбрать из галереи</span>
<p style="color: var(--text-muted); font-size: 11px; margin-top: 4px;">(Параметры EXIF будут считаны автоматически)</p>
</div>
<div id="admin-photo-file-preview-wrap" style="display: none; margin-top: 8px; max-width: 380px;">
<img id="admin-photo-file-preview" src="" alt="Photo Preview" style="width: 100%; border-radius: 4px; border: 1px solid var(--accent-cyan);">
</div>
</div>
<div class="admin-grid-2">
<div class="form-group">
<label for="admin-photo-title">Название кадра:</label>
<input type="text" id="admin-photo-title" placeholder="05. Волжский рассвет над Сенгилеем">
</div>
<div class="form-group">
<label for="admin-photo-badge">Бейдж формата:</label>
<input type="text" id="admin-photo-badge" value="RAW DNG" placeholder="RAW DNG / ProRAW / MACRO">
</div>
</div>
<div class="admin-grid-2">
<div class="form-group">
<label for="admin-photo-status">Статус:</label>
<select id="admin-photo-status">
<option value="published">⚡ Опубликовать сразу (Live)</option>
<option value="scheduled">⏳ По таймеру (Scheduled)</option>
<option value="draft">📁 Черновик (Draft)</option>
</select>
</div>
<div class="form-group">
<label for="admin-photo-date">Дата / Таймер публикации:</label>
<input type="datetime-local" id="admin-photo-date">
</div>
</div>
<div class="form-group" style="margin-bottom: 14px;">
<label for="admin-photo-desc">Комментарий / История кадра:</label>
<textarea id="admin-photo-desc" rows="3" placeholder="Опишите условия съемки, освещение, локацию в Ульяновске и настроение..."></textarea>
</div>
<!-- EXIF Metadata Card -->
<div style="background: var(--bg-code); border: 1px solid var(--border-color); border-radius: 4px; padding: 12px; margin-bottom: 16px;">
<span style="display: block; font-size: 11px; color: var(--accent-green); font-weight: bold; margin-bottom: 8px; text-transform: uppercase;">📱 Параметры съемки & EXIF (Автозаполнение)</span>
<div class="admin-grid-3">
<div class="form-group">
<label for="admin-photo-lens">Объектив / Фокусное:</label>
<input type="text" id="admin-photo-lens" value="24mm">
</div>
<div class="form-group">
<label for="admin-photo-aperture">Диафрагма:</label>
<input type="text" id="admin-photo-aperture" value="f/1.8">
</div>
<div class="form-group">
<label for="admin-photo-shutter">Выдержка:</label>
<input type="text" id="admin-photo-shutter" value="1/120s">
</div>
<div class="form-group">
<label for="admin-photo-iso">ISO:</label>
<input type="text" id="admin-photo-iso" value="ISO 100">
</div>
<div class="form-group">
<label for="admin-photo-engine">Движок / Камера:</label>
<input type="text" id="admin-photo-engine" value="Mobile RAW">
</div>
<div class="form-group">
<label for="admin-photo-location">Локация:</label>
<input type="text" id="admin-photo-location" value="Ульяновск, Волга">
</div>
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="submit" class="btn-ctrl btn-accent" style="padding: 8px 24px; font-size: 13px;">[ 📸 ОПУБЛИКОВАТЬ В ГАЛЕРЕЮ ]</button>
<button type="button" class="btn-ctrl" onclick="MatrixAdmin.resetPhotoForm()">[ ОЧИСТИТЬ ]</button>
</div>
</form>
</div>
</div>
<!-- SUBTAB 3: CONTENT & TIMER MANAGER -->
<div id="subtab-manager" class="admin-subtab-view">
<div class="post-card">
<div class="post-header">
<h3 class="post-title" style="font-size: 16px;">📋 Все публикации блога и таймеры</h3>
<button class="btn-ctrl" onclick="MatrixAdmin.init()">[ ОБНОВИТЬ СПИСОК ]</button>
</div>
<p style="color: var(--text-dim); font-size: 12px; margin-bottom: 12px;">
Управляйте статусами записей всех разделов: публикуйте отложенные посты досрочно, редактируйте текст и удаляйте ненужные.
</p>
<div style="overflow-x: auto;">
<table class="telemetry-table" style="min-width: 550px;">
<thead>
<tr>
<th>Раздел</th>
<th>Заголовок</th>
<th>Дата / Таймер</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="admin-posts-table-body">
<!-- Populated via MatrixAdmin.renderAdminManager -->
</tbody>
</table>
</div>
</div>
</div>
<!-- SUBTAB 4: BACKUP & EXPORT -->
<div id="subtab-backup" class="admin-subtab-view">
<div class="post-card">
<div class="post-header">
<h3 class="post-title" style="font-size: 16px;">💾 Резервные копии и Синхронизация данных</h3>
</div>
<p style="color: var(--text-dim); margin-bottom: 16px;">
Все ваши публикации, черновики и фотографии хранятся локально в браузере. Вы можете в любой момент экспортировать базу в единый JSON-файл для переноса на другой компьютер или телефон.
</p>
<div style="display: flex; flex-direction: column; gap: 14px; max-width: 440px;">
<button class="btn-ctrl btn-accent" style="padding: 10px;" onclick="MatrixAdmin.exportBackup()">
📥 [ СКАЧАТЬ JSON БЭКАП ВСЕХ ЗАПИСЕЙ И ФОТО ]
</button>
<div style="border: 1px dashed var(--border-color); padding: 12px; border-radius: 4px; background: var(--bg-code);">
<span style="display: block; font-size: 11px; color: var(--text-muted); margin-bottom: 6px; text-transform: uppercase;">Восстановление из файла:</span>
<input type="file" id="admin-backup-file-input" accept=".json" onchange="if(this.files[0]) MatrixAdmin.importBackup(this.files[0])" style="font-size: 12px;">
</div>
<button class="btn-ctrl" style="color: var(--accent-amber); padding: 8px;" onclick="if(confirm('Сбросить все записи к начальным данным?')) { MatrixStore.resetDefaults(); MatrixAdmin.init(); }">
🔄 [ Сбросить к стандартным записям ]
</button>
</div>
</div>
</div>
</div>
</section>
<!-- TAB 7: DAILY JOURNAL -->
<section id="tab-journal" class="tab-content">
<div id="journal-feed-container">
<!-- Dynamically populated from MatrixStore (Category: journal) -->
</div>
</section>
<!-- TAB 8: GYM WORKOUT PLAN -->
<section id="tab-workout" class="tab-content">
<div id="workout-feed-container">
<!-- Dynamically populated from MatrixStore (Category: workout) -->
</div>
</section>
<!-- TAB 9: TABLE TENNIS -->
<section id="tab-tennis" class="tab-content">
<div id="tennis-feed-container">
<!-- Dynamically populated from MatrixStore (Category: tennis) -->
</div>
</section>
<!-- Footer -->
<footer>
<div>
[MatrixHasYou] &copy; 2026 // Ульяновск • Волга • Aspect Air 2025 29" • ЖПЧХ.
</div>
<div class="footer-easter-egg" onclick="triggerEasterEgg()">
<span style="color: var(--accent-green);">[!]</span> <em>"Самурай почистит и смажет цепь, даже если сегодня никуда не выезжал... 🚴‍♂️🥋"</em>
</div>
</footer>
</div>
<!-- Lightbox Modal Component -->
<div id="photo-lightbox" class="lightbox-modal" onclick="closeLightbox(event)">
<div class="lightbox-content" onclick="event.stopPropagation()">
<div class="lightbox-header">
<span id="lb-title">IMAGE_VIEWER // RAW • УЛЬЯНОВСК</span>
<button class="lightbox-close" onclick="closeLightbox()">[✕ CLOSE]</button>
</div>
<div class="lightbox-body">
<div class="lightbox-img-pane">
<img id="lb-img" src="" alt="Full Preview">
</div>
<div class="lightbox-info-pane">
<h3 id="lb-heading" style="color: var(--accent-green); font-size: 16px;">Photo Title</h3>
<p id="lb-desc" style="color: var(--text-main); font-size: 12px; line-height: 1.5;">Photo description...</p>
<div style="margin-top: 10px; border-top: 1px solid var(--border-color); padding-top: 10px;">
<span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase;">EXIF Metadata:</span>
<table class="telemetry-table" style="margin-top: 6px;">
<tr><td>Lens / Focal</td><td id="lb-lens" style="color: var(--accent-cyan);">24mm</td></tr>
<tr><td>Aperture</td><td id="lb-aperture" style="color: var(--accent-green);">f/1.8</td></tr>
<tr><td>Shutter Speed</td><td id="lb-shutter" style="color: var(--accent-amber);">1/250s</td></tr>
<tr><td>ISO Sensitivity</td><td id="lb-iso" style="color: var(--accent-cyan);">ISO 64</td></tr>
<tr><td>Capture Engine</td><td id="lb-engine">Mobile ProRAW</td></tr>
<tr><td>Location Sector</td><td id="lb-location">Ульяновск / Волга</td></tr>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Toast Notification -->
<div id="matrix-toast-notification"></div>
<!-- Core Scripts -->
<script src="js/markdown-parser.js"></script>
<script src="js/image-processor.js"></script>
<script src="js/posts-store.js?v=6"></script>
<script src="js/admin-controller.js?v=6"></script>
<!-- Matrix Rain Canvas & Interactivity Script -->
<script>
// Matrix Rain Canvas Animation
const canvas = document.getElementById('matrix-canvas');
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
initRain();
});
const characters = '01アイウエオカキクケコサシスセソタチツテトナニヌネハヒフヘホマミムメモヤユヨラリルレロワヲンMATRIXHASYOUSRAMASPECTAIR2025MTB29ERULYANOVSKVOLGAЖПЧХ';
const fontSize = 14;
let columns = Math.floor(width / fontSize);
let drops = [];
function initRain() {
columns = Math.floor(width / fontSize);
drops = [];
for (let i = 0; i < columns; i++) {
drops[i] = Math.random() * -100;
}
}
initRain();
function drawMatrixRain() {
ctx.fillStyle = 'rgba(7, 13, 9, 0.08)';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = '#00ff66';
ctx.font = fontSize + 'px monospace';
for (let i = 0; i < drops.length; i++) {
const char = characters.charAt(Math.floor(Math.random() * characters.length));
const x = i * fontSize;
const y = drops[i] * fontSize;
if (Math.random() > 0.85) {
ctx.fillStyle = '#ffffff';
} else if (Math.random() > 0.7) {
ctx.fillStyle = '#38ef7d';
} else {
ctx.fillStyle = '#00cc55';
}
ctx.fillText(char, x, y);
if (y > height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i]++;
}
requestAnimationFrame(drawMatrixRain);
}
drawMatrixRain();
// Full Matrix Rain Toggle
let fullRainMode = false;
function toggleMatrixRain(forceToggle) {
fullRainMode = forceToggle ? !fullRainMode : fullRainMode;
if (fullRainMode) {
canvas.classList.add('full-mode');
} else {
canvas.classList.remove('full-mode');
}
}
canvas.addEventListener('click', () => {
if (fullRainMode) toggleMatrixRain(true);
});
// Theme Switcher
const themes = ['matrix', 'amber', 'github-dark'];
let currentThemeIndex = 0;
function toggleTheme() {
currentThemeIndex = (currentThemeIndex + 1) % themes.length;
const theme = themes[currentThemeIndex];
if (theme === 'matrix') {
document.documentElement.removeAttribute('data-theme');
document.getElementById('theme-name').innerText = 'MATRIX';
} else if (theme === 'amber') {
document.documentElement.setAttribute('data-theme', 'amber');
document.getElementById('theme-name').innerText = 'AMBER_CRT';
} else {
document.documentElement.setAttribute('data-theme', 'github-dark');
document.getElementById('theme-name').innerText = 'GITHUB_DARK';
}
}
// Tab Switching
function switchTab(tabId, el) {
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.nav-tab').forEach(btn => btn.classList.remove('active'));
const target = document.getElementById('tab-' + tabId);
if (target) {
target.classList.add('active');
}
if (el) {
el.classList.add('active');
} else {
const correspondingBtn = Array.from(document.querySelectorAll('.nav-tab')).find(b => b.getAttribute('onclick')?.includes(`'${tabId}'`));
if (correspondingBtn) correspondingBtn.classList.add('active');
}
if (tabId === 'admin') {
MatrixAdmin.checkAdminAuth();
}
}
function openAdminTab() {
switchTab('admin');
}
// Admin Subtab Switching
function switchAdminTab(subtabId) {
document.querySelectorAll('.admin-subtab-view').forEach(view => view.classList.remove('active'));
document.querySelectorAll('.admin-subtab-btn').forEach(btn => btn.classList.remove('active'));
const targetView = document.getElementById('subtab-' + subtabId);
if (targetView) targetView.classList.add('active');
const activeBtn = Array.from(document.querySelectorAll('.admin-subtab-btn')).find(b => b.getAttribute('onclick')?.includes(subtabId));
if (activeBtn) activeBtn.classList.add('active');
}
// Admin Auth Helper
function submitAdminCredentials() {
const user = document.getElementById('admin-login-input').value.trim();
const pass = document.getElementById('admin-pass-input').value;
if (MatrixAdmin.unlockAdmin(user, pass)) {
document.getElementById('admin-login-input').value = '';
document.getElementById('admin-pass-input').value = '';
} else {
alert('Неверный логин или пароль оператора!');
}
}
function removeNoteHeroImage() {
document.getElementById('admin-note-file').value = '';
document.getElementById('admin-note-file-preview-wrap').style.display = 'none';
}
// Lightbox Handler
function openLightbox(src, title, desc, lens, shutter, iso, engine, location) {
document.getElementById('lb-img').src = src;
document.getElementById('lb-title').innerText = 'EXIF // ' + title;
document.getElementById('lb-heading').innerText = title;
document.getElementById('lb-desc').innerText = desc;
document.getElementById('lb-lens').innerText = lens;
document.getElementById('lb-aperture').innerText = lens.split(' ')[1] || 'f/1.8';
document.getElementById('lb-shutter').innerText = shutter;
document.getElementById('lb-iso').innerText = iso;
document.getElementById('lb-engine').innerText = engine;
document.getElementById('lb-location').innerText = location;
document.getElementById('photo-lightbox').classList.add('active');
}
function closeLightbox() {
document.getElementById('photo-lightbox').classList.remove('active');
}
// Keyboard Shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeLightbox();
if (e.key === 'm' || e.key === 'M' || e.key === 'ь' || e.key === 'Ь') {
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') toggleMatrixRain(true);
}
if (e.key === 't' || e.key === 'T' || e.key === 'е' || e.key === 'Е') {
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') toggleTheme();
}
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
const tabs = ['notes', 'gallery', 'howto', 'calculator', 'books', 'journal', 'workout', 'tennis'];
const num = parseInt(e.key);
if (num >= 1 && num <= 8) {
switchTab(tabs[num - 1]);
} else if (e.key === '0' || e.key === '`' || e.key === 'ё' || e.key === 'Ё') {
openAdminTab();
}
}
});
// Gear Calculator Engine for 29er MTB
function calculateGears() {
const chainring = parseFloat(document.getElementById('chainring').value) || 34;
const cog = parseFloat(document.getElementById('cog').value) || 11;
const cadence = parseFloat(document.getElementById('cadence').value) || 85;
const tireCirc = parseFloat(document.getElementById('tire-size').value) || 2280; // in mm
const ratio = chainring / cog;
const developmentMeters = (ratio * tireCirc) / 1000;
const wheelDiamInches = tireCirc / (Math.PI * 25.4);
const gearInches = ratio * wheelDiamInches;
const speedKmh = (developmentMeters * cadence * 60) / 1000;
document.getElementById('res-ratio').innerText = ratio.toFixed(2);
document.getElementById('res-inches').innerText = gearInches.toFixed(1) + '"';
document.getElementById('res-dev').innerText = developmentMeters.toFixed(2) + ' m';
document.getElementById('res-speed').innerText = speedKmh.toFixed(1) + ' km/h';
}
calculateGears();
// Easter Egg
function triggerEasterEgg() {
alert('«Самурай почистит и смажет цепь, даже если сегодня никуда не выезжал.»\nУльяновск • Волга • Aspect Air 2025 29" MTB • ЖПЧХ. 🚴‍♂️🥋');
toggleMatrixRain(true);
}
// Initialize Matrix Dynamic Engine
document.addEventListener('DOMContentLoaded', () => {
if (window.MatrixAdmin) {
MatrixAdmin.init();
}
});
</script>
</body>
</html>