"""SEO page components: hero, FAQ, related Spaces, disclaimer, footer.""" import html as _html from .config import SpaceConfig, COLLECTION_TITLE, COLLECTION_URL def hero_html(config: SpaceConfig) -> str: """Single H1 + one-sentence outcome promise. The only H1 on the page.""" return ( '
' f"

{_html.escape(config.h1)}

" f'

{_html.escape(config.tagline)}

' "
" ) def section_html(heading: str, body_html: str, level: int = 2) -> str: tag = f"h{level}" return ( '
' f"<{tag}>{_html.escape(heading)}{body_html}
" ) def faq_html(faqs: list[tuple[str, str]]) -> str: """Accessible
FAQ. faqs = [(question, answer_html)].""" items = "".join( f"
{_html.escape(q)}

{a}

" for q, a in faqs ) return ( '

Frequently Asked Questions

' f'
{items}
' ) def related_html(config: SpaceConfig) -> str: if not config.related: return "" cards = "".join( f'' f'' f'' for r in config.related ) return ( '

More Free D&D Tools

' f'' f'

Browse the full collection: ' f"{_html.escape(COLLECTION_TITLE)}

" ) def disclaimer_html() -> str: return ( '

' "This is an unofficial fan tool. Dungeons & Dragons and D&D are " "trademarks of Wizards of the Coast LLC. This tool is not affiliated with, " "endorsed, or sponsored by Wizards of the Coast. Portions of the results are " "generated by AI: review everything with your DM and adapt it to your table " "before play. This tool is built and maintained by " 'Loreify, an AI session-notes app for D&D ' "campaigns.

" )