| | |
| | """ |
| | Convert Markdown slides to HTML presentation |
| | """ |
| |
|
| | import re |
| | from pathlib import Path |
| |
|
| | def convert_md_to_html(md_file, output_file): |
| | """Convert Markdown to HTML presentation""" |
| |
|
| | |
| | with open(md_file, 'r', encoding='utf-8') as f: |
| | content = f.read() |
| |
|
| | |
| | slides = content.split('\n---\n') |
| |
|
| | |
| | html = """<!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>SPARKNET Presentation</title> |
| | <style> |
| | * { |
| | margin: 0; |
| | padding: 0; |
| | box-sizing: border-box; |
| | } |
| | |
| | body { |
| | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | overflow: hidden; |
| | } |
| | |
| | .slide-container { |
| | width: 100vw; |
| | height: 100vh; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | position: relative; |
| | } |
| | |
| | .slide { |
| | background: white; |
| | width: 90%; |
| | max-width: 1200px; |
| | height: 85vh; |
| | padding: 60px; |
| | border-radius: 20px; |
| | box-shadow: 0 20px 60px rgba(0,0,0,0.3); |
| | overflow-y: auto; |
| | display: none; |
| | } |
| | |
| | .slide.active { |
| | display: block; |
| | animation: fadeIn 0.5s ease-in; |
| | } |
| | |
| | @keyframes fadeIn { |
| | from { opacity: 0; transform: translateY(20px); } |
| | to { opacity: 1; transform: translateY(0); } |
| | } |
| | |
| | .slide.lead { |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | justify-content: center; |
| | text-align: center; |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | color: white; |
| | } |
| | |
| | h1 { |
| | font-size: 3em; |
| | margin-bottom: 0.5em; |
| | color: #2c3e50; |
| | } |
| | |
| | .slide.lead h1 { |
| | color: white; |
| | font-size: 4em; |
| | } |
| | |
| | h2 { |
| | font-size: 2em; |
| | margin: 0.5em 0; |
| | color: #34495e; |
| | border-bottom: 3px solid #667eea; |
| | padding-bottom: 10px; |
| | } |
| | |
| | .slide.lead h2 { |
| | color: rgba(255,255,255,0.9); |
| | border: none; |
| | font-size: 2em; |
| | } |
| | |
| | h3 { |
| | font-size: 1.5em; |
| | margin: 1em 0 0.5em 0; |
| | color: #667eea; |
| | } |
| | |
| | h4 { |
| | font-size: 1.2em; |
| | margin: 0.8em 0 0.4em 0; |
| | color: #764ba2; |
| | } |
| | |
| | p { |
| | line-height: 1.8; |
| | margin: 0.8em 0; |
| | font-size: 1.1em; |
| | } |
| | |
| | ul, ol { |
| | margin: 1em 0 1em 2em; |
| | line-height: 2; |
| | } |
| | |
| | li { |
| | margin: 0.5em 0; |
| | font-size: 1.05em; |
| | } |
| | |
| | pre { |
| | background: #f8f9fa; |
| | padding: 20px; |
| | border-radius: 8px; |
| | overflow-x: auto; |
| | margin: 1em 0; |
| | border-left: 4px solid #667eea; |
| | } |
| | |
| | code { |
| | font-family: 'Courier New', monospace; |
| | font-size: 0.9em; |
| | } |
| | |
| | table { |
| | width: 100%; |
| | border-collapse: collapse; |
| | margin: 1.5em 0; |
| | box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
| | } |
| | |
| | th, td { |
| | padding: 15px; |
| | text-align: left; |
| | border-bottom: 1px solid #ddd; |
| | } |
| | |
| | th { |
| | background: #667eea; |
| | color: white; |
| | font-weight: 600; |
| | } |
| | |
| | tr:hover { |
| | background: #f8f9fa; |
| | } |
| | |
| | .controls { |
| | position: fixed; |
| | bottom: 30px; |
| | right: 30px; |
| | display: flex; |
| | gap: 15px; |
| | z-index: 1000; |
| | } |
| | |
| | button { |
| | background: white; |
| | border: none; |
| | padding: 15px 25px; |
| | border-radius: 50px; |
| | cursor: pointer; |
| | font-size: 1.1em; |
| | font-weight: 600; |
| | box-shadow: 0 4px 15px rgba(0,0,0,0.2); |
| | transition: all 0.3s; |
| | color: #667eea; |
| | } |
| | |
| | button:hover { |
| | transform: translateY(-2px); |
| | box-shadow: 0 6px 20px rgba(0,0,0,0.3); |
| | background: #667eea; |
| | color: white; |
| | } |
| | |
| | .slide-number { |
| | position: fixed; |
| | bottom: 30px; |
| | left: 30px; |
| | background: white; |
| | padding: 10px 20px; |
| | border-radius: 50px; |
| | font-weight: 600; |
| | box-shadow: 0 4px 15px rgba(0,0,0,0.2); |
| | color: #667eea; |
| | } |
| | |
| | strong { |
| | color: #667eea; |
| | font-weight: 700; |
| | } |
| | |
| | .slide.lead strong { |
| | color: white; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="slide-container"> |
| | """ |
| |
|
| | |
| | slide_html = [] |
| | for i, slide in enumerate(slides): |
| | if not slide.strip(): |
| | continue |
| |
|
| | |
| | is_lead = '<!-- _class: lead -->' in slide |
| |
|
| | |
| | slide = re.sub(r'<!--.*?-->', '', slide, flags=re.DOTALL) |
| | slide = re.sub(r'^---.*?$', '', slide, flags=re.MULTILINE) |
| |
|
| | |
| | slide = re.sub(r'^# (.*?)$', r'<h1>\1</h1>', slide, flags=re.MULTILINE) |
| | slide = re.sub(r'^## (.*?)$', r'<h2>\1</h2>', slide, flags=re.MULTILINE) |
| | slide = re.sub(r'^### (.*?)$', r'<h3>\1</h3>', slide, flags=re.MULTILINE) |
| | slide = re.sub(r'^#### (.*?)$', r'<h4>\1</h4>', slide, flags=re.MULTILINE) |
| |
|
| | |
| | slide = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', slide) |
| | slide = re.sub(r'\*(.*?)\*', r'<em>\1</em>', slide) |
| |
|
| | |
| | slide = re.sub(r'^- (.*?)$', r'<li>\1</li>', slide, flags=re.MULTILINE) |
| | slide = re.sub(r'(<li>.*?</li>)', r'<ul>\1</ul>', slide, flags=re.DOTALL) |
| | slide = re.sub(r'</ul>\s*<ul>', '', slide) |
| |
|
| | |
| | slide = re.sub(r'```(.*?)```', r'<pre><code>\1</code></pre>', slide, flags=re.DOTALL) |
| |
|
| | |
| | lines = slide.split('\n') |
| | in_table = False |
| | table_html = [] |
| | processed_lines = [] |
| |
|
| | for line in lines: |
| | if '|' in line and not line.strip().startswith('<'): |
| | if not in_table: |
| | in_table = True |
| | table_html = ['<table>'] |
| |
|
| | cells = [cell.strip() for cell in line.split('|')[1:-1]] |
| | if all(set(cell) <= {'-', ' ', ':'} for cell in cells): |
| | continue |
| |
|
| | if len(table_html) == 1: |
| | table_html.append('<thead><tr>') |
| | for cell in cells: |
| | table_html.append(f'<th>{cell}</th>') |
| | table_html.append('</tr></thead><tbody>') |
| | else: |
| | table_html.append('<tr>') |
| | for cell in cells: |
| | table_html.append(f'<td>{cell}</td>') |
| | table_html.append('</tr>') |
| | else: |
| | if in_table: |
| | table_html.append('</tbody></table>') |
| | processed_lines.append(''.join(table_html)) |
| | in_table = False |
| | table_html = [] |
| | processed_lines.append(line) |
| |
|
| | if in_table: |
| | table_html.append('</tbody></table>') |
| | processed_lines.append(''.join(table_html)) |
| |
|
| | slide = '\n'.join(processed_lines) |
| |
|
| | |
| | slide = re.sub(r'^([^<\n][^\n]*?)$', r'<p>\1</p>', slide, flags=re.MULTILINE) |
| |
|
| | lead_class = ' lead' if is_lead else '' |
| | active_class = ' active' if i == 0 else '' |
| |
|
| | slide_html.append(f' <div class="slide{lead_class}{active_class}" data-slide="{i}">\n{slide}\n </div>') |
| |
|
| | html += '\n'.join(slide_html) |
| |
|
| | |
| | html += """ |
| | </div> |
| | |
| | <div class="slide-number"> |
| | <span id="current-slide">1</span> / <span id="total-slides"></span> |
| | </div> |
| | |
| | <div class="controls"> |
| | <button onclick="previousSlide()">← Previous</button> |
| | <button onclick="nextSlide()">Next →</button> |
| | </div> |
| | |
| | <script> |
| | let currentSlide = 0; |
| | const slides = document.querySelectorAll('.slide'); |
| | const totalSlides = slides.length; |
| | |
| | document.getElementById('total-slides').textContent = totalSlides; |
| | |
| | function showSlide(n) { |
| | slides[currentSlide].classList.remove('active'); |
| | currentSlide = (n + totalSlides) % totalSlides; |
| | slides[currentSlide].classList.add('active'); |
| | document.getElementById('current-slide').textContent = currentSlide + 1; |
| | } |
| | |
| | function nextSlide() { |
| | showSlide(currentSlide + 1); |
| | } |
| | |
| | function previousSlide() { |
| | showSlide(currentSlide - 1); |
| | } |
| | |
| | // Keyboard navigation |
| | document.addEventListener('keydown', (e) => { |
| | if (e.key === 'ArrowRight' || e.key === ' ') { |
| | nextSlide(); |
| | } else if (e.key === 'ArrowLeft') { |
| | previousSlide(); |
| | } |
| | }); |
| | |
| | // Touch navigation for mobile |
| | let touchStartX = 0; |
| | let touchEndX = 0; |
| | |
| | document.addEventListener('touchstart', (e) => { |
| | touchStartX = e.changedTouches[0].screenX; |
| | }); |
| | |
| | document.addEventListener('touchend', (e) => { |
| | touchEndX = e.changedTouches[0].screenX; |
| | if (touchStartX - touchEndX > 50) { |
| | nextSlide(); |
| | } else if (touchEndX - touchStartX > 50) { |
| | previousSlide(); |
| | } |
| | }); |
| | </script> |
| | </body> |
| | </html> |
| | """ |
| |
|
| | |
| | with open(output_file, 'w', encoding='utf-8') as f: |
| | f.write(html) |
| |
|
| | print(f"✓ Created HTML presentation: {output_file}") |
| |
|
| |
|
| | if __name__ == '__main__': |
| | md_file = Path('SPARKNET_Slides.md') |
| | html_file = Path('SPARKNET_Slides.html') |
| |
|
| | if not md_file.exists(): |
| | print(f"Error: {md_file} not found") |
| | exit(1) |
| |
|
| | convert_md_to_html(md_file, html_file) |
| | print(f"\nOpen {html_file} in your browser to view the presentation") |
| | print("Use arrow keys or buttons to navigate") |
| |
|