diff --git a/.gitignore b/.gitignore
index 9b10a17..e7b5d5a 100644
Binary files a/.gitignore and b/.gitignore differ
diff --git a/internal/webapp/static/app.js b/internal/webapp/static/app.js
index de2b81a..b215fcc 100644
--- a/internal/webapp/static/app.js
+++ b/internal/webapp/static/app.js
@@ -364,28 +364,45 @@ function evidenceBlock(evidence = []) {
return `Evidence
${evidence.map((item) => `- ${escapeHTML(item.quote || item.id)}
`).join("")}
`;
}
-window.handleCredentialResponse = async (response) => {
+window._tutorGoogleCallback = async (response) => {
+ console.log("[auth] Google callback fired");
try {
const res = await request("/api/v1/auth/google", {
method: "POST",
body: JSON.stringify({ id_token: response.credential }),
});
+ console.log("[auth] Backend login success", res.user?.email);
localStorage.setItem("tutor_token", res.token);
localStorage.setItem("tutor_user", JSON.stringify(res.user));
+ if (els.loginError) {
+ els.loginError.textContent = "";
+ els.loginError.classList.remove("visible");
+ }
renderAuth();
} catch (err) {
- if (els.loginError) els.loginError.textContent = err.message;
+ console.error("[auth] Backend login failed", err);
+ if (els.loginError) {
+ els.loginError.textContent = err.message;
+ els.loginError.classList.add("visible");
+ }
}
};
+if (window._tutorPendingGoogleResponse) {
+ window._tutorGoogleCallback(window._tutorPendingGoogleResponse);
+ window._tutorPendingGoogleResponse = null;
+}
+
function renderAuth() {
const user = JSON.parse(localStorage.getItem("tutor_user") || "null");
const token = localStorage.getItem("tutor_token");
+ console.log("[auth] renderAuth", { hasUser: !!user, hasToken: !!token });
if (user && token) {
els.loginView.style.display = "none";
els.workspaceView.style.display = "grid";
- els.userInfo.textContent = user.email;
- setStatus(`Signed in as ${user.email}`);
+ els.userInfo.textContent = user.email || user.name || "User";
+ setStatus(`Signed in as ${user.email || user.name}`);
+ if (els.loginError) els.loginError.classList.remove("visible");
} else {
els.loginView.style.display = "flex";
els.workspaceView.style.display = "none";
diff --git a/internal/webapp/static/index.html b/internal/webapp/static/index.html
index f95d7b9..d322860 100644
--- a/internal/webapp/static/index.html
+++ b/internal/webapp/static/index.html
@@ -7,6 +7,17 @@
+
@@ -14,11 +25,13 @@
Tutor Platform
Interview practice
Prove you are becoming more interview-ready after each short practice loop.
+
-
+
+ By signing in, you agree to our Terms and Privacy Policy.
diff --git a/internal/webapp/static/styles.css b/internal/webapp/static/styles.css
index 064a878..bae1b34 100644
--- a/internal/webapp/static/styles.css
+++ b/internal/webapp/static/styles.css
@@ -44,7 +44,7 @@ select {
}
.workspace {
- display: grid;
+ display: none;
grid-template-columns: minmax(260px, 320px) minmax(360px, 1fr) minmax(280px, 360px);
gap: 1px;
min-height: 100vh;
@@ -464,27 +464,94 @@ button.is-loading .btn-spinner {
justify-content: center;
min-height: 100vh;
padding: 28px;
- background: var(--bg);
+ background: linear-gradient(160deg, #f5f7f4 0%, #e8efe5 100%);
}
.login-card {
background: var(--surface);
border: 1px solid var(--line);
- border-radius: 12px;
- padding: 44px 36px;
- max-width: 420px;
+ border-radius: 18px;
+ padding: 52px 40px 44px;
+ max-width: 440px;
width: 100%;
text-align: center;
+ box-shadow: 0 12px 40px rgba(24, 32, 27, 0.07);
+}
+
+.login-card .eyebrow {
+ display: inline-block;
+ letter-spacing: 0.14em;
+ font-size: 11px;
+ text-transform: uppercase;
+ color: var(--accent);
+ font-weight: 750;
+ margin-bottom: 10px;
+}
+
+html[lang="ko"] .login-card .eyebrow {
+ text-transform: none;
+ letter-spacing: 0.04em;
}
.login-card h1 {
- font-size: clamp(32px, 5vw, 52px);
- margin: 14px 0 18px;
+ font-size: clamp(34px, 5.5vw, 56px);
+ margin: 8px 0 18px;
+ line-height: 1.05;
}
.login-card .lede {
- margin: 0 auto 28px;
- max-width: 34ch;
+ margin: 0 auto 32px;
+ max-width: 32ch;
+ color: var(--muted);
+ font-size: 15px;
+ line-height: 1.55;
+}
+
+.login-divider {
+ height: 1px;
+ background: var(--line);
+ margin: 28px 0 24px;
+ position: relative;
+}
+
+.login-divider::before {
+ content: attr(data-label);
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: var(--surface);
+ padding: 0 12px;
+ color: var(--muted);
+ font-size: 11px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.login-legal {
+ margin-top: 18px;
+ font-size: 12px;
+ color: var(--muted);
+ line-height: 1.5;
+}
+
+.login-legal a {
+ color: var(--muted);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+.login-error {
+ margin-top: 14px;
+ padding: 10px 14px;
+ border-radius: 8px;
+ background: var(--weak-bg);
+ color: var(--weak);
+ font-size: 13px;
+ display: none;
+}
+.login-error.visible {
+ display: block;
}
.user-bar {