-
diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css
index aa80558c0c..46cb44b0b1 100644
--- a/frontend/src/v2/v2.css
+++ b/frontend/src/v2/v2.css
@@ -1960,10 +1960,17 @@ body.modern-ui.v2-canvas {
/* ---------- Login ---------- */
+/* The auth page is its own scroll container: .v2-root and html hide overflow,
+ so a card taller than the window (a short laptop, a phone with the keyboard
+ up) would otherwise lose its last rows. The card centres with auto margins,
+ which fall to zero when it overflows; place-items: center would push its top
+ out of reach instead. */
.v2-login {
flex: 1;
- display: grid;
- place-items: center;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
padding: 24px;
background: var(--v2-page-bg);
}
@@ -1973,25 +1980,47 @@ body.modern-ui.v2-canvas {
.v2-login__card {
width: 100%;
max-width: 400px;
+ margin: auto;
background: var(--v2-surface);
border-radius: 14px;
padding: 32px;
box-shadow: var(--v2-shadow-pane);
}
-/* Sign-in stacks its rows on one 16px rhythm, as drawn. Other auth pages
- still space their rows with margins until they are lifted. */
+/* Each auth card stacks its rows on one 16px rhythm, as drawn. A form inside
+ the card (invite-required has two) lends its rows to that grid. */
.v2-login__card--stack {
display: grid;
gap: 16px;
}
+.v2-login__card--stack > form {
+ display: contents;
+}
.v2-login__card--stack .v2-login__brand,
.v2-login__card--stack .v2-login__field,
.v2-login__card--stack .v2-login__or,
.v2-login__card--stack .v2-login__oauth,
-.v2-login__card--stack .v2-login__error {
+.v2-login__card--stack .v2-login__notice,
+.v2-login__card--stack .v2-login__subtitle,
+.v2-login__card--stack .v2-login__hint,
+.v2-login__card--stack .v2-login__legal,
+.v2-login__card--stack .v2-login__field-hint {
margin: 0;
}
+/* The one prose line a page keeps (the frames' .sub), and a plain hint
+ under it: no box on a lifted card. */
+.v2-login__card--stack .v2-login__subtitle {
+ font-size: 13.5px;
+ line-height: 1.5;
+}
+.v2-login__card--stack .v2-login__hint {
+ padding: 0;
+ border-radius: 0;
+ background: none;
+ font-size: 13px;
+ line-height: 1.5;
+ color: var(--v2-text-secondary);
+}
.v2-login__card--stack .v2-login__title {
margin: 4px 0 0;
}
@@ -2043,11 +2072,22 @@ body.modern-ui.v2-canvas {
}
.v2-login__label {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
font-size: 12.5px;
font-weight: 600;
color: var(--v2-text-primary);
}
+/* "optional" rides in the label, not the field, at its far end. */
+.v2-login__label em {
+ margin-left: 8px;
+ font: 500 11px/1 var(--v2-font-mono);
+ font-style: normal;
+ color: var(--v2-text-muted);
+}
+
/* "Forgot password?" sits on the password label's line. */
.v2-login__label-row {
display: flex;
@@ -2104,8 +2144,9 @@ body.modern-ui.v2-canvas {
cursor: not-allowed;
}
-/* One quiet row with the alert mark, as the auth frames draw errors. */
-.v2-login__error {
+/* One quiet row, as the auth frames draw both errors (alert mark) and
+ confirmations (check mark). V2AuthNotice renders it. */
+.v2-login__notice {
display: flex;
align-items: flex-start;
gap: 10px;
@@ -2119,7 +2160,7 @@ body.modern-ui.v2-canvas {
line-height: 19px;
}
-.v2-login__error svg {
+.v2-login__notice svg {
flex: none;
margin-top: 1.5px;
}
@@ -2130,10 +2171,8 @@ body.modern-ui.v2-canvas {
text-align: right;
}
-/* "or" separator between password form and social buttons. NOT
- .v2-login__divider — that name is already taken by the invite-required
- page's bordered section break (defined later in this file, so it would
- win the cascade and stack a second hairline on top of this one). */
+/* The "or" rule between the providers and the form. On invite-required it
+ carries the waitlist line in place of "or". */
.v2-login__or {
display: flex;
align-items: center;
@@ -2194,10 +2233,18 @@ body.modern-ui.v2-canvas {
display: block;
margin-top: 6px;
font-size: 12px;
- line-height: 1.45;
+ line-height: 1.5;
color: var(--v2-text-tertiary);
}
+/* The terms line: muted, under the key. */
+.v2-login__legal {
+ font-size: 12px;
+ line-height: 1.5;
+ color: var(--v2-text-muted);
+ text-align: center;
+}
+
.v2-login__hint {
margin-top: 18px;
padding: 10px 12px;
@@ -2232,7 +2279,10 @@ body.modern-ui.v2-canvas {
color: var(--v2-text-secondary);
}
-.v2-root .v2-login__alt a.v2-login__link {
+.v2-root .v2-login__alt a.v2-login__link,
+.v2-root .v2-login__legal a.v2-login__link,
+.v2-root .v2-login__hint a.v2-login__link,
+.v2-root .v2-login__notice a.v2-login__link {
font-weight: 600;
}
.v2-root a.v2-login__link:hover {
@@ -2251,43 +2301,32 @@ body.modern-ui.v2-canvas {
margin: 0;
box-shadow: none;
}
-.v2-login__success {
- margin-top: 10px;
- padding: 8px 12px;
- border-radius: var(--v2-radius);
- background: var(--v2-success-soft);
- color: var(--v2-success);
- font-size: 12px;
-}
+/* The second path on a page is a key, not a second fill: the waitlist, and
+ the way out of a failed verification. */
.v2-root button.v2-login__submit--ghost {
- background: transparent;
+ border-color: var(--v2-key-edge);
+ background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b));
+ box-shadow: var(--v2-key-inset);
color: var(--v2-text-primary);
- border: 1px solid var(--v2-border);
}
.v2-root button.v2-login__submit--ghost:hover:not(:disabled) {
- background: var(--v2-surface-hover);
+ background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b));
border-color: var(--v2-border-strong);
}
-.v2-login__divider {
- margin: 22px 0 14px;
- padding-top: 18px;
- border-top: 1px solid var(--v2-border-soft);
- font-size: 13px;
- font-weight: 600;
- color: var(--v2-text-secondary);
- text-align: left;
+.v2-root button.v2-login__submit--ghost:focus-visible {
+ box-shadow: var(--v2-key-inset), var(--v2-focus-ring);
}
/* The 390 auth frames are full-bleed: the card is the screen. This block sits
after the base rules above, which it overrides at equal specificity. */
@media (max-width: 480px) {
.v2-login {
- place-items: start stretch;
padding: 0;
background: var(--v2-surface);
}
.v2-login__card {
+ margin: 0;
max-width: none;
box-shadow: none;
border-radius: 0;