@@ -39,53 +39,77 @@ const PYTHINKER_LOGO_SVG =
3939 '</svg>' ;
4040
4141const SUCCESS_BADGE_SVG =
42- '<svg class="badge-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="28 " height="28 " aria-hidden="true" focusable="false">' +
43- '<circle cx="12" cy="12" r="11" fill="#1a3d2f " stroke="#3ecf8e " stroke-width="1.5"/>' +
44- '<path d="M7.5 12.2l3 3 6-6.5" fill="none" stroke="#3ecf8e " stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
42+ '<svg class="badge-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="44 " height="44 " aria-hidden="true" focusable="false">' +
43+ '<circle cx="12" cy="12" r="11" fill="#f0fdf4 " stroke="#15803d " stroke-width="1.5"/>' +
44+ '<path class="badge-draw" d="M7.5 12.2l3 3 6-6.5" fill="none" stroke="#15803d " stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
4545 '</svg>' ;
4646
4747const ERROR_BADGE_SVG =
48- '<svg class="badge-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="28 " height="28 " aria-hidden="true" focusable="false">' +
49- '<circle cx="12" cy="12" r="11" fill="#3d1a1a " stroke="#f87171 " stroke-width="1.5"/>' +
50- '<path d="M8.5 8.5l7 7M15.5 8.5l-7 7" fill="none" stroke="#f87171 " stroke-width="2" stroke-linecap="round"/>' +
48+ '<svg class="badge-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="44 " height="44 " aria-hidden="true" focusable="false">' +
49+ '<circle cx="12" cy="12" r="11" fill="#fef2f2 " stroke="#dc2626 " stroke-width="1.5"/>' +
50+ '<path class="badge-draw" d="M8.5 8.5l7 7M15.5 8.5l-7 7" fill="none" stroke="#dc2626 " stroke-width="2" stroke-linecap="round"/>' +
5151 '</svg>' ;
5252
53+ // Palette and card treatment mirror apps/site/src/style.css (light canvas,
54+ // grid background, hairline card, blue accent). Inline-only: the Inter/mono
55+ // families fall back to system fonts because this page must work offline.
56+ const GRID_BG =
57+ 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'48\' height=\'48\'%3E%3Cpath d=\'M48 0H0v48\' fill=\'none\' stroke=\'%23111113\' stroke-opacity=\'0.045\' stroke-width=\'1\'/%3E%3C/svg%3E")' ;
58+
5359const PAGE_STYLES =
60+ ':root{color-scheme:light}' +
5461 '*,*::before,*::after{box-sizing:border-box}' +
5562 'html,body{height:100%;margin:0}' +
5663 'body{' +
57- 'font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;' +
58- 'background:#0b0f19;' +
59- 'background-image:radial-gradient(ellipse 80% 60% at 50% -10%,rgba(62,207,142,.12),transparent),' +
60- 'radial-gradient(ellipse 60% 50% at 100% 100%,rgba(238,154,135,.08),transparent);' +
61- 'color:#e8eaed;' +
64+ "font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',helvetica,arial,sans-serif;" +
65+ `background-color:#ffffff;background-image:${ GRID_BG } ;color:#111113;` +
6266 'display:flex;align-items:center;justify-content:center;padding:24px;' +
6367 '}' +
6468 '.card{' +
65- 'width:min(100%,480px );padding:48px 40px 44px ;border-radius:20px ;' +
66- 'background:linear-gradient(180deg,#1a2030 0%,#161b22 100%) ;' +
67- 'border:1px solid #2d333b ;' +
68- 'box-shadow:0 24px 48px rgba(0,0,0 ,.35),0 0 0 1px rgba(255,255,255,.04) inset ;' +
69+ 'width:min(100%,440px );padding:40px 36px 32px ;border-radius:18px ;' +
70+ 'background:#ffffff ;' +
71+ 'border:1px solid #e5e7eb ;' +
72+ 'box-shadow:0 24px 70px -42px rgba(17,17,19 ,.35);' +
6973 'display:flex;flex-direction:column;align-items:center;text-align:center;' +
74+ 'animation:card-in .35s ease-out both;' +
7075 '}' +
71- '.logo{margin-bottom:8px}' +
76+ '.logo{margin-bottom:10px}' +
77+ '.logo svg{width:64px;height:80px}' +
7278 '.brand{' +
73- 'margin:0 0 28px;font-size:13px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#6b7280;' +
79+ "margin:0 0 24px;font-family:'JetBrains Mono',ui-monospace,'SF Mono',monospace;" +
80+ 'font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#8b8d94;' +
7481 '}' +
75- '.badge{margin-bottom:16px }' +
82+ '.badge{margin-bottom:14px }' +
7683 '.badge-icon{display:block}' +
84+ '.badge-draw{stroke-dasharray:24;stroke-dashoffset:24;animation:badge-draw .4s ease-out .25s forwards}' +
7785 'h1{' +
78- 'margin:0 0 12px;font-size:26px;line-height:1.25;font-weight:650;letter-spacing:-.02em;color:#f5f7fa;' +
86+ 'margin:0 0 10px;font-size:24px;line-height:1.19;font-weight:600;letter-spacing:-.025em;color:#111113;' +
87+ '}' +
88+ 'p.message{' +
89+ 'margin:0 0 24px;font-size:16px;line-height:1.55;color:#55565c;max-width:34ch;' +
90+ '}' +
91+ '.status{' +
92+ 'width:100%;display:flex;align-items:center;justify-content:center;gap:8px;' +
93+ 'padding:10px 14px;border-radius:12px;' +
94+ 'background:#f7f7f8;border:1px solid #e5e7eb;' +
95+ "font-family:'JetBrains Mono',ui-monospace,'SF Mono',monospace;" +
96+ 'font-size:13px;color:#55565c;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;' +
7997 '}' +
80- 'p{' +
81- 'margin:0;font-size:15px;line-height:1.6;color:#9aa0a8;max-width:34ch;' +
98+ '.status .ok{color:#15803d}' +
99+ '.status .err{color:#dc2626}' +
100+ '@keyframes card-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}' +
101+ '@keyframes badge-draw{to{stroke-dashoffset:0}}' +
102+ '@media (prefers-reduced-motion:reduce){' +
103+ '.card{animation:none}' +
104+ '.badge-draw{animation:none;stroke-dashoffset:0}' +
82105 '}' ;
83106
84107function renderOAuthPage ( options : {
85108 readonly title : string ;
86109 readonly heading : string ;
87110 readonly message : string ;
88111 readonly badge : string ;
112+ readonly statusLine : string ;
89113} ) : string {
90114 return (
91115 '<!doctype html><html lang="en"><head>' +
@@ -99,7 +123,8 @@ function renderOAuthPage(options: {
99123 '<p class="brand">Pythinker Code</p>' +
100124 `<div class="badge">${ options . badge } </div>` +
101125 `<h1>${ options . heading } </h1>` +
102- `<p>${ options . message } </p>` +
126+ `<p class="message">${ options . message } </p>` +
127+ `<div class="status" role="status">${ options . statusLine } </div>` +
103128 '</main></body></html>'
104129 ) ;
105130}
@@ -110,6 +135,7 @@ export function renderOAuthSuccessPage(): string {
110135 heading : "You're logged in to Pythinker" ,
111136 message : 'You can close this tab and return to Pythinker.' ,
112137 badge : SUCCESS_BADGE_SVG ,
138+ statusLine : 'status: <span class="ok">authenticated</span>' ,
113139 } ) ;
114140}
115141
@@ -119,6 +145,7 @@ export function renderOpenAICodexOAuthSuccessPage(): string {
119145 heading : 'OpenAI Codex sign-in complete' ,
120146 message : 'You can close this tab and return to Pythinker Code.' ,
121147 badge : SUCCESS_BADGE_SVG ,
148+ statusLine : 'provider: openai-codex · status: <span class="ok">authenticated</span>' ,
122149 } ) ;
123150}
124151
@@ -128,5 +155,6 @@ export function renderOAuthErrorPage(): string {
128155 heading : 'Sign-in failed' ,
129156 message : 'The authorization server reported an error. Return to Pythinker for details.' ,
130157 badge : ERROR_BADGE_SVG ,
158+ statusLine : 'status: <span class="err">failed</span>' ,
131159 } ) ;
132160}
0 commit comments