From e41bd916f7cac129e7a8cca09c3c8450746bc889 Mon Sep 17 00:00:00 2001
From: jae
Date: Tue, 31 Mar 2026 21:10:40 +0000
Subject: [PATCH] feat: add individual post page (post.html, post.js, post.css)
---
css/post.css | 490 +++++++++++++++++++++++++++++++++++++++++++++++++++
js/post.js | 325 ++++++++++++++++++++++++++++++++++
post.html | 118 +++++++++++++
3 files changed, 933 insertions(+)
create mode 100644 css/post.css
create mode 100644 js/post.js
create mode 100644 post.html
diff --git a/css/post.css b/css/post.css
new file mode 100644
index 0000000..f4beb73
--- /dev/null
+++ b/css/post.css
@@ -0,0 +1,490 @@
+/* ===================================================
+ JAESWIFT — Individual Post Page Styles
+ =================================================== */
+
+/* ─── Post Header ─── */
+.post-header {
+ padding: 10rem 2rem 3rem;
+ position: relative;
+ border-bottom: 1px solid rgba(0, 255, 200, 0.1);
+}
+
+.post-header-content {
+ max-width: 1100px;
+ margin: 0 auto;
+}
+
+.post-back {
+ display: inline-block;
+ color: var(--accent, #00ffc8);
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.75rem;
+ letter-spacing: 1px;
+ text-decoration: none;
+ margin-bottom: 1.5rem;
+ opacity: 0.6;
+ transition: opacity 0.3s;
+}
+.post-back:hover {
+ opacity: 1;
+}
+
+.post-header-meta {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+ flex-wrap: wrap;
+}
+
+.post-header .post-date {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.8rem;
+ color: rgba(0, 255, 200, 0.6);
+ letter-spacing: 1px;
+}
+
+.post-header .post-time {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.75rem;
+ color: rgba(200, 214, 229, 0.4);
+}
+
+.post-header-title {
+ font-family: 'Orbitron', sans-serif;
+ font-size: clamp(1.4rem, 3vw, 2.2rem);
+ font-weight: 700;
+ color: #fff;
+ margin-bottom: 1rem;
+ line-height: 1.3;
+}
+
+.post-header-tags {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.post-header-tags .post-tag {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.65rem;
+ padding: 0.25rem 0.6rem;
+ border: 1px solid rgba(0, 255, 200, 0.2);
+ color: var(--accent, #00ffc8);
+ letter-spacing: 1px;
+ text-transform: uppercase;
+}
+
+/* ─── Threat Badges ─── */
+.post-threat {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.7rem;
+ padding: 0.2rem 0.6rem;
+ letter-spacing: 1px;
+ font-weight: 600;
+}
+.threat-low {
+ color: #00ffc8;
+ border: 1px solid rgba(0, 255, 200, 0.3);
+ text-shadow: 0 0 8px rgba(0, 255, 200, 0.3);
+}
+.threat-medium, .threat-med {
+ color: #ffa502;
+ border: 1px solid rgba(255, 165, 2, 0.3);
+ text-shadow: 0 0 8px rgba(255, 165, 2, 0.3);
+}
+.threat-high {
+ color: #ff4757;
+ border: 1px solid rgba(255, 71, 87, 0.3);
+ text-shadow: 0 0 8px rgba(255, 71, 87, 0.3);
+}
+.threat-critical {
+ color: #ff0040;
+ border: 1px solid rgba(255, 0, 64, 0.4);
+ text-shadow: 0 0 12px rgba(255, 0, 64, 0.4);
+ animation: threatPulse 1.5s ease-in-out infinite;
+}
+@keyframes threatPulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.6; }
+}
+
+/* ─── Post Layout ─── */
+.post-body-section {
+ padding: 3rem 2rem 5rem;
+}
+
+.post-layout {
+ max-width: 1100px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: 1fr 300px;
+ gap: 2.5rem;
+ align-items: start;
+}
+
+/* ─── Post Content ─── */
+.post-content-main {
+ min-width: 0;
+}
+
+.post-rendered {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.9rem;
+ line-height: 1.8;
+ color: rgba(200, 214, 229, 0.85);
+}
+
+.post-rendered h1,
+.post-rendered h2,
+.post-rendered h3,
+.post-rendered h4 {
+ font-family: 'Orbitron', sans-serif;
+ color: #fff;
+ margin: 2rem 0 1rem;
+ line-height: 1.3;
+}
+.post-rendered h1 { font-size: 1.6rem; }
+.post-rendered h2 {
+ font-size: 1.2rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid rgba(0, 255, 200, 0.1);
+}
+.post-rendered h3 { font-size: 1rem; color: var(--accent, #00ffc8); }
+.post-rendered h4 { font-size: 0.9rem; }
+
+.post-rendered p {
+ margin-bottom: 1.2rem;
+}
+
+.post-rendered strong {
+ color: #fff;
+ font-weight: 600;
+}
+
+.post-rendered em {
+ color: rgba(0, 255, 200, 0.7);
+ font-style: italic;
+}
+
+.post-rendered a {
+ color: var(--accent, #00ffc8);
+ text-decoration: none;
+ border-bottom: 1px solid rgba(0, 255, 200, 0.3);
+ transition: border-color 0.3s;
+}
+.post-rendered a:hover {
+ border-color: var(--accent, #00ffc8);
+}
+
+.post-rendered ul {
+ list-style: none;
+ padding-left: 0;
+ margin: 1rem 0;
+}
+.post-rendered ul li {
+ position: relative;
+ padding-left: 1.5rem;
+ margin-bottom: 0.5rem;
+}
+.post-rendered ul li::before {
+ content: '▸';
+ position: absolute;
+ left: 0;
+ color: var(--accent, #00ffc8);
+ font-size: 0.8rem;
+}
+
+.post-rendered hr {
+ border: none;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(0, 255, 200, 0.2), transparent);
+ margin: 2rem 0;
+}
+
+/* ─── Code Blocks ─── */
+.post-rendered pre {
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid rgba(0, 255, 200, 0.1);
+ border-left: 3px solid var(--accent, #00ffc8);
+ border-radius: 0;
+ padding: 1.2rem 1.5rem;
+ margin: 1.5rem 0;
+ overflow-x: auto;
+ position: relative;
+}
+
+.post-rendered pre::before {
+ content: attr(data-lang) 'TERMINAL';
+ position: absolute;
+ top: 0;
+ right: 0;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.6rem;
+ padding: 0.2rem 0.6rem;
+ background: rgba(0, 255, 200, 0.08);
+ color: rgba(0, 255, 200, 0.4);
+ letter-spacing: 1px;
+}
+
+.post-rendered pre code {
+ font-family: 'Share Tech Mono', 'JetBrains Mono', monospace;
+ font-size: 0.82rem;
+ color: var(--accent, #00ffc8);
+ line-height: 1.6;
+ background: none;
+ padding: 0;
+}
+
+.post-rendered code.inline-code {
+ font-family: 'Share Tech Mono', monospace;
+ font-size: 0.82rem;
+ background: rgba(0, 255, 200, 0.06);
+ border: 1px solid rgba(0, 255, 200, 0.1);
+ padding: 0.15rem 0.4rem;
+ color: var(--accent, #00ffc8);
+}
+
+/* ─── Sidebar ─── */
+.post-sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ position: sticky;
+ top: 6rem;
+}
+
+.post-stats-panel,
+.post-meta-panel,
+.post-nav-panel {
+ background: rgba(0, 255, 200, 0.02);
+ border: 1px solid rgba(0, 255, 200, 0.08);
+}
+
+/* ─── Stat Rows (sidebar) ─── */
+.stat-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.4rem 0;
+}
+
+.stat-label {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.65rem;
+ color: rgba(200, 214, 229, 0.5);
+ letter-spacing: 1px;
+ min-width: 80px;
+}
+
+.stat-pips {
+ display: flex;
+ gap: 4px;
+}
+
+.pip {
+ width: 14px;
+ height: 8px;
+ background: rgba(200, 214, 229, 0.08);
+ border: 1px solid rgba(200, 214, 229, 0.1);
+ transition: all 0.3s;
+}
+.pip.filled {
+ background: var(--accent, #00ffc8);
+ border-color: var(--accent, #00ffc8);
+ box-shadow: 0 0 6px rgba(0, 255, 200, 0.3);
+}
+.pip.filled.warn {
+ background: #ffa502;
+ border-color: #ffa502;
+ box-shadow: 0 0 6px rgba(255, 165, 2, 0.3);
+}
+.pip.filled.danger {
+ background: #ff4757;
+ border-color: #ff4757;
+ box-shadow: 0 0 6px rgba(255, 71, 87, 0.3);
+}
+
+.stat-divider {
+ height: 1px;
+ background: rgba(0, 255, 200, 0.08);
+ margin: 0.5rem 0;
+}
+
+/* ─── BPM Display ─── */
+.stat-big-row {
+ text-align: center;
+ padding: 0.5rem 0;
+}
+
+.stat-bpm-display {
+ position: relative;
+}
+
+.stat-bpm-value {
+ font-family: 'Orbitron', sans-serif;
+ font-size: 2rem;
+ font-weight: 700;
+ color: #ff4757;
+ text-shadow: 0 0 15px rgba(255, 71, 87, 0.4);
+ transition: transform 0.1s;
+}
+.stat-bpm-value.pulse {
+ transform: scale(1.15);
+}
+
+.stat-bpm-unit {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.65rem;
+ color: rgba(255, 71, 87, 0.5);
+ letter-spacing: 2px;
+ display: block;
+ margin-top: -0.2rem;
+}
+
+/* ─── Coffee Row ─── */
+.stat-coffee-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.4rem 0;
+}
+
+.stat-coffee {
+ font-size: 0.9rem;
+}
+
+/* ─── Metadata Panel ─── */
+.meta-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.4rem 0;
+ border-bottom: 1px solid rgba(0, 255, 200, 0.04);
+}
+.meta-row:last-child {
+ border-bottom: none;
+}
+
+.meta-key {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.6rem;
+ color: rgba(200, 214, 229, 0.4);
+ letter-spacing: 1px;
+}
+
+.meta-val {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.75rem;
+ color: rgba(200, 214, 229, 0.8);
+ text-align: right;
+}
+.meta-val.mono {
+ font-size: 0.65rem;
+ color: rgba(0, 255, 200, 0.5);
+}
+
+/* ─── Navigation Panel ─── */
+.post-nav-link {
+ display: block;
+ padding: 0.8rem 0;
+ border-bottom: 1px solid rgba(0, 255, 200, 0.06);
+ text-decoration: none;
+ transition: all 0.3s;
+}
+.post-nav-link:last-child {
+ border-bottom: none;
+}
+.post-nav-link:hover {
+ background: rgba(0, 255, 200, 0.03);
+ padding-left: 0.5rem;
+}
+
+.nav-dir {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.6rem;
+ color: var(--accent, #00ffc8);
+ letter-spacing: 1px;
+ display: block;
+ margin-bottom: 0.2rem;
+}
+
+.nav-post-title {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.75rem;
+ color: rgba(200, 214, 229, 0.7);
+ display: block;
+ line-height: 1.4;
+}
+
+.nav-all .nav-dir {
+ text-align: center;
+ margin-top: 0.3rem;
+}
+
+/* ─── Loading & Error ─── */
+.post-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+ padding: 4rem 0;
+ color: rgba(0, 255, 200, 0.5);
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.8rem;
+ letter-spacing: 2px;
+}
+
+.post-error {
+ text-align: center;
+ padding: 4rem 2rem;
+ font-family: 'Orbitron', sans-serif;
+ font-size: 1.1rem;
+ color: #ff4757;
+ text-shadow: 0 0 20px rgba(255, 71, 87, 0.3);
+}
+
+/* ─── Responsive ─── */
+@media (max-width: 900px) {
+ .post-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .post-sidebar {
+ position: static;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+ }
+
+ .post-nav-panel {
+ grid-column: 1 / -1;
+ }
+}
+
+@media (max-width: 600px) {
+ .post-header {
+ padding: 8rem 1rem 2rem;
+ }
+
+ .post-body-section {
+ padding: 2rem 1rem 3rem;
+ }
+
+ .post-header-title {
+ font-size: 1.2rem;
+ }
+
+ .post-sidebar {
+ grid-template-columns: 1fr;
+ }
+
+ .post-rendered {
+ font-size: 0.82rem;
+ }
+
+ .post-rendered pre {
+ padding: 0.8rem 1rem;
+ font-size: 0.75rem;
+ }
+}
diff --git a/js/post.js b/js/post.js
new file mode 100644
index 0000000..046cc39
--- /dev/null
+++ b/js/post.js
@@ -0,0 +1,325 @@
+/* ===================================================
+ JAESWIFT BLOG — Individual Post Loader
+ Markdown rendering, operator stats, navigation
+ =================================================== */
+(function () {
+ 'use strict';
+
+ const API = window.location.hostname === 'localhost'
+ ? 'http://localhost:5000'
+ : '/api';
+
+ // ─── Simple Markdown Parser ───
+ function parseMarkdown(md) {
+ let html = md;
+
+ // Code blocks (```lang ... ```)
+ html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
+ const cls = lang ? ` class="language-${lang}"` : '';
+ return `${escapeHtml(code.trim())}
`;
+ });
+
+ // Inline code
+ html = html.replace(/`([^`]+)`/g, '$1');
+
+ // Headers
+ html = html.replace(/^#### (.+)$/gm, '$1
');
+ html = html.replace(/^### (.+)$/gm, '$1
');
+ html = html.replace(/^## (.+)$/gm, '$1
');
+ html = html.replace(/^# (.+)$/gm, '$1
');
+
+ // Bold & Italic
+ html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1');
+ html = html.replace(/\*\*(.+?)\*\*/g, '$1');
+ html = html.replace(/\*(.+?)\*/g, '$1');
+
+ // Links
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1');
+
+ // Unordered lists
+ html = html.replace(/^- (.+)$/gm, '$1');
+ html = html.replace(/(.*<\/li>\n?)+/g, (match) => ``);
+
+ // Horizontal rule
+ html = html.replace(/^---$/gm, '
');
+
+ // Paragraphs (double newline)
+ html = html.replace(/\n\n(?!<)/g, '
');
+ html = '
' + html + '
';
+
+ // Clean up empty paragraphs and fix nesting
+ html = html.replace(/<(h[1-4]|pre|ul|hr|blockquote)/g, '<$1');
+ html = html.replace(/<\/(h[1-4]|pre|ul|hr|blockquote)><\/p>/g, '$1>');
+ html = html.replace(/
<\/p>/g, '');
+ html = html.replace(/
\s*<\/p>/g, '');
+
+ return html;
+ }
+
+ function escapeHtml(str) {
+ return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
+ }
+
+ // ─── Stat Pips ───
+ function buildPips(val, max) {
+ max = max || 5;
+ let html = '
';
+ for (let i = 0; i < max; i++) {
+ let cls = 'pip';
+ if (i < val) {
+ cls += ' filled';
+ if (val <= 2) cls += ' danger';
+ else if (val <= 3) cls += ' warn';
+ }
+ html += '
';
+ }
+ html += '
';
+ return html;
+ }
+
+ // ─── Clock ───
+ function initClock() {
+ const el = document.getElementById('navClock');
+ if (!el) return;
+ const tick = () => {
+ const d = new Date();
+ el.textContent = d.toLocaleTimeString('en-GB', { hour12: false }) + ' UTC' + (d.getTimezoneOffset() <= 0 ? '+' : '') + (-d.getTimezoneOffset() / 60);
+ };
+ tick();
+ setInterval(tick, 1000);
+ }
+
+ // ─── Navbar ───
+ function initNavbar() {
+ const toggle = document.getElementById('navToggle');
+ const menu = document.getElementById('navMenu');
+ if (toggle && menu) {
+ toggle.addEventListener('click', () => menu.classList.toggle('active'));
+ }
+ window.addEventListener('scroll', () => {
+ const nb = document.getElementById('navbar');
+ if (nb) nb.classList.toggle('scrolled', window.scrollY > 50);
+ }, { passive: true });
+ }
+
+ // ─── Render Operator Stats ───
+ function renderStats(post) {
+ const el = document.getElementById('postStats');
+ if (!el) return;
+
+ const coffee = '☕'.repeat(post.coffee || 0) +
+ '' + '☕'.repeat(5 - (post.coffee || 0)) + '';
+
+ el.innerHTML = `
+
+ MOOD
+ ${buildPips(post.mood)}
+
+
+ ENERGY
+ ${buildPips(post.energy)}
+
+
+ MOTIVATION
+ ${buildPips(post.motivation)}
+
+
+ FOCUS
+ ${buildPips(post.focus)}
+
+
+ DIFFICULTY
+ ${buildPips(post.difficulty)}
+
+
+
+
+
${post.heart_rate || '---'}
+
BPM
+
+
+
+
+ CAFFEINE
+ ${coffee}
+
+ `;
+
+ // Animate heartbeat
+ animateHeartbeat(post.heart_rate || 72);
+ }
+
+ // ─── Heartbeat Animation ───
+ function animateHeartbeat(bpm) {
+ const el = document.getElementById('bpmValue');
+ if (!el) return;
+ const interval = 60000 / bpm;
+
+ setInterval(() => {
+ el.classList.add('pulse');
+ setTimeout(() => el.classList.remove('pulse'), 200);
+ // Flicker the value slightly
+ const jitter = Math.floor(Math.random() * 5) - 2;
+ el.textContent = bpm + jitter;
+ }, interval);
+ }
+
+ // ─── Render Metadata Panel ───
+ function renderMetaPanel(post) {
+ const el = document.getElementById('postMetaPanel');
+ if (!el) return;
+
+ el.innerHTML = `
+
+ DATE
+ ${post.date}
+
+
+ TIME
+ ${post.time_written || 'UNKNOWN'}
+
+
+ WORDS
+ ${post.word_count || '---'}
+
+
+ THREAT
+ ${post.threat_level || 'LOW'}
+
+
+ READ TIME
+ ~${Math.max(1, Math.ceil((post.word_count || 300) / 250))} MIN
+
+
+ SLUG
+ ${post.slug}
+
+ `;
+ }
+
+ // ─── Render Navigation ───
+ function renderNav(allPosts, currentSlug) {
+ const el = document.getElementById('postNav');
+ if (!el) return;
+
+ // Sort by date descending
+ const sorted = [...allPosts].sort((a, b) => new Date(b.date) - new Date(a.date));
+ const idx = sorted.findIndex(p => p.slug === currentSlug);
+
+ let navHtml = '';
+
+ if (idx > 0) {
+ const prev = sorted[idx - 1];
+ navHtml += `
+ ← NEWER
+ ${prev.title}
+ `;
+ }
+
+ if (idx < sorted.length - 1) {
+ const next = sorted[idx + 1];
+ navHtml += `
+ OLDER →
+ ${next.title}
+ `;
+ }
+
+ navHtml += `
+ ⟐ ALL TRANSMISSIONS
+ `;
+
+ el.innerHTML = navHtml;
+ }
+
+ // ─── Load Post ───
+ async function loadPost() {
+ const params = new URLSearchParams(window.location.search);
+ const slug = params.get('slug');
+
+ if (!slug) {
+ document.getElementById('postContent').innerHTML =
+ 'NO TRANSMISSION ID SPECIFIED
';
+ return;
+ }
+
+ try {
+ // Fetch the specific post
+ const res = await fetch(API + '/posts/' + encodeURIComponent(slug));
+ if (!res.ok) throw new Error('Post not found');
+ const post = await res.json();
+
+ // Update page title
+ document.title = 'JAESWIFT // ' + post.title.toUpperCase();
+
+ // Update header
+ document.getElementById('postTitle').textContent = post.title;
+ document.getElementById('postDate').textContent = post.date;
+ document.getElementById('postTime').textContent = post.time_written || '';
+
+ const threatEl = document.getElementById('postThreat');
+ threatEl.textContent = post.threat_level || 'LOW';
+ threatEl.className = 'post-threat threat-' + (post.threat_level || 'LOW').toLowerCase();
+
+ // Tags
+ const tagsEl = document.getElementById('postTags');
+ tagsEl.innerHTML = (post.tags || []).map(t =>
+ '' + t + ''
+ ).join('');
+
+ // Render content
+ const contentEl = document.getElementById('postContent');
+ contentEl.innerHTML = '' + parseMarkdown(post.content || '') + '
';
+
+ // Animate content in
+ contentEl.style.opacity = '0';
+ contentEl.style.transform = 'translateY(20px)';
+ contentEl.style.transition = 'all 0.6s ease';
+ requestAnimationFrame(() => {
+ contentEl.style.opacity = '1';
+ contentEl.style.transform = 'translateY(0)';
+ });
+
+ // Sidebar panels
+ renderStats(post);
+ renderMetaPanel(post);
+
+ // Load all posts for navigation
+ try {
+ const allRes = await fetch(API + '/posts');
+ const allPosts = await allRes.json();
+ renderNav(allPosts, slug);
+ } catch (e) {
+ console.warn('Could not load post navigation');
+ }
+
+ } catch (err) {
+ // Fallback: try static JSON
+ try {
+ const res2 = await fetch('api/data/posts.json');
+ const posts = await res2.json();
+ const post = posts.find(p => p.slug === slug);
+ if (!post) throw new Error('Not found');
+
+ document.title = 'JAESWIFT // ' + post.title.toUpperCase();
+ document.getElementById('postTitle').textContent = post.title;
+ document.getElementById('postDate').textContent = post.date;
+ document.getElementById('postTime').textContent = post.time_written || '';
+ document.getElementById('postContent').innerHTML =
+ '' + parseMarkdown(post.content || '') + '
';
+ renderStats(post);
+ renderMetaPanel(post);
+ renderNav(posts, slug);
+ } catch (e2) {
+ document.getElementById('postContent').innerHTML =
+ 'TRANSMISSION NOT FOUND — SIGNAL LOST
';
+ }
+ }
+ }
+
+ // ─── Init ───
+ document.addEventListener('DOMContentLoaded', () => {
+ initClock();
+ initNavbar();
+ loadPost();
+ });
+})();
diff --git a/post.html b/post.html
new file mode 100644
index 0000000..f618700
--- /dev/null
+++ b/post.html
@@ -0,0 +1,118 @@
+
+
+
+
+
+ JAESWIFT // POST
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
DECRYPTING TRANSMISSION...
+
+
+
+
+
+
+
+
+
+
+
+
+
+