#!/usr/bin/env python3 """fix_retro_articles.py — Wendet BSN-Standard auf alle Retro-Artikel an.""" import re, os, sys from datetime import datetime WORKSPACE = '/home/hermes/workspace' TODAY = '24. Juni 2026' STANDARD_CSS = ''' body { font-family: Georgia, 'Times New Roman', serif; line-height: 1.8; max-width: 720px; margin: 0 auto; padding: 2rem 1.5rem; color: #1a1a1a; background: #fff; } article { } .dachzeile { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; color: #c00; margin-bottom: 0.3rem; font-weight: 600; } h1 { font-size: 2rem; line-height: 1.25; margin-top: 0.2rem; margin-bottom: 0.5rem; } .meta { font-size: 0.9rem; color: #666; margin-bottom: 2rem; } h2 { font-size: 1.4rem; margin-top: 2.5rem; margin-bottom: 0.8rem; } p { margin-bottom: 1.2rem; } blockquote { margin: 1.5rem 0; padding: 0.8rem 1.2rem; border-left: 3px solid #c00; background: #f9f9f9; font-style: italic; color: #444; } blockquote p { margin-bottom: 0.5rem; } a { color: #c00; text-decoration: underline; } a:hover { text-decoration: none; } .vgwort { margin: 2rem 0 0.5rem; } .read-more { display: none; } hr { border: none; border-top: 1px solid #ddd; margin: 2rem 0; } .quellen { font-size: 0.85rem; color: #555; margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #ddd; } .quellen h3 { font-size: 1rem; margin-bottom: 0.5rem; } .quellen ul { list-style: none; padding-left: 0; } .quellen li { margin-bottom: 0.4rem; word-break: break-all; } .infobox { background: #f0f4f8; border: 1px solid #d0d8e0; border-radius: 6px; padding: 1rem 1.3rem; margin: 1.2rem 0; font-size: 0.9rem; } .infobox p { margin: 0.3rem 0; } .affiliate-box { background: #f5f0eb; border: 1px solid #d4c5b2; border-radius: 8px; padding: 1rem 1.5rem; margin: 1.5rem 0; } ''' VG_WORT_PIXEL = '' DACHZEILE_MAP = { 'wingspan': 'Retrospektive', 'tainted_grail': 'Retrospektive', 'spirit_island': 'Retrospektive', 'sleeping_gods': 'Retrospektive', 'root': 'Retrospektive', 'oathsworn': 'Retrospektive', 'nemesis': 'Retrospektive', 'gloomhaven': 'Retrospektive', 'earthborne_rangers': 'Retrospektive', 'blood_on_the_clocktower': 'Retrospektive', '7th_continent': 'Retrospektive', 'sky_team': 'Retrospektive', 'deep_regrets': 'Spielevorstellung', } def get_dachzeile(filename): for key, val in DACHZEILE_MAP.items(): if key in filename: return val return 'Retrospektive' def fix_article(filepath): with open(filepath) as f: content = f.read() name = os.path.basename(filepath) dachzeile_text = get_dachzeile(name) # 1. Replace custom with standard CSS content = re.sub( r'', f'', content, flags=re.DOTALL ) # 2. Add meta description if missing if '(.*?)', content, re.DOTALL) if body_match: body_text = body_match.group(1) body_text = re.sub(r'<[^>]+>', ' ', body_text) body_text = re.sub(r'\s+', ' ', body_text).strip()[:150] meta_desc = f'' content = content.replace('DATE · Retrospektive · Lesezeit: X Minuten

) # with just

DATUM

content = re.sub( r'

[^<]*

', f'

{TODAY}

', content ) # 4. Add VG-Wort pixel after
tag if 'vgwort-pixel' not in content: content = content.replace( '
', f'
\n\n{VG_WORT_PIXEL}\n' ) # 5. Add dachzeile after VG-Wort pixel, before h1 if 'class="dachzeile"' not in content: content = re.sub( r'(
\s*' + re.escape(VG_WORT_PIXEL) + r'\s*)

', rf'\1

{dachzeile_text}

\n\n

', content ) # 6. Add border-bottom div after h1 if 'border-bottom:2px' not in content: content = re.sub( r'(

.*?

)', r'\1\n
', content ) # 7. Add read-more after first content paragraph (first

after meta) if '


[^<]*

', content) if meta_match: meta_end = meta_match.end() # Find next

after meta next_p = re.search(r'

', content[meta_end:]) if next_p: para_start = meta_end + next_p.start() # Find end of this paragraph para_end_match = re.search(r'

', content[para_start:]) if para_end_match: para_end = para_start + para_end_match.end() content = (content[:para_end] + f'\n\n
\n\n' + content[para_end:]) # 8. Fix Quellen format:

Quellen

→ proper format if '

Quellen

' in content: content = content.replace( '

Quellen

', '

Quellen

' ) # 9. Add Recherchestand before closing
if missing if 'Recherchestand' not in content: content = content.replace( '
', f'

Recherchestand: {TODAY}.

\n
' ) # 10. Ensure all external links have target="_blank" # Only add to links that don't already have it def add_target_blank(m): link = m.group(0) if 'target=' in link: return link return link.replace(']*>', add_target_blank, content) # Write back with open(filepath, 'w') as f: f.write(content) return True def main(): retro_files = [ f for f in os.listdir(WORKSPACE) if (('retro_2026-06-15' in f or 'sky_team_2026' in f or 'deep_regrets_2026' in f) and f.endswith('.html')) ] for f in sorted(retro_files): filepath = os.path.join(WORKSPACE, f) print(f"Fixing: {f}") try: fix_article(filepath) print(f" ✅ Done") except Exception as e: print(f" ❌ Error: {e}") if __name__ == '__main__': main()