|
| 1 | +<!doctype html> |
| 2 | +<html lang="zh-CN"> |
| 3 | +<head> |
| 4 | + <meta charset="utf-8"> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> |
| 6 | + <meta name="referrer" content="no-referrer"> |
| 7 | + <meta name="robots" content="noindex, nofollow"> |
| 8 | + <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> |
| 9 | + <meta name="theme-color" content="#171717" media="(prefers-color-scheme: dark)"> |
| 10 | + <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; object-src 'none'"> |
| 11 | + <title>正在返回 PythonIDE</title> |
| 12 | + <style> |
| 13 | + :root { |
| 14 | + color-scheme: light dark; |
| 15 | + --bg: #fff; |
| 16 | + --surface: #fff; |
| 17 | + --ink: #0d0d0d; |
| 18 | + --muted: #6f6f6f; |
| 19 | + --faint: #929292; |
| 20 | + --line: rgba(13, 13, 13, .12); |
| 21 | + --line-strong: rgba(13, 13, 13, .22); |
| 22 | + --inverse: #0d0d0d; |
| 23 | + --inverse-text: #fff; |
| 24 | + --danger: #b42318; |
| 25 | + --danger-soft: rgba(180, 35, 24, .08); |
| 26 | + --ease: cubic-bezier(.22, 1, .36, 1); |
| 27 | + --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, "PingFang SC", sans-serif; |
| 28 | + font-family: var(--font); |
| 29 | + } |
| 30 | + @media (prefers-color-scheme: dark) { |
| 31 | + :root { |
| 32 | + --bg: #171717; |
| 33 | + --surface: #202020; |
| 34 | + --ink: #f5f5f5; |
| 35 | + --muted: #a6a6a6; |
| 36 | + --faint: #858585; |
| 37 | + --line: rgba(255, 255, 255, .12); |
| 38 | + --line-strong: rgba(255, 255, 255, .22); |
| 39 | + --inverse: #f5f5f5; |
| 40 | + --inverse-text: #111; |
| 41 | + --danger: #ff8b82; |
| 42 | + --danger-soft: rgba(255, 139, 130, .1); |
| 43 | + } |
| 44 | + } |
| 45 | + * { box-sizing: border-box; } |
| 46 | + html { min-width: 320px; min-height: 100%; background: var(--bg); -webkit-text-size-adjust: 100%; } |
| 47 | + body { |
| 48 | + min-height: 100vh; |
| 49 | + min-height: 100svh; |
| 50 | + margin: 0; |
| 51 | + display: grid; |
| 52 | + grid-template-rows: auto 1fr auto; |
| 53 | + background: var(--bg); |
| 54 | + color: var(--ink); |
| 55 | + font-family: var(--font); |
| 56 | + -webkit-font-smoothing: antialiased; |
| 57 | + } |
| 58 | + body::before { |
| 59 | + content: ""; |
| 60 | + position: fixed; |
| 61 | + inset: 0; |
| 62 | + z-index: -1; |
| 63 | + pointer-events: none; |
| 64 | + background: radial-gradient(70% 55% at 50% -8%, rgba(127,127,127,.13), transparent 72%), var(--bg); |
| 65 | + } |
| 66 | + [hidden] { display: none !important; } |
| 67 | + .site-header, .site-footer { width: min(1120px, calc(100% - 40px)); margin: 0 auto; } |
| 68 | + .site-header { |
| 69 | + min-height: calc(72px + env(safe-area-inset-top)); |
| 70 | + padding-top: env(safe-area-inset-top); |
| 71 | + display: flex; |
| 72 | + align-items: center; |
| 73 | + border-bottom: 1px solid var(--line); |
| 74 | + } |
| 75 | + .brand { display: inline-flex; align-items: center; gap: 10px; font-size: 17px; font-weight: 650; letter-spacing: -.02em; } |
| 76 | + .brand-mark { width: 28px; height: 28px; display: block; } |
| 77 | + main { |
| 78 | + width: min(100%, 640px); |
| 79 | + margin: 0 auto; |
| 80 | + padding: clamp(52px, 9vh, 108px) 24px 72px; |
| 81 | + display: grid; |
| 82 | + place-items: center; |
| 83 | + align-content: center; |
| 84 | + } |
| 85 | + .auth-state { width: min(100%, 520px); display: grid; justify-items: center; text-align: center; animation: enter 520ms var(--ease) both; } |
| 86 | + .indicator { |
| 87 | + width: 58px; |
| 88 | + height: 58px; |
| 89 | + margin-bottom: 30px; |
| 90 | + border: 1px solid var(--line); |
| 91 | + border-radius: 50%; |
| 92 | + display: grid; |
| 93 | + place-items: center; |
| 94 | + background: var(--surface); |
| 95 | + box-shadow: 0 14px 40px rgba(0,0,0,.06); |
| 96 | + } |
| 97 | + .spinner { width: 28px; height: 28px; border: 2px solid var(--line-strong); border-top-color: var(--ink); border-radius: 50%; animation: spin 760ms linear infinite; } |
| 98 | + .state-symbol { display: none; font-size: 25px; line-height: 1; } |
| 99 | + body[data-state="fallback"] .spinner, body[data-state="error"] .spinner { display: none; } |
| 100 | + body[data-state="fallback"] .fallback-symbol, body[data-state="error"] .error-symbol { display: block; } |
| 101 | + body[data-state="error"] .indicator { border-color: var(--danger); background: var(--danger-soft); box-shadow: none; color: var(--danger); } |
| 102 | + .eyebrow { margin: 0 0 16px; color: var(--muted); font-size: 12px; font-weight: 680; letter-spacing: .1em; text-transform: uppercase; } |
| 103 | + h1 { margin: 0; font-size: clamp(38px, 9vw, 54px); font-weight: 620; letter-spacing: -.052em; line-height: 1.06; text-wrap: balance; } |
| 104 | + .message { max-width: 500px; margin: 22px 0 0; color: var(--muted); font-size: clamp(16px, 3.8vw, 18px); line-height: 1.65; text-wrap: pretty; } |
| 105 | + .actions { width: min(100%, 320px); margin-top: 34px; display: grid; gap: 12px; } |
| 106 | + .button { |
| 107 | + min-height: 50px; |
| 108 | + padding: 0 22px; |
| 109 | + border: 1px solid var(--inverse); |
| 110 | + border-radius: 999px; |
| 111 | + display: inline-flex; |
| 112 | + align-items: center; |
| 113 | + justify-content: center; |
| 114 | + gap: 14px; |
| 115 | + background: var(--inverse); |
| 116 | + color: var(--inverse-text); |
| 117 | + font-size: 15px; |
| 118 | + font-weight: 650; |
| 119 | + text-decoration: none; |
| 120 | + transition: transform 220ms var(--ease), opacity 180ms ease; |
| 121 | + } |
| 122 | + .button:hover { transform: translateY(-2px); opacity: .9; } |
| 123 | + .helper { margin: 0; color: var(--faint); font-size: 12px; line-height: 1.55; } |
| 124 | + .trust-note { margin: 34px 0 0; display: inline-flex; align-items: center; gap: 8px; color: var(--faint); font-size: 12px; } |
| 125 | + .trust-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ink); opacity: .54; } |
| 126 | + .site-footer { |
| 127 | + min-height: calc(64px + env(safe-area-inset-bottom)); |
| 128 | + padding-bottom: env(safe-area-inset-bottom); |
| 129 | + border-top: 1px solid var(--line); |
| 130 | + display: flex; |
| 131 | + align-items: center; |
| 132 | + justify-content: space-between; |
| 133 | + color: var(--faint); |
| 134 | + font-size: 11px; |
| 135 | + } |
| 136 | + .footer-context { font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; } |
| 137 | + @keyframes spin { to { transform: rotate(360deg); } } |
| 138 | + @keyframes enter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } |
| 139 | + @media (max-width: 520px) { |
| 140 | + .site-header, .site-footer { width: calc(100% - 32px); } |
| 141 | + main { padding: 44px 20px 56px; } |
| 142 | + .footer-context { display: none; } |
| 143 | + } |
| 144 | + @media (prefers-reduced-motion: reduce) { |
| 145 | + *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; } |
| 146 | + } |
| 147 | + </style> |
| 148 | +</head> |
| 149 | +<body data-state="returning"> |
| 150 | + <header class="site-header"> |
| 151 | + <div class="brand" aria-label="PythonIDE"> |
| 152 | + <svg class="brand-mark" viewBox="60 100 904 824" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
| 153 | + <circle cx="826" cy="512" r="116" fill="#E8AA24"/><circle cx="669" cy="240" r="116" fill="#1F5F95"/> |
| 154 | + <circle cx="355" cy="240" r="116" fill="#1F5F95"/><circle cx="198" cy="512" r="116" fill="#1F5F95"/> |
| 155 | + <circle cx="355" cy="784" r="116" fill="#1F5F95"/><circle cx="669" cy="784" r="116" fill="#1F5F95"/> |
| 156 | + </svg> |
| 157 | + <span>PythonIDE</span> |
| 158 | + </div> |
| 159 | + </header> |
| 160 | + <main> |
| 161 | + <section class="auth-state" aria-labelledby="title" aria-describedby="status"> |
| 162 | + <div class="indicator" aria-hidden="true"><span class="spinner"></span><span class="state-symbol fallback-symbol">→</span><span class="state-symbol error-symbol">!</span></div> |
| 163 | + <p class="eyebrow" id="eyebrow">安全连接</p> |
| 164 | + <h1 id="title">正在返回 PythonIDE</h1> |
| 165 | + <p class="message" id="status" role="status" aria-live="polite">正在将登录结果安全地交还给 App,这通常只需一瞬间。</p> |
| 166 | + <div class="actions" id="actions" hidden> |
| 167 | + <a class="button" id="continue" href="pythonide://oauth/custom-ai"><span id="button-label">返回 PythonIDE</span><span aria-hidden="true">→</span></a> |
| 168 | + <p class="helper" id="helper">若 App 没有自动打开,请点击上方按钮继续。</p> |
| 169 | + </div> |
| 170 | + <p class="trust-note"><span class="trust-dot" aria-hidden="true"></span><span id="trust-note">无需在此页面输入账号或密码</span></p> |
| 171 | + </section> |
| 172 | + </main> |
| 173 | + <footer class="site-footer"><span>© 2026 PythonIDE</span><span class="footer-context">AI OAuth callback</span></footer> |
| 174 | + <script> |
| 175 | + (() => { |
| 176 | + const body = document.body; |
| 177 | + const actions = document.getElementById('actions'); |
| 178 | + const button = document.getElementById('continue'); |
| 179 | + const eyebrow = document.getElementById('eyebrow'); |
| 180 | + const title = document.getElementById('title'); |
| 181 | + const status = document.getElementById('status'); |
| 182 | + const buttonLabel = document.getElementById('button-label'); |
| 183 | + const helper = document.getElementById('helper'); |
| 184 | + const trustNote = document.getElementById('trust-note'); |
| 185 | + const preferred = ((navigator.languages && navigator.languages[0]) || navigator.language || 'en').toLowerCase(); |
| 186 | + const zh = preferred.startsWith('zh'); |
| 187 | + const copy = zh ? { |
| 188 | + returningEyebrow: '安全连接', returningTitle: '正在返回 PythonIDE', returningStatus: '正在将登录结果安全地交还给 App,这通常只需一瞬间。', |
| 189 | + fallbackEyebrow: '需要继续', fallbackTitle: '继续返回 PythonIDE', fallbackStatus: '自动返回似乎没有完成。点击下方按钮即可继续,无需重新登录。', |
| 190 | + button: '返回 PythonIDE', helper: '若 App 没有打开,请确认已经安装最新版 PythonIDE。', trust: '无需在此页面输入账号或密码', |
| 191 | + errorEyebrow: '连接未完成', errorTitle: '无法完成返回', errorStatus: '登录响应缺少必要信息。请返回 PythonIDE 后重新连接账户。' |
| 192 | + } : { |
| 193 | + returningEyebrow: 'Secure connection', returningTitle: 'Returning to PythonIDE', returningStatus: 'Securely handing the sign-in result back to the app. This should only take a moment.', |
| 194 | + fallbackEyebrow: 'Continue', fallbackTitle: 'Continue to PythonIDE', fallbackStatus: 'The automatic return may not have completed. Use the button below to continue without signing in again.', |
| 195 | + button: 'Return to PythonIDE', helper: 'If the app does not open, make sure the latest PythonIDE is installed.', trust: 'You never need to enter an account or password on this page', |
| 196 | + errorEyebrow: 'Connection incomplete', errorTitle: 'Unable to return', errorStatus: 'The sign-in response is missing required information. Return to PythonIDE and connect again.' |
| 197 | + }; |
| 198 | + document.documentElement.lang = zh ? 'zh-CN' : 'en'; |
| 199 | + eyebrow.textContent = copy.returningEyebrow; title.textContent = copy.returningTitle; status.textContent = copy.returningStatus; |
| 200 | + buttonLabel.textContent = copy.button; helper.textContent = copy.helper; trustNote.textContent = copy.trust; |
| 201 | + const fallback = () => { body.dataset.state = 'fallback'; eyebrow.textContent = copy.fallbackEyebrow; title.textContent = copy.fallbackTitle; status.textContent = copy.fallbackStatus; actions.hidden = false; }; |
| 202 | + if (!window.location.search) { |
| 203 | + body.dataset.state = 'error'; eyebrow.textContent = copy.errorEyebrow; title.textContent = copy.errorTitle; status.textContent = copy.errorStatus; actions.hidden = false; return; |
| 204 | + } |
| 205 | + const destination = `pythonide://oauth/custom-ai${window.location.search}`; |
| 206 | + button.href = destination; |
| 207 | + window.setTimeout(fallback, 1200); |
| 208 | + try { window.location.replace(destination); } catch (_) { fallback(); } |
| 209 | + })(); |
| 210 | + </script> |
| 211 | +</body> |
| 212 | +</html> |
0 commit comments