+
    Q(i7                        R t ^ RIHt ^ RIt^ RIHt RtRtRtRt	Rt
R	tR
tRtRtRtRR R lltRR R lltRR R lltRR R lltR R ltR R ltR R R lltR# )!z
Shared UI utilities for FastMCP HTML pages.

This module provides reusable HTML/CSS components for OAuth callbacks,
consent pages, and other user-facing interfaces.
)annotationsNHTMLResponsez0https://gofastmcp.com/assets/brand/blue-logo.pnga  
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }

    body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
        margin: 0;
        padding: 0;
        min-height: 100vh;
        display: flex;
        align-items: center;
        justify-content: center;
        background: #f9fafb;
        color: #0a0a0a;
    }

    .container {
        background: #ffffff;
        border: 1px solid #e5e7eb;
        padding: 3rem 2.5rem;
        border-radius: 1rem;
        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
        text-align: center;
        max-width: 36rem;
        margin: 1rem;
        width: 100%;
    }

    @media (max-width: 640px) {
        .container {
            padding: 2rem 1.5rem;
            margin: 0.5rem;
        }
    }

    .logo {
        width: 64px;
        height: auto;
        margin-bottom: 1.5rem;
        display: block;
        margin-left: auto;
        margin-right: auto;
    }

    h1 {
        font-size: 1.5rem;
        font-weight: 600;
        margin-bottom: 1.5rem;
        color: #111827;
    }
a  
    .button-group {
        display: flex;
        gap: 0.75rem;
        margin-top: 1.5rem;
        justify-content: center;
    }

    button {
        padding: 0.75rem 2rem;
        font-size: 0.9375rem;
        font-weight: 500;
        border-radius: 0.5rem;
        border: none;
        cursor: pointer;
        transition: all 0.15s;
        font-family: inherit;
    }

    button:hover {
        transform: translateY(-1px);
        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
    }

    .btn-approve, .btn-primary {
        background: #10b981;
        color: #ffffff;
        min-width: 120px;
    }

    .btn-deny, .btn-secondary {
        background: #6b7280;
        color: #ffffff;
        min-width: 120px;
    }
a  
    .info-box {
        background: #f0f9ff;
        border: 1px solid #bae6fd;
        border-radius: 0.5rem;
        padding: 1rem;
        margin-bottom: 1.5rem;
        text-align: left;
        font-size: 0.9375rem;
        line-height: 1.5;
        color: #374151;
    }

    .info-box p {
        margin-bottom: 0.5rem;
    }

    .info-box p:last-child {
        margin-bottom: 0;
    }

    .info-box.centered {
        text-align: center;
    }

    .info-box.error {
        background: #fef2f2;
        border-color: #fecaca;
        color: #991b1b;
    }

    .info-box strong {
        color: #0ea5e9;
        font-weight: 600;
    }

    .info-box .server-name-link {
        color: #0ea5e9;
        text-decoration: underline;
        font-weight: 600;
        cursor: pointer;
        transition: opacity 0.15s;
    }

    .info-box .server-name-link:hover {
        opacity: 0.8;
    }

    /* Monospace info box - gray styling with code font */
    .info-box-mono {
        background: #f9fafb;
        border: 1px solid #e5e7eb;
        border-radius: 0.5rem;
        padding: 0.875rem;
        margin: 1.25rem 0;
        font-size: 0.875rem;
        color: #6b7280;
        font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
        text-align: left;
    }

    .info-box-mono.centered {
        text-align: center;
    }

    .info-box-mono.error {
        background: #fef2f2;
        border-color: #fecaca;
        color: #991b1b;
    }

    .info-box-mono strong {
        color: #111827;
        font-weight: 600;
    }

    .warning-box {
        background: #f0f9ff;
        border: 1px solid #bae6fd;
        border-radius: 0.5rem;
        padding: 1rem;
        margin-bottom: 1.5rem;
        text-align: center;
    }

    .warning-box p {
        margin-bottom: 0.5rem;
        line-height: 1.5;
        color: #6b7280;
        font-size: 0.9375rem;
    }

    .warning-box p:last-child {
        margin-bottom: 0;
    }

    .warning-box strong {
        color: #0ea5e9;
        font-weight: 600;
    }

    .warning-box a {
        color: #0ea5e9;
        text-decoration: underline;
        font-weight: 600;
    }

    .warning-box a:hover {
        color: #0284c7;
        text-decoration: underline;
    }
a  
    .status-message {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.75rem;
        margin-bottom: 1.5rem;
    }

    .status-icon {
        font-size: 1.5rem;
        line-height: 1;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 2rem;
        height: 2rem;
        border-radius: 0.5rem;
        flex-shrink: 0;
    }

    .status-icon.success {
        background: #10b98120;
    }

    .status-icon.error {
        background: #ef444420;
    }

    .message {
        font-size: 1.125rem;
        line-height: 1.75;
        color: #111827;
        font-weight: 600;
        text-align: left;
    }
a)  
    .detail-box {
        background: #f9fafb;
        border: 1px solid #e5e7eb;
        border-radius: 0.5rem;
        padding: 1rem;
        margin-bottom: 1.5rem;
        text-align: left;
    }

    .detail-row {
        display: flex;
        padding: 0.5rem 0;
        border-bottom: 1px solid #e5e7eb;
    }

    .detail-row:last-child {
        border-bottom: none;
    }

    .detail-label {
        font-weight: 600;
        min-width: 160px;
        color: #6b7280;
        font-size: 0.875rem;
        flex-shrink: 0;
        padding-right: 1rem;
    }

    .detail-value {
        flex: 1;
        font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
        font-size: 0.75rem;
        color: #111827;
        word-break: break-all;
        overflow-wrap: break-word;
    }
aa  
    .redirect-section {
        background: #fffbeb;
        border: 1px solid #fcd34d;
        border-radius: 0.5rem;
        padding: 1rem;
        margin-bottom: 1.5rem;
        text-align: left;
    }

    .redirect-section .label {
        font-size: 0.875rem;
        color: #6b7280;
        font-weight: 600;
        margin-bottom: 0.5rem;
        display: block;
    }

    .redirect-section .value {
        font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
        font-size: 0.875rem;
        color: #111827;
        word-break: break-all;
        margin-top: 0.25rem;
    }
u  
    details {
        margin-bottom: 1.5rem;
        text-align: left;
    }

    summary {
        cursor: pointer;
        font-size: 0.875rem;
        color: #6b7280;
        font-weight: 600;
        list-style: none;
        padding: 0.5rem;
        border-radius: 0.25rem;
    }

    summary:hover {
        background: #f9fafb;
    }

    summary::marker {
        display: none;
    }

    summary::before {
        content: "▶";
        display: inline-block;
        margin-right: 0.5rem;
        transition: transform 0.2s;
        font-size: 0.75rem;
    }

    details[open] summary::before {
        transform: rotate(90deg);
    }
z}
    .close-instruction, .help-text {
        font-size: 0.875rem;
        color: #6b7280;
        margin-top: 1.5rem;
    }
a.  
    .help-link-container {
        position: fixed;
        bottom: 1.5rem;
        right: 1.5rem;
        font-size: 0.875rem;
    }

    .help-link {
        color: #6b7280;
        text-decoration: none;
        cursor: help;
        position: relative;
        display: inline-block;
        border-bottom: 1px dotted #9ca3af;
    }

    @media (max-width: 640px) {
        .help-link {
            background: #ffffff;
            padding: 0.25rem 0.5rem;
            border-radius: 0.25rem;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
        }
    }

    .help-link:hover {
        color: #111827;
        border-bottom-color: #111827;
    }

    .help-link:hover .tooltip {
        opacity: 1;
        visibility: visible;
    }

    .tooltip {
        position: absolute;
        bottom: 100%;
        right: 0;
        left: auto;
        margin-bottom: 0.5rem;
        background: #1f2937;
        color: #ffffff;
        padding: 0.75rem 1rem;
        border-radius: 0.5rem;
        font-size: 0.8125rem;
        line-height: 1.5;
        width: 280px;
        max-width: calc(100vw - 3rem);
        opacity: 0;
        visibility: hidden;
        transition: opacity 0.2s, visibility 0.2s;
        box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
        text-align: left;
    }

    .tooltip::after {
        content: '';
        position: absolute;
        top: 100%;
        right: 1rem;
        border: 6px solid transparent;
        border-top-color: #1f2937;
    }

    .tooltip-link {
        color: #60a5fa;
        text-decoration: underline;
    }
c          
     ,    V ^8  d   QhRRRRRRRRRR/# )   contentstrtitleadditional_styles
csp_policyreturn )formats   "Z/Users/agent/.openclaw/workspace/venv/lib/python3.14/site-packages/fastmcp/utilities/ui.py__annotate__r     s:     - --- - 	-
 	-    c                    \         P                  ! V4      pV'       d   R\         P                  ! VRR7       R2MRpRV R\         RV R	V R
V  R2# )aR  
Create a complete HTML page with FastMCP styling.

Args:
    content: HTML content to place inside the page
    title: Page title
    additional_styles: Extra CSS to include
    csp_policy: Content Security Policy header value.
        If empty string "", the CSP meta tag is omitted entirely.

Returns:
    Complete HTML page as string
z4<meta http-equiv="Content-Security-Policy" content="T)quotez" /> z
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>z%</title>
        <style>
            z
            z
        </style>
        z 
    </head>
    <body>
        z
    </body>
    </html>
    )htmlescapeBASE_STYLES)r   r	   r
   r   csp_metas   &&&& r   create_pager     s    & KKE
  ?t{{:]a?b>ccgh  w M   		
 	 
	  r   c               $    V ^8  d   QhRRRRRR/# )r   icon_urlz
str | Nonealt_textr   r   r   )r   s   "r   r   r     s&     H H* Hs H3 Hr   c                    T ;'       g    \         p\        P                  ! V4      pR\        P                  ! V4       RV R2# )zCreate logo HTML.

Args:
    icon_url: Optional custom icon URL. If not provided, uses the FastMCP logo.
    alt_text: Alt text for the logo image.

Returns:
    HTML for logo image tag.
z
<img src="z" alt="z" class="logo" />)FASTMCP_LOGO_URLr   r   )r   r   urlalts   &&  r   create_logor!     s@     
&
&&C
++h
CC()5FGGr   c               $    V ^8  d   QhRRRRRR/# )r   messager   
is_successboolr   r   )r   s   "r   r   r     s!      3 D C r   c                v    \         P                  ! V 4      p V'       d   RMRpV'       d   RMRpRV RV RV  R2# )	u   
Create a status message with icon.

Args:
    message: Status message text
    is_success: True for success (✓), False for error (✕)

Returns:
    HTML for status message
u   ✓u   ✕successerrorzK
        <div class="status-message">
            <span class="status-icon ">z)</span>
            <div class="message">z</div>
        </div>
    r   r   )r#   r$   icon
icon_classs   &&  r   create_status_messager-     sM     kk'"G5ED(gJ&&0\D6 :"") + r   c          
     ,    V ^8  d   QhRRRRRRRRRR/# )r   r   r   is_errorr%   centered	monospacer   r   )r   s   "r   r   r     s:     7 777 7 	7
 	7r   c                    \         P                  ! V 4      p V'       d   RMRpV.pV'       d   VP                  R4       V'       d   VP                  R4       RP                  V4      pRV RV  R2# )	a#  
Create an info box.

Args:
    content: HTML content for the info box
    is_error: True for error styling, False for normal
    centered: True to center the text, False for left-aligned
    monospace: True to use gray monospace font styling instead of blue

Returns:
    HTML for info box
zinfo-box-monozinfo-boxr(   r0    z<div class="r)   </div>)r   r   appendjoin)r   r/   r0   r1   
base_classclasses	class_strs   &&&&   r   create_info_boxr:     sc    $ kk'"G$-:JlGwz"!I)Bwiv66r   c                    V ^8  d   QhRRRR/# )r   rowszlist[tuple[str, str]]r   r   r   )r   s   "r   r   r   8  s     8 81 8c 8r   c                >    RP                  R V  4       4      pRV R2# )z|
Create a detail box with key-value pairs.

Args:
    rows: List of (label, value) tuples

Returns:
    HTML for detail box

c              3     "   T F8  w  rR \         P                  ! V4       R\         P                  ! V4       R2x  K:  	  R# 5i)zH
        <div class="detail-row">
            <div class="detail-label">z.:</div>
            <div class="detail-value">z</div>
        </div>
        Nr*   ).0labelvalues   &  r   	<genexpr>$create_detail_box.<locals>.<genexpr>B  sJ       !LE''+{{5'9&: ;''+{{5'9&: ;		 !s   A Az<div class="detail-box">r4   r6   )r<   	rows_htmls   & r   create_detail_boxrG   8  s2     		  ! I &i[77r   c                    V ^8  d   QhRRRR/# )r   buttonszlist[tuple[str, str, str]]r   r   r   )r   s   "r   r   r   O  s     = =!; = =r   c                >    RP                  R V  4       4      pRV R2# )z|
Create a group of buttons.

Args:
    buttons: List of (text, value, css_class) tuples

Returns:
    HTML for button group
r>   c              3  @   "   T F  w  rpR V RV RV R2x  K  	  R# 5i)z+<button type="submit" name="action" value="z	" class="r)   z	</button>Nr   )r@   textrB   	css_classs   &   r   rC   &create_button_group.<locals>.<genexpr>Y  s5      &-"D 6eWIi[PRSWRXXab&-s   z<div class="button-group">r4   rE   )rI   buttons_htmls   & r   create_button_grouprP   O  s0     99 &- L
 (~V<<r   c               $    V ^8  d   QhRRRRRR/# )r   r   r   status_codeintr   r   r   )r   s   "r   r   r   a  s!      c  l r   c                "    \        V VRR/R7      # )a  
Create an HTMLResponse with security headers.

Adds X-Frame-Options: DENY to prevent clickjacking attacks per MCP security best practices.

Args:
    html: HTML content to return
    status_code: HTTP status code

Returns:
    HTMLResponse with security headers
zX-Frame-OptionsDENY)r   rR   headersr   )r   rR   s   &&r   create_secure_html_responserW   a  s     "F+ r   )FastMCPr   zTdefault-src 'none'; style-src 'unsafe-inline'; img-src https: data:; base-uri 'none')NrX   )T)FFF)   )__doc__
__future__r   r   starlette.responsesr   r   r   BUTTON_STYLESINFO_BOX_STYLESSTATUS_MESSAGE_STYLESDETAIL_BOX_STYLESREDIRECT_SECTION_STYLESDETAILS_STYLESHELPER_TEXT_STYLESTOOLTIP_STYLESr   r!   r-   r:   rG   rP   rW   r   r   r   <module>re      s    #  , F 5p#Lod$ N% P 8#L FR-`H.7:8.=$ r   