matrixhasyou-site/js/posts-store.js

299 lines
13 KiB
JavaScript
Executable file
Raw Permalink 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.

/**
* Matrix Cycling Blog - Data Store & Post Scheduler
* Tailored for MatrixHasYou // Ulyanovsk (Volga), Aspect Air 2025 29" MTB, Books, Gym, Tennis, Journal.
*/
window.MatrixStore = (function () {
const STORAGE_KEY_POSTS = "matrix_cycling_posts_v6";
const STORAGE_KEY_PHOTOS = "matrix_cycling_photos_v6";
const DEFAULT_NOTES = [
{
"id": "note-aspect-specs-001",
"type": "note",
"category": "howto",
"title": "🚴‍♂️ ТТХ моего велосипеда: Aspect Air 29\" (2025) — Полный мануал, стандарты и расходники",
"date": "2026-08-24T00:30:00.000Z",
"publishAt": "2026-08-24T00:30:00.000Z",
"status": "published",
"heroImage": "images/aspect_matrix_bridge.jpg",
"tags": [
"aspect_air_2025",
"29er",
"mtb_specs",
"shimano_cues",
"ulyanovsk",
"matrix"
],
"telemetry": {
"distance": "1x9 Shimano CUES",
"avgSpeed": "Sunshine 11-46T 9S",
"elevation": "Prowheel 34T (24mm)",
"watts": "EXSHO Air 100mm (34mm)"
},
"content": `Полная техническая спецификация, стандарты узлов, размеры расходников и геометрия найнера **Aspect Air 29" (2025)**. Этот мануал служит эталонным справочником по всем компонентам для обслуживания, ТО и быстрого подбора запчастей при ремонте.
---
### 1. ⚙️ Трансмиссия и Привод (Drivetrain)
* **Конфигурация:** \`1x9\` скоростей
* **Задний переключатель:** \`Shimano CUES RD-U4000\` (с технологией Shadow RD, прямая подводка троса)
* **Манетка переключения:** \`Shimano CUES SL-U4000\` (технология 2-Way Release)
* **Система шатунов:** \`PROWHEEL C10Y-NW 34T\` с полым валом **Ø24 мм** (Hollowtech II совместимый стандарт)
* Длина шатунов: **175 мм** (для рам M / L / XL) / **170 мм** (для рамы S)
* Звезда: **34 зуба (34T)**, профиль **Narrow-Wide** (защита от спадания цепи)
* **Каретка:** Резьбовая \`BSA 68/73 мм\` (B902 / внешние промышленные подшипники под вал 24 мм)
* **Кассета:** \`Sunshine MTB-CS-HR9-46 9S\` (диапазон **11-46T**: звезды 11-13-16-20-24-28-34-40-46)
* **Барабан втулки:** Стандарт \`Shimano HG (HyperGlide)\`
* **Цепь:** \`KMC XGLIDE\` (9 скоростей, замок быстросъемный)
---
### 2. 🛞 Колеса, Обода и Покрышки (Wheelset)
* **Размер колес:** \`29 дюймов (622 мм)\`
* **Обода:** \`Code D23race\` — двойные алюминиевые, **Tubeless Ready** (готовы под бескамерную установку), ETRTO \`622 x 23 мм\`, 32 спицы (32H), ниппель **Presta (FV)**
* **Передняя втулка:** \`Code H1.2\` (алюминиевый корпус на 2 промышленных подшипниках, стандарт **Boost 15x110 мм** на сквозной оси)
* **Задняя втулка:** \`Code H1.2\` (алюминиевый корпус на 4 промышленных подшипниках, стандарт **Boost 12x148 мм** на сквозной оси, барабан HG)
* **Покрышки:** \`Kenda Booster K1227 Skinwall 29x2.25"\` (универсальный накатистый XC-протектор со светлым бортом)
* **Рабочее давление шин:**
* Грунт / Корни / Песок: **1.81.9 бар (2628 PSI)**
* Асфальт / Твердое покрытие: **2.02.2 бар (2932 PSI)**
---
### 3. 🔱 Амортизационная вилка (Suspension Fork)
* **Модель:** \`EXSHO FIX 15 AIR / HLO / REB\`
* **Тип:** Воздушно-масляная (Air Spring)
* **Ход вилки:** **100 мм**
* **Ось:** Сквозная **Boost 15x110 мм**
* **Ноги:** Анодированный алюминиевый сплав, диаметр **Ø34 мм** (повышенная торсионная жесткость)
* **Бушинги:** Металло-тефлоновые скользящие направляющие
* **Регулировки:**
1. *Воздушная камера (Air Preload)* — накачка насосом высокого давления под вес райдера
2. *Блокировка (HLO - Hydraulic Lockout)* — флажок на короне вилки
3. *Регулировка отскока (Rebound)* — крутилка в нижней части правой ноги
---
### 4. 🛑 Тормозная система (Braking System)
* **Тормоза:** \`Shimano MT200\` — двухпоршневая гидравлика на минеральном масле Shimano Mineral Oil
* **Крепление заднего калипера:** \`Post Mount (PM)\` внутри заднего треугольника на нижнем пере (защита от ударов)
* **Передний ротор:** \`RPT-009 Ø180 мм\` (6 болтов)
* **Задний ротор:** \`RPT-009 Ø160 мм\` (6 болтов)
* **Тормозные колодки:** \`B01S / B03S / B05S Resin (органические)\`
---
### 5. 📐 Рама, Рулевое управление и Кокпит (Frame & Cockpit)
* **Материал рамы:** \`Alloy 6061\` (гидроформированные трубы переменного сечения, двойной баттинг, полированные швы *Smooth Welding*)
* **Стандарты рамы:** Дропауты под сквозную ось **Boost 12x148 мм**, скрытая внутренняя проводка тросов и гидролиний (*IHR / Silent Cables*)
* **Рулевая колонка:** \`Gineyea T05 52-52/39.8\` (полуинтегрированная на закрытых промподшипниках под конусный шток 1.5"-1.5")
* **Руль:** \`Code-802 Alloy\` — диаметр под вынос **Ø31.8 мм**, ширина **760 мм**, подъем 15 мм, загиб назад 6°
* **Вынос руля:** \`Code AS-002N Alloy\` (угол 0°, длина 60 мм под рамы M/L, 50 мм под S, 70 мм под XL)
* **Грипсы:** \`Code G104\` (эргономичный цепкий компаунд с алюминиевым замком Lock-on)
* **Подседельный штырь:** \`Code-609 Alloy\`, диаметр **Ø31.6 мм**, длина **400 мм**, надежный двухболтовый замок
* **Седло:** \`Code-12231\` (размеры **245 x 155 мм**, анатомический вырез и плотная спортивная пена)
* **Вес велосипеда:** ~**13.4 кг** (в сборе)
> [!TIP] Заметка для ТО и быстрого заказа расходников:
> * **Кассета:** 9 скоростей, барабан HG, диапазон 11-46T.
> * **Цепь:** 9-speed KMC / Shimano (116 звеньев).
> * **Колодки:** Shimano B05S Resin.
> * **Ниппель:** Presta (FV 48 мм).
> * **Оси:** Перед 15x110 Boost, Зад 12x148 Boost.`
}
];
const DEFAULT_PHOTOS = [];
// --- Local Storage Management ---
function getStoredNotes() {
try {
const raw = localStorage.getItem(STORAGE_KEY_POSTS);
if (raw === null) {
localStorage.setItem(STORAGE_KEY_POSTS, JSON.stringify(DEFAULT_NOTES));
return DEFAULT_NOTES;
}
return JSON.parse(raw);
} catch (e) {
console.error("Error reading notes from localStorage", e);
return DEFAULT_NOTES;
}
}
function saveStoredNotes(notes) {
try {
localStorage.setItem(STORAGE_KEY_POSTS, JSON.stringify(notes));
} catch (e) {
console.error("Error saving notes to localStorage", e);
}
}
function getStoredPhotos() {
try {
const raw = localStorage.getItem(STORAGE_KEY_PHOTOS);
if (raw === null) {
localStorage.setItem(STORAGE_KEY_PHOTOS, JSON.stringify(DEFAULT_PHOTOS));
return DEFAULT_PHOTOS;
}
return JSON.parse(raw);
} catch (e) {
console.error("Error reading photos from localStorage", e);
return DEFAULT_PHOTOS;
}
}
function saveStoredPhotos(photos) {
try {
localStorage.setItem(STORAGE_KEY_PHOTOS, JSON.stringify(photos));
} catch (e) {
console.error("Error saving photos to localStorage", e);
}
}
// --- Public Queries ---
function isItemLive(item) {
if (!item || item.status === "draft") return false;
const pubDate = new Date(item.publishAt || item.date);
return pubDate <= new Date();
}
function getPublishedNotes(category = null) {
const now = new Date();
let notes = getStoredNotes().filter(note => {
if (note.status === "draft") return false;
const pubDate = new Date(note.publishAt || note.date);
return pubDate <= now;
});
if (category && category !== "all") {
notes = notes.filter(n => n.category === category);
}
return notes.sort((a, b) => new Date(b.publishAt || b.date) - new Date(a.publishAt || a.date));
}
function getAllNotes() {
return getStoredNotes().sort((a, b) => new Date(b.date || b.publishAt) - new Date(a.date || a.publishAt));
}
function getPublishedPhotos() {
const now = new Date();
return getStoredPhotos().filter(photo => {
if (photo.status === "draft") return false;
const pubDate = new Date(photo.publishAt || photo.date);
return pubDate <= now;
}).sort((a, b) => new Date(b.publishAt || b.date) - new Date(a.publishAt || a.date));
}
function getAllPhotos() {
return getStoredPhotos().sort((a, b) => new Date(b.date || b.publishAt) - new Date(a.date || a.publishAt));
}
// --- Mutations ---
function saveNote(noteData) {
const notes = getStoredNotes();
const nowIso = new Date().toISOString();
if (noteData.id) {
const index = notes.findIndex(n => n.id === noteData.id);
if (index !== -1) {
notes[index] = { ...notes[index], ...noteData, updatedAt: nowIso };
} else {
notes.unshift({ ...noteData, createdAt: nowIso, updatedAt: nowIso });
}
} else {
const newId = "note-" + Date.now();
notes.unshift({
id: newId,
...noteData,
createdAt: nowIso,
updatedAt: nowIso
});
}
saveStoredNotes(notes);
return true;
}
function deleteNote(id) {
let notes = getStoredNotes();
notes = notes.filter(n => n.id !== id);
saveStoredNotes(notes);
return true;
}
function savePhoto(photoData) {
const photos = getStoredPhotos();
const nowIso = new Date().toISOString();
if (photoData.id) {
const index = photos.findIndex(p => p.id === photoData.id);
if (index !== -1) {
photos[index] = { ...photos[index], ...photoData, updatedAt: nowIso };
} else {
photos.unshift({ ...photoData, createdAt: nowIso, updatedAt: nowIso });
}
} else {
const newId = "photo-" + Date.now();
photos.unshift({
id: newId,
...photoData,
createdAt: nowIso,
updatedAt: nowIso
});
}
saveStoredPhotos(photos);
return true;
}
function deletePhoto(id) {
let photos = getStoredPhotos();
photos = photos.filter(p => p.id !== id);
saveStoredPhotos(photos);
return true;
}
function exportDatabase() {
return JSON.stringify({
version: "6.0",
exportedAt: new Date().toISOString(),
notes: getStoredNotes(),
photos: getStoredPhotos()
}, null, 2);
}
function importDatabase(jsonString) {
try {
const data = JSON.parse(jsonString);
if (Array.isArray(data.notes)) saveStoredNotes(data.notes);
if (Array.isArray(data.photos)) saveStoredPhotos(data.photos);
return true;
} catch (e) {
console.error("Import failed", e);
return false;
}
}
return {
isItemLive,
getLiveNotes: getPublishedNotes,
getPublishedNotes,
getAllNotes,
getAllNotesForAdmin: getAllNotes,
getLivePhotos: getPublishedPhotos,
getPublishedPhotos,
getAllPhotos,
getAllPhotosForAdmin: getAllPhotos,
saveNote,
deleteNote,
savePhoto,
deletePhoto,
exportDatabase,
importDatabase
};
})();