﻿/* proxima-nova, the Panel's typeface, comes from the Typekit kit linked in _Layout.cshtml.

   NOT from @font-face rules copied into this file. That was tried first, copying the four
   declarations out of the Panel's own site.css, and every one of them 404s: those
   "use.typekit.net/af/<hash>/..." URLs are kit-internal and Adobe rotates them, so a
   copy goes stale on its own with no deploy and no error beyond a quiet 404. The Panel's
   login page has been falling back to Segoe UI for exactly that reason -- the copies in
   its site.css are dead too, and _SpLoginLayout never linked the kit that would have
   replaced them.

   The kit stylesheet is the maintained URL: it is regenerated with the fonts, which is
   why _LayoutV4 has always rendered in proxima inside the panel while these pages did
   not. Needs style-src and font-src for use.typekit.net (and font-src for p.typekit.net,
   which the kit serves the actual files from); see Program.cs. */

:root {
    --ink: #434343;
    --muted: #888;
    --line: #e3e5ea;
    --danger: #dc3545;
    --ok: #1f8f4e;

    /* SURFACES AND STATES, named by the job rather than the shade. Each of these was written out
       as a hex between four and nineteen times, so the only way to find every place a surface is
       drawn was to search for the colour -- and a colour is exactly the thing that changes.

       --surface and --on-dark are both white today and must stay separate: one is a ground that a
       theme would repaint, the other is text that has to stay legible ON a repainted ground. One
       token for both would tie them together and break the second the first one moved. */
    --surface: #fff;
    --on-dark: #fff;
    --field: #f7f7f7;
    --surface-hover: #f7f8f8;
    --line-hover: #cfd3da;
    /* Every provider button, ours and Google's rendered one, is this wide.
       Kept here because the Razor partial has to pass the same number to
       Google as data-width — see _ExternalProviders.cshtml. */
    --sp-social-width: 320px;

    /* The front door's own card height, pinned by CardHeightTests. See .sp-login-card. */
    --sp-card-min: 732px;
    /* HOW FAR THE PITCH'S LAST LINE SITS ABOVE THE BOTTOM OF THE DARK COLUMN, so that it finishes
       level with the TOP of the Sign in button.

       IT WAS THE BUTTON'S BOTTOM UNTIL 2026-09-23, which made the two columns end together. That
       was right about the symptom it fixed -- a pitch floating 138px below the button reads as the
       panel not having finished -- and wrong about the goal. Two columns that finish together are
       two columns of equal weight, and this card is not a pair: one side is a pitch somebody skims
       once, the other is the thing they came for. Letting the right run on past the left by the
       height of the button is what puts the weight back on the form ("having the right side a
       little longer keeps more of the focus on the sign in section").

       STILL A MEASUREMENT, NOT A TASTE, and that is the part worth keeping. The card has a floor
       (--sp-card-min) and the form does not fill it, so nothing in CSS relates one column's content
       to the other's and the distance has to be stated. It is stated as an ALIGNMENT -- the pitch's
       last line lands on a real edge in the other column -- so it stays derivable rather than
       becoming a number somebody liked once. LandingPageLayoutTests measures both boxes in a browser
       and fails if they drift apart.

       WHAT CHANGES IT: anything that moves the Sign in button. Three provider buttons are what
       production configures; a deployment with fewer would need this smaller by ~52px per button
       dropped, and the test is what says so out loud instead of leaving it crooked. Nothing in the
       DARK column affects it -- that is what margin-top:auto on .sp-login-new-user buys. */
    --sp-left-footer-lift: 182px;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    font-family: "proxima-nova", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    color: var(--ink);
    background: #f5f6f8;
}

/* Login page shell — pixel-matched to OMA's real Supplier Panel login page
   (OMA.Web/dev/assets/css/site.css, "SP LOGIN REDESIGN" block) so the jump
   from sma.*.com.au to login.easyasset.com.au doesn't feel like a jump at
   all. No jQuery/Bootstrap/Typekit/reCAPTCHA here — same visual result,
   built with what this app already uses (vanilla CSS custom properties,
   server-rendered Razor Pages). */
.sp-login-page { background: #f4f4f4; }

@media (min-width: 601px) {
    .sp-login-page { min-height: 100vh; }
}

/* PAGE CHROME — the header band and the dark footer band.

   Ported from the Panel's own common.css (.chrome-* block), not re-derived. The card was
   already matched; the frame around it was not, so this service rendered a card floating
   on grey while the Panel renders the same card between a white logo bar and a #434343
   footer. That frame is most of what makes a page read as "the same site", and its
   absence is what made the sign-in hop feel like a different product.

   Geometry is the Panel's measure, not a choice: .nav, .footer__wrapper and .i-container
   all sit on max-width 1000px with 15px side padding, so the logo lines up with the first
   card beneath it. Wider than the 860px login card on purpose — that is how it looks in
   the Panel. */
.chrome-page {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* The one flex item between header and footer, so margin-top:auto on the footer reaches
   the bottom edge on a short page. Page content sits INSIDE it: a direct flex child with
   `margin: 0 auto` shrinks to its content instead of stretching. */
.chrome-main {
    flex: 1 1 auto;
    min-width: 0;
}

.chrome-header {
    background-color: var(--surface);
    border-bottom: solid 1px rgba(0,0,0,.08);
}

.chrome-header__inner {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    max-width: 1000px;
    margin: 0 auto;
    padding: 10px 15px;
    /* 68px, not the 48px this started at, so the band is the same height on every brand.
       Otherwise it sizes to whatever logo it holds: a stacked portal mark (IS/MS/HH) gives
       69px, the wide EasyAsset wordmark gives 49px, and the header appears to change height
       when the theme changes. 68 + 1px border matches the portal-mark height, so the common
       case is unchanged and only the EA skin moves. Kept in step with the Panel's
       .chrome-header__inner in common.css -- these two rules have to stay equal. */
    min-height: 68px;
}

.chrome-header__brand { display: inline-flex; align-items: center; }

/* 110px is .nav__logoimage's own width in the Panel, which sizes portal logos by WIDTH.
   Measured against the running Panel at the same theme: 110 x 48, band 69px, identical. */
.chrome-header__brand img { width: 110px; height: auto; }

/* The EasyAsset wordmark is a wide horizontal lockup, not a stacked mark like IS/MS/HH, so
   at a fixed 110px WIDTH it comes out around 22px tall and the header reads as squashed
   next to the Panel's. The Panel already solved this: theme-ea.css sizes that one logo by
   HEIGHT instead (body.theme-ea .nav__logoimage, max-height 34px / max-width 140px). Same
   rule here, so the two headers match on the EA skin as well as on the portal skins. */
.sp-theme-easyasset .chrome-header__brand img {
    max-width: 140px;
    max-height: 34px;
    width: auto;
    height: auto;
}

/* THE WORDS STAY, THE BAND GOES. A supplier who cannot get in needs the phone number more than
   anybody else on the site, so the help line is the last thing to cut. The dark slab behind it was
   the other half of the signed-in chrome that the header band came from, and on a page holding one
   card it read as the bottom of a different site. */
.chrome-footer {
    margin-top: auto;
    /* THE SAME INK AND THE SAME WEIGHT AS THE WORDS INSIDE THE BUTTON ABOVE IT.
       It was --muted at normal weight, which is the treatment this card gives to text somebody may
       skip. A supplier who cannot get in needs the phone number more than anybody else on the
       site, and it was the quietest thing on the screen. 14px/600 is .sp-btn-primary's own type. */
    color: var(--ink);
    font-size: 14px;
    /* NORMAL, WITH THE QUESTION BOLD INSIDE IT. 600 across all three sentences was emphasis with
       nothing to emphasise -- see the <strong> in the layout. The ink and the size stay: those are
       what stopped it being the quietest thing on the screen, and they were the half that worked. */
    font-weight: 400;
}

.chrome-footer__inner {
    box-sizing: border-box;
    max-width: 1000px;
    margin: 0 auto;
    /* 12px above, 24px below. The gap to the card was the same 24px as the gap to the bottom of the
       window, so the help line sat midway between the thing it belongs to and the edge of the page
       and read as neither. Halving the top pulls it under the card, which is where it is for: it is
       the answer to "I cannot get in", not page furniture. Below is left alone -- that one is
       breathing room against the window edge and shortening it would crowd the line instead. */
    padding: 12px 15px 24px;
    text-align: center;
}

.chrome-footer__inner a {
    color: var(--ink);
    text-decoration: underline;
}

/* THE HELP LINE BELONGS TO THE CARD, NOT TO THE BOTTOM OF THE WINDOW.

   .chrome-main grows to fill the column, so on a tall window the footer was pinned to the viewport
   edge with a stretch of empty grey between it and the card -- far enough away to read as page
   furniture rather than as the answer to "I cannot get in". Letting main size to its content puts
   it directly under the card, and .chrome-page's 100vh still paints the rest of the window.

   BOTH HALVES ARE NEEDED. Letting main size to its content is not enough on its own: the footer's
   own margin-top:auto then eats the slack and puts it right back on the viewport edge, which is
   what the first attempt at this did. Main stops growing AND the footer stops pushing.

   .chrome-page still carries min-height:100vh, so the window below the footer stays the page's own
   grey rather than going white.

   Scoped to the login pages: everywhere else a footer that climbs up a short page is the bug this
   rule would cause. */
.sp-login-page .chrome-main { flex: 0 0 auto; }
.sp-login-page .chrome-footer { margin-top: 0; }

/* AND THE GAP ITSELF, WHICH IS NOT THE FOOTER'S TO GIVE.

   Both rules above put the help line under the card instead of on the viewport edge, and it was
   still too far away: 52px of clear space, of which only 12 belonged to the footer. The other 40
   are .sp-login-wrap's own bottom padding -- the frame around the card -- so trimming the footer
   further could never have closed it, and trimming it to nothing would have crowded the words
   against the card without moving the block.

   24px total, which reads as attached to the card rather than floating between it and the bottom of
   the window.

   NOT THE DIALOG. The same wrap frames the card when the Panel opens this in a modal, where the
   padding is the FRAME and has to stay equal on all four sides -- ConnectDialogFitTests measures
   exactly that, and an asymmetric bottom is the failure it is looking for. Embedded implies dialog,
   so :not() covers both.

   NOT MOBILE EITHER: under 600px the wrap has no padding at all, the card is full-bleed, and the
   footer's own 12px is the whole gap. The min-width keeps this rule out of that. */
@media only screen and (min-width: 601px) {
    .sp-login-page .sp-login-wrap:not(.sp-login-wrap--dialog) { padding-bottom: 12px; }
}

/* One line on desktop, two on mobile — the Panel's own rule. */
.sp-footer-br { display: none; }

@media screen and (max-width: 500px) {
    .chrome-header__brand img { width: 92px; }
}

.sp-login-wrap {
    display: flex;
    justify-content: center;
    padding: 40px 16px;
}

.sp-login-card {
    display: flex;
    width: 100%;
    max-width: 860px;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 4px 28px rgba(0, 0, 0, 0.13);
    background: var(--surface);
}

/* ONE HEIGHT FOR THE WHOLE JOURNEY, AND THE FRONT DOOR SETS IT.

   The card is as tall as its taller column, and on the front door that is the dark one: three
   promises, a video thumbnail and a "New to X?" footer. Every screen past it wears the compact
   column -- since the heading came off it, just the words SUPPLIER PANEL -- so the card collapsed
   to whatever the form on the right needed. Measured across the migration: 732, then 394, then
   334. A supplier walks those in about fifteen seconds and the card resizing under them at every
   step is the most visible thing on the page.

   A MEASURED NUMBER, NOT A DERIVED ONE, because there is nothing to derive it from: it is the
   result of two columns, their padding, a flex rule and what wraps at this width. CardHeightTests
   measures the front door in a browser and fails if any screen in the flow disagrees with it, so
   changing the pitch in the left column shows up as that test naming the new number rather than as
   a card that quietly starts resizing again.

   NOT ON THE DIALOG, which is one column at 520px inside the Panel's own modal and has no dark
   side to match -- see ConnectDialogFitTests, which fails the moment this reaches it. Not below
   601px either, where the columns stack into bands and a floor would only add empty dark. */
.sp-login-card:not(.sp-login-card--dialog) { min-height: var(--sp-card-min); }

/* DIALOG MODE — the right-hand column on its own, shaped like a modal.

   Same card, same radius, same shadow as the two-column version, so it reads as the thing the
   sign-in pages already are rather than a second visual language. Narrower because a single
   column at 860px is a very long line length for a form. */
.sp-login-card--dialog {
    max-width: 520px;
    position: relative;
}

/* The close control. 44px is the accessible minimum target; the glyph inside is smaller so it
   does not read as heavy. Sits over the card's own padding rather than displacing content, which
   is why .sp-login-right gets extra top padding in dialog mode. */
.sp-dialog-close {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 2;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    color: var(--muted);
    font-size: 16px;
    line-height: 1;
    text-decoration: none;
}

.sp-dialog-close:hover,
.sp-dialog-close:focus-visible { background: var(--stone-bgcolor); color: var(--ink); }

/* The close control sits at top:24px right:28px, which is exactly where the logo is. Dropped
   rather than moved: inside a dialog the Panel has already said whose it is. */
.sp-login-card--dialog .sp-login-logo { display: none; }

.sp-login-card--dialog .sp-login-right { width: 100%; padding-top: 44px; }  /* no logo here, so no clearance needed */
.sp-login-card--dialog .sp-login-form-wrap { max-width: none; margin-top: 0; }

/* EMBEDDED — this document is inside the Panel's modal.

   The Panel already draws the dialog: its own rounded panel, shadow, backdrop and close button.
   So everything this page would use to look like a dialog has to come off, or it renders as a
   card inside a card with two sets of corners and a double drop shadow.

   Background transparent rather than white for the same reason: the Panel's modal owns the
   surface, and painting over it leaves a pale rectangle wherever the two disagree by a pixel. */
.sp-embed { background: transparent; min-height: 0; }
.sp-embed .sp-login-wrap { padding: 0; display: block; }
.sp-embed .sp-login-card,
.sp-embed .sp-login-card--dialog {
    max-width: none;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
}

/* EVEN ON ALL FOUR SIDES, because the frame has no padding of its own.

   The Panel sets its dialog body to `padding: 0` so the frame fills it edge to edge, which makes
   the gap declared here the ONLY gap between this text and the dialog's border. It was
   `4px 28px 12px`: a hairline above the heading, a different gap below the last line, and a
   dialog that read as though the page inside it had been cropped rather than placed.

   The close control is the parent's, so none of the top padding is doing the job it does
   standalone -- there is nothing to clear. 28px on every side, the same 28px .sp-login-right uses
   as a dialog of its own, so embedded and standalone frame the card identically. */
.sp-embed .sp-login-right { padding: 28px; }
.sp-embed .chrome-main { flex: 0 1 auto; }

/* THE CODE SCREEN, EMBEDDED. Centred, and only here.

   Standalone this is a page: the card is 520px, the text is left-aligned like every other screen
   in the flow, and it should stay that way. Inside the Panel's dialog the same markup is the whole
   of a ~445px surface with nothing else on it, and a left-aligned column with 130px of empty space
   down one side reads as a fragment of a page rather than a dialog.

   320px because that is what the code row actually measures -- six 44px boxes and five 10px gaps
   is 314 -- so the boxes set the width of the block and everything else lines up to them instead
   of to an arbitrary number.

   Scoped twice on purpose: .sp-embed keeps it away from the standalone page, .sp-code-screen keeps
   it away from the sign-in methods list, which is embedded too and is a list, not a prompt. */
.sp-embed .sp-code-screen {
    max-width: 320px;
    margin: 0 auto;
    text-align: center;
}

.sp-embed .sp-code-screen .sp-otp { justify-content: center; }

/* The label sits above the boxes and is part of the same centred block. */
.sp-embed .sp-code-screen .sp-field label { display: block; text-align: center; }

/* LEFT PANEL */
.sp-login-left {
    position: relative;
    overflow: hidden;
    background: #3a3a3a;
    color: var(--on-dark);
    width: 50%;
    /* 28px at the foot rather than 36. This column decides the card's height on the sign-in screen
       -- it is the taller of the two and always will be -- so every gap in it is a gap in the card,
       and the one under the last line is what reads as the panel not having finished. */
    padding: 36px 50px 28px;
    display: flex;
    flex-direction: column;
}

.sp-login-left::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 150px;
    height: 130px;
    background-image: radial-gradient(var(--bgcolor-theme) 1.4px, transparent 1.5px);
    background-size: 15px 15px;
    opacity: 0.55;
    -webkit-mask-image: linear-gradient(to top right, #000, transparent 65%);
    mask-image: linear-gradient(to top right, #000, transparent 65%);
    pointer-events: none;
}

.sp-login-left-body { position: relative; z-index: 1; }

.sp-login-label {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: 1.2px;
    color: var(--bgcolor-theme);
    margin-bottom: 12px;
    /* 58, WHICH IS THE 48 THE HEADER ROW USED TO CARRY PLUS THIS LABEL'S OWN 10.
       That row existed to sit this label beside the mobile "How to succeed" pill; the pill went with
       the video, one child is not a row, and the number it held has to land somewhere or the whole
       dark column rides up by 48px. The mobile override below replaces it outright, as it always
       did. */
    margin-top: 58px;
}

/* THE COMPANY, UNDER THE PRODUCT. Bigger than the label above it and a step lighter, which is the
   opposite treatment on purpose: "SUPPLIER PANEL" is a small tracked-out label saying which product
   this is, and the name is the content it labels. Size carries the name, weight carries the label,
   so neither has to shout the other down about which line to read first.

   Plain light ink rather than the accent. The label is themed because it is the brand; the
   storefront's name is a fact about this visit, and putting it in the portal's colour too would
   make the pair read as one two-line logo.

   overflow-wrap, because this is somebody's registered business name in a narrow dark column and
   the long ones are long. It wraps rather than running under the edge of the card. */
.sp-login-company {
    font-size: 28px;
    font-weight: 600;
    line-height: 1.25;
    color: var(--on-dark);
    margin-bottom: 12px;
    overflow-wrap: break-word;
}

.sp-login-heading {
    font-size: 28px;
    font-weight: 800;
    line-height: 1.25;
    margin: 15px 0 24px;
}

.sp-login-bullets {
    list-style: none;
    padding: 0;
    margin: 16px 0 8px;
    display: flex;
    flex-direction: column;
}

.sp-login-bullets li {
    display: flex;
    align-items: center;
    gap: 14px;
    font-size: 15px;
    font-weight: 500;
    /* 11px a side: three bullets and their two rules cost 22px less than at 14, and the divider
       still reads as a divider rather than as crowding. */
    padding: 11px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}

.sp-login-bullets li:first-child { padding-top: 0; }
.sp-login-bullets li:last-child { border-bottom: none; padding-bottom: 0; }

.sp-bullet-badge {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    color: #cdd4db;
}

.sp-bullet-svg { width: 32px; height: 32px; }
.sp-bullet-svg-accent { stroke: var(--bgcolor-theme); }

.sp-bullet-text { display: flex; flex-direction: column; }
.sp-bullet-title { font-weight: 700; }
.sp-bullet-subtitle { font-size: 13px; font-weight: 400; color: #9daab5; }

.sp-login-new-user {
    position: relative;
    z-index: 1;
    font-size: 15px;
    /* auto, NOT A GAP, so this block is measured from the BOTTOM of the column.
       Everything above it -- the label, the heading, three bullets -- can grow, wrap or gain a line
       without moving it, which is what a fixed top margin could not survive once the video came out
       and stopped setting the column's height. */
    margin-top: auto;
    margin-bottom: var(--sp-left-footer-lift);
}

.sp-login-new-user strong { display: block; margin-bottom: 2px; }

.sp-drive-sale { color: var(--bgcolor-theme) !important; text-decoration: underline !important; }

/* Desktop: only the left-panel copy shows. The mobile-only duplicate
   (moved into the right panel/form area, matching OMA's real page) takes
   over once the left panel's version hides at the 600px breakpoint. */
.sp-login-new-user--mobile { display: none; }

/* RIGHT PANEL */
.sp-login-right {
    background: var(--surface);
    width: 50%;
    /* 36px 40px, which is .sp-login-right's own measure in the Panel's site.css. Both files style
       the same card and the two have to agree; where they differ, the Panel is the original --
       except at the foot, which is 28 here for the same reason the left column's is. */
    padding: 36px 40px 28px;
    position: relative;
    display: flex;
    flex-direction: column;
}

/* THE BRAND, BACK INSIDE THE CARD.

   It was here, then it moved to a header band above the card, then the band was dropped and this
   came back with it. The band was the Panel's SIGNED-IN chrome, and a sign-in screen wearing it
   looked like a page of an application nobody had signed into yet; the card on a plain page is what
   the Panel's own login screen does. Top-right, so it balances the heading rather than competing
   with it. */
.sp-login-logo { position: absolute; top: 24px; right: 28px; }
.sp-login-logo img { height: 52px; width: auto; }

/* The EasyAsset wordmark is a wide horizontal lockup rather than a stacked mark like IS/MS/HH, so
   at the same 52px height it reads as oversized. OMA's theme-ea.css trims it to 34px; this matches. */
.sp-theme-easyasset .sp-login-logo img { height: 34px; }

/* 60px FROM THE TOP, WHICH IS ALSO WHAT CLEARS THE LOGO.
   36px of column padding plus this puts the heading 96px down, and the mark ends at 76px (24 top +
   52 tall). The Panel's own site.css carries the same pair, and matching it is the point: two
   stylesheets drawing one card.

   It was briefly centred instead, to close the white gap under the provider buttons on the sign-in
   screen. That gap is in the Panel too -- the left column is taller than a login form and always
   will be -- and centring it cost more than it bought: on a SHORT card, the chooser, the heading
   rose under the wordmark and was struck through by it. */
/* 60, THE PANEL'S OWN NUMBER, AND THE REASON FOR DIFFERING FROM IT IS GONE.

   This was 48 -- the one place this card was deliberately tighter than the Panel's. The argument
   was that on the chooser the right column IS the card's height, so twelve pixels here were twelve
   off the empty stretch of dark beside it. That stopped being true when the card got a floor
   (--sp-card-min): the left column or the floor decides the height on every screen now, and the
   twelve pixels bought nothing while costing the only thing they touched.

   What they cost is measurable. The mark ends 76px down (24 top + 52 tall). At 36 + 48 the heading
   started at 84, eight pixels under a 52px logo -- which reads as the two colliding. At 36 + 60 it
   starts at 96, the same 20px clearance the Panel's company picker has, and the two cards a
   supplier sees back to back finally agree about it.

   MobileLayoutTests measures the overlap rather than trusting it, and more clearance can only
   help there. */
.sp-login-form-wrap { max-width: 360px; margin: 60px 0 0; width: 100%; }

/* THE BOTTOM OF THE CARD IS THE COLUMN'S PADDING AND NOTHING ELSE.

   Every screen ended on whatever its last block happened to carry, so the gap under the content was
   set by the page rather than by the card: a .sp-footer-hint paragraph kept the user agent's own
   1em (13px), .sp-expando added its 8px, a bare </form> added nothing. Measured across the pages
   that gave 36, 40, 44 and 49px of bottom gap against a padding-top of 36px on every one of them.
   Same rule and same reason as .sp-expando__panel > :last-child further down.

   The second selector is for the screens whose whole body sits inside one wrapper -- EnterCode in
   .sp-code-screen, and any form whose submit is the last thing on the page -- where the trailing
   margin belongs to a grandchild and the first selector never reaches it. */
.sp-login-form-wrap > :last-child,
.sp-login-form-wrap > :last-child > :last-child { margin-bottom: 0; }

/* TWO GAPS, AND THEY MEAN DIFFERENT THINGS. 8px pairs two things that read as one -- a heading
   and its sentence, a caption and the row it labels, a button and its alternative. 20px separates
   one of those units from the next. Every screen in the card uses those two numbers and no others.

   It was three numbers for the pairing job: 4 here, 8 under a button, 10 under the providers
   caption. Each was defensible alone and together they read as drift, because nothing on the page
   tells you which of the three a given pair is supposed to be. One number cannot drift. */
.sp-login-welcome { font-size: 26px; font-weight: 700; margin: 0 0 4px; }
.sp-login-subtitle { color: var(--muted); font-size: 14px; margin: 0 0 20px; line-height: 1.5; }

/* Adjacent margins collapse to the LARGER of the two, and both sides are 20 now, so this yields
   to nothing and the gap is 20 either way. Kept as the one place that STATES the collapse, because
   the three selectors above only work by relying on it. */
.sp-login-subtitle:has(+ .sp-social-row),
.sp-validation-summary:has(+ .sp-social-row) { margin-bottom: 0; }

/* THE PAIRING GAP IS ONLY FOR THE THING IT PAIRS WITH.
   Where a screen has no subtitle, whatever comes next inherits the 4px anyway -- and 4px is the
   distance that says "these two are one thing". On the chooser that is the promises box, which
   sat almost touching the heading while everything below it on the same screen was 20px apart.
   Measured, not eyeballed: 4 above the box, 20 below it.
   Margins collapse between adjacent siblings here, so this resolves to 20 rather than 24. */
.sp-login-welcome + *:not(.sp-login-subtitle) { margin-top: 20px; }

/* THE SAME TWO NUMBERS INSIDE A FORM AS OUTSIDE ONE. A label and its box are one thing, so 8px;
   two fields are two things, so 20px.

   BOTH HAD TO MOVE TOGETHER. It was 4 and 12, and lifting only the label to 8 would have left the
   pair 8 apart inside and 12 apart from the next field -- close enough that the eye stops grouping
   them, which is the opposite of what a pairing gap is for. The ratio is what does the work, not
   the number: 8 against 20 groups exactly as 4 against 12 did, on the scale the rest of the card
   already uses. */
.sp-field { margin-bottom: 20px; position: relative; }
.sp-field label { display: block; font-weight: 600; font-size: 13px; margin-bottom: 8px; }

/* OMA's own value — a plain grey, not the theme accent (that's reserved
   for .sp-drive-sale-style calls to action, per OMA's real CSS). */
.sp-forgot-link {
    color: #555;
    font-size: 12px;
    text-decoration: underline;
}

/* THE PLAIN FIELD, SIZED LIKE THE FLOATING ONE IT SITS BESIDE.
   It was 34px against the 52px of .sp-field--float, and the two appear in the same flow one screen
   apart -- an address box you could comfortably hit, then a password box noticeably smaller on the
   very next page. Nothing was wrong with either on its own, which is why it survived: it is only
   visible as a difference.

   44px rather than 52: this field carries its label ABOVE it, so it does not need the room the
   floating one reserves for a caption inside. 44 is the smallest comfortable target, and the radius
   and fill now match, so the two read as the same control at two sizes rather than two controls.

   Found by measuring, not by looking. A screenshot of either screen on its own looks fine. */
.sp-field input[type="text"],
.sp-field input[type="email"],
.sp-field input[type="password"] {
    width: 100%;
    min-height: 44px;
    padding: 11px 13px;
    border: 1px solid #e4e4e4;
    border-radius: 8px;
    background: var(--field);
    font-size: 15px;
}

.sp-field input:focus {
    outline: none;
    border-color: var(--bgcolor-theme);
    box-shadow: 0 0 0 0.15rem rgba(0, 0, 0, 0.08);
}

/* -- A FIELD WHOSE LABEL SITS INSIDE IT ---------------------------------------------------------

   Scoped to .sp-field--float and NOT applied to .sp-field itself, deliberately. That base class is
   on ten other screens -- the code boxes, set-password, connect-company -- and one of them already
   overrides the label to centre it. Restyling the base would silently redesign all of them to fix
   one, which is how a small change becomes an unreviewable one.

   The label is a real <label> positioned over the input, not a placeholder. A placeholder is gone
   the moment anybody types, so the field loses its name exactly when somebody tabs back to check
   what they put there. This keeps the name and still reads as a placeholder while empty.

   It lifts on VALUE, not on focus: focusing an empty box leaves the label where it is, which is
   the state the browser's own autofill dropdown appears over. Lifting on focus would animate the
   label out from under that dropdown for no reason. */
.sp-field--float { position: relative; margin-bottom: 14px; }

.sp-field--float > input[type="text"],
.sp-field--float > input[type="email"],
.sp-field--float > input[type="password"] {
    width: 100%;
    height: 52px;
    /* Top-heavy: the lifted label occupies the space above the value. 52/20/4 is as tight as the
       two will sit without the caption touching the value -- at 56px the caret sat visibly low in
       the box, with a band of dead fill under it. */
    padding: 20px 13px 4px;
    /* A real border now, not a transparent one. It is what gives the box an edge to change colour
       on focus instead of the fill having to do it, and it is the part of the outlined reference
       that survives here. */
    border: 1px solid #e4e4e4;
    border-radius: 8px;
    background: var(--field);
    font: inherit;
    font-size: 15px;
    color: var(--ink);
    transition: border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
}

.sp-field--float > input:hover { border-color: #d2d2d2; }

/* One accent stroke, thickened with a ring rather than by changing border-width -- growing the
   border to 2px reflows the text inside it by a pixel on every focus. */
.sp-field--float > input:focus {
    outline: none;
    border-color: var(--bgcolor-theme);
    background: var(--surface);
    box-shadow: 0 0 0 1px var(--bgcolor-theme);
}

.sp-field--float > label {
    position: absolute;
    left: 14px;
    top: 50%;
    margin: 0;
    /* 14px, not 15. At the value's own size the resting label read as text somebody had already
       typed rather than as the name of an empty field. */
    font-size: 14px;
    font-weight: 400;
    color: #949494;
    white-space: nowrap;
    /* Clicks belong to the input underneath, not to the text sitting on top of it. */
    pointer-events: none;
    transform: translateY(-50%);
    /* Left CENTRE, so the scale below shrinks the label towards the left edge without moving the
       line it sits on. */
    transform-origin: left center;
    transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1), color 160ms ease;
}

/* LIFTED -- INSIDE THE BOX, NOT ASTRIDE THE BORDER.
   The outlined reference notches its label into the top border, which works because its field has
   no fill: the label paints the CARD colour over the line and the break looks clean. With a filled
   box the line separates two different colours, so a notch can only match one of them and shows a
   seam against the other. Lifting it inside keeps the fill and needs no trick at all.

   On :focus as well as on value: the caret sits at the same left edge the label occupies, so a
   label that waited for the first keystroke would sit under the cursor of an empty focused box.

   :not(:placeholder-shown) is why every input here carries placeholder=" " -- with no placeholder
   attribute the selector never matches. Autofill counts as a value, so it lifts for that too.

   scale() and not font-size: font-size relayouts the field on every keystroke. */
.sp-field--float > input:focus + label,
.sp-field--float > input:not(:placeholder-shown) + label,
.sp-field--float > input:-webkit-autofill + label {
    transform: translateY(calc(-50% - 12px)) scale(0.78);
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--muted);
}

.sp-field--float > input:focus + label { color: var(--ink); }

/* Chrome paints its own background on an autofilled field, which would replace the fill and leave
   one field looking unlike its neighbour. An inset shadow is the only thing that overrides it --
   background-color is ignored here. */
.sp-field--float > input:-webkit-autofill,
.sp-field--float > input:-webkit-autofill:hover,
.sp-field--float > input:-webkit-autofill:focus {
    -webkit-box-shadow: inset 0 0 0 100px var(--field);
    -webkit-text-fill-color: var(--ink);
}

/* Room for the reveal button, so a long password never runs underneath it. An eye needs 48px
   where the old "Show"/"Hide" text needed 78px, and the field keeps the difference. */
.sp-field--float > input[type="password"] { padding-right: 48px; }

/* SHOW / HIDE.
   Rendered hidden and unhidden by the script: without JS it would be a button that does nothing,
   which is worse than no button at all. */
.sp-field__reveal {
    position: absolute;
    right: 8px;
    top: 26px;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 50%;
    background: transparent;
    color: #6a6a6a;
    cursor: pointer;
    transition: background-color 160ms ease, color 160ms ease;
}

.sp-field__reveal:hover { background: #ececec; color: var(--ink); }
.sp-field__reveal:focus-visible { outline: 2px solid var(--bgcolor-theme); outline-offset: 2px; }

.sp-field__eye { width: 18px; height: 18px; display: block; }

/* One attribute drives both the icon and what a screen reader is told, so the two cannot drift
   apart. Pressed means the password is visible, so the crossed-out eye is what you press to
   put it back. */
.sp-field__reveal[aria-pressed="false"] .sp-field__eye--hide,
.sp-field__reveal[aria-pressed="true"] .sp-field__eye--show { display: none; }

.sp-hint { font-size: 12px; color: var(--muted); margin-top: 8px; }

/* REQUIRED, MARKED ON THE FIELD RATHER THAN EXPLAINED IN A LEGEND.
   Only the screens where a field is genuinely mandatory carry it, so the mark means something --
   a form where every label wears an asterisk has told the reader nothing.

   Inherits the label's own colour and size, including the shrink the floating label applies when
   the box has a value, so it travels with the word it belongs to instead of sitting at a fixed size
   beside a caption that moved. */
.sp-req { color: var(--danger); margin-left: 2px; }

/* accent-color, so the tick takes the portal's colour instead of the browser's default blue --
   which was the one control on the page that stayed blue on all four brands. */
.sp-checkbox { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; font-size: 13px; }
.sp-checkbox input[type="checkbox"] { accent-color: var(--bgcolor-theme); width: 16px; height: 16px; }

/* "Keep me signed in" and "Forgot password?" share one row. The link used to sit up beside
   the Password label, which put the two things a person weighs at that moment three rows
   apart and left a ragged gap under the field. */
.sp-checkbox-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 20px;
}

.sp-checkbox-row .sp-checkbox { margin-bottom: 0; }

/* Buttons carry their own bottom margin because they stack: a form per
   action means no parent is in a position to space them, and without this
   Continue and "Send it again" sit welded together. */
/* ONE BUTTON, TWO FILLS.
   These drifted: primary was 15px text on a 6px radius, ghost was 14px on 8px, the row actions
   were 13px on a 16px pill, and sign-out was its own thing again. Four sizes and three radii for
   one control. Everything below is now the same box -- 40px tall, 16px of side padding, a full
   pill, 14px/600 -- and the only difference between them is what fills it. */
.sp-btn-primary,
.sp-btn-ghost {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    /* 44px, which is the number the inputs directly above these already use and the smallest
       target the platform guidelines ask for (WCAG 2.5.5, and Apple's and Google's own). It was
       40 -- four short, and four short of the field it sits under, so a form read as two slightly
       different control heights stacked. Mobile was already 48. */
    min-height: 44px;
    padding: 0 16px;
    border: 1px solid transparent;
    /* A PILL, AND ONLY ON THESE. The phone already drew them this way and the desktop did not, so
       one product had two shapes for the same button depending on the width of the screen. The
       provider rows above keep their 8px corner deliberately: the pill is what separates the action
       this card is asking for from the three doors offered beside it. */
    border-radius: 999px;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    margin-bottom: 8px;
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, transform 160ms cubic-bezier(0.23, 1, 0.32, 1);
}

.sp-btn-primary {
    background: var(--bgcolor-theme);
    color: var(--textcolorcontrast-theme);
}

.sp-btn-primary:hover { opacity: .85; }
.sp-btn-primary:active,
.sp-btn-ghost:active { transform: scale(0.99); }

.sp-btn-primary:hover { opacity: 0.85; }

.sp-social-row {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    margin-bottom: 0;
}

/* THE OPTICAL CORRECTION WENT WITH THE CIRCLES.

   These gaps were 14px rather than 20, because a circle touches its neighbour at a single point and
   curves away everywhere else, so 20px under a 65px ring read as roughly 26. The buttons have flat
   edges now and the eye measures them the same way it measures every other block on the card, so
   the plain separation gap is correct again and there is nothing left to correct.

   The heading rule stays only to name .sp-login-welcome: its own "whatever follows me sits 20px
   down" rule is more specific than .sp-social-row alone, and without this the three selectors here
   would disagree about which number applies. */
.sp-login-welcome + .sp-social-row,
.sp-login-subtitle + .sp-social-row,
.sp-validation-summary + .sp-social-row { margin-top: 20px; }

/* The forms are only there to carry the antiforgery token, so they must not add a box of their own
   between the row and its buttons -- and in a column they have to be as wide as the button inside,
   or a shrink-to-fit form would size each row to its own label. */
.sp-social-row form { margin: 0; display: block; width: 100%; }
/* login.js hides our own Google button once Google has drawn its rendered one. `display: flex`
   above outranks the user agent's `[hidden] { display: none }`, so without this the attribute is
   set and nothing happens -- which is the two Google buttons in the row. */
.sp-social-row form[hidden] { display: none; }

/* Marks on the buttons under the form. One size, because they are the same size of button.
   .sp-btn-primary and .sp-btn-ghost are named here for any mark that lands inside one -- without
   them it falls back to .sp-social-icon's 18px and sits a pixel proud of every other button mark
   on the card. (The chooser's "Continue with Email" is no longer one of those: it wears
   .sp-social-btn and takes the provider rows' own 18px, which is the point of it.) */
.sp-btn-icon,
.sp-btn-primary .sp-social-icon,
.sp-btn-ghost .sp-social-icon { width: 17px; height: 17px; flex: none; }


.sp-social-btn {
    width: 100%;
    max-width: var(--sp-social-width);
    margin-left: auto;
    margin-right: auto;
    /* Same 44 as every other button. */
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 0 12px;
    border: 1px solid #747775;
    /* 8px, THE SAME CORNER AS EVERY OTHER BUTTON ON THIS CARD.

       It was 20 -- a pill -- because Google's button spec asks for one, and that mattered while
       Google's OWN rendered button sat in this very stack: a pill beside a rectangle reads as two
       products, and the vendor's button was the one we could not restyle. That button is gone (see
       _ExternalProviders), so the constraint went with it, and what is left is three of our own
       buttons standing directly above a Sign in button at 8px. The odd one out was ours. */
    border-radius: 8px;
    background: var(--surface);
    /* --ink, not Google's #1F1F1F. This shape follows Google's button spec because it once sat
       beside Google's own rendered button and had to match it; that button is gone, and this one
       is now a connect control on the manage screen wearing a slightly different black from every
       other word on the page. One black. */
    color: var(--ink);
    /* The chooser's "Continue with Email" is an <a> on the migration route and a <button> on the
       invited one, so the underline has to be said off or the same control is drawn two ways. */
    text-decoration: none;
    font-family: "Google Sans", Roboto, "Segoe UI", -apple-system, system-ui, sans-serif;
    font-weight: 500;
    font-size: 14px;
    line-height: 20px;
    cursor: pointer;
    margin-bottom: 8px;
    transition: background-color 120ms ease, box-shadow 120ms ease;
}

/* IN THE COLUMN IT IS AS WIDE AS THE FIELDS, AND ITS MARK IS ON THE LEFT.

   The 320px cap and the auto margins are for the manage screen, where these sit in a narrower list.
   Here they stand directly above an email field at the form column's full 360px, and a button
   centred 20px inside the box beneath it reads as a mistake rather than as a choice.

   THE MARK IS PINNED LEFT AND THE WORDS ARE CENTRED, which flex alone cannot do.

   Laying the two out in a row gives one or the other: justify-content:center centres the PAIR, so
   three labels of different lengths put the three marks at three different x positions, and a
   column of three is read down its left edge. justify-content:flex-start lines the marks up and
   leaves the words ragged against a 360px button.

   Taking the mark out of the flow settles it. Absolutely positioned it no longer counts toward the
   centring, so the label centres against the whole button while the mark stays on 18px down the
   stack. The 18px padding stays as the floor a long label would wrap against.

   margin-bottom off because .sp-social-row's gap owns the spacing now; leaving both gives 16px
   between rows and 8px of dead space under the last one. */
.sp-social-row .sp-social-btn {
    position: relative;
    max-width: none;
    margin: 0;
    justify-content: flex-start;
    /* 48 ON THE LEFT: the mark sits at 18 and is 18 wide, so this is it plus 12 of air.
       A column of three is read down its left edge, and centring the words put three labels of
       three different lengths at three different x positions -- "Continue with Microsoft", the
       longest, started 7px left of "Continue with Google". Both edges are straight now: the marks
       on 18, the words on 48. LandingPageLayoutTests measures the spread rather than trusting it. */
    padding: 0 18px 0 48px;
}

.sp-social-row .sp-social-btn .sp-social-icon {
    position: absolute;
    left: 18px;
}

.sp-social-btn:hover { background: var(--surface-hover); box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3); }
.sp-social-btn:active { background: #f1f3f4; }
/* Keyboard focus has to be visible on a login page — :focus-visible keeps it
   off mouse clicks. */
.sp-social-btn:focus-visible { outline: 2px solid #0b57d0; outline-offset: 2px; }

/* No .sp-google-button any more: Google draws nothing here. It is one of our own circles, the
   same 52px as the other two, which is what the mismatch this used to scale around was. */

/* object-fit, because three of these are now <img> rather than <svg> (see _ProviderIcon). A raster
   mark handed a width and a height it does not share the aspect of would otherwise be stretched to
   fill them; contain fits it inside instead and leaves the shape alone. Harmless on the two that
   are still drawn. */
.sp-social-icon { width: 18px; height: 18px; flex-shrink: 0; object-fit: contain; }

/* A RULE THROUGH THE ROW, WITH THE WORDS SITTING IN THE GAP.
   Centred text on its own does not divide anything -- it reads as a caption belonging to whichever
   block it happens to sit nearer. The lines are what make it a boundary between two ways in.

   Flex with two growing pseudo-elements rather than a border on the container and a background
   behind the text: the second needs the text's background to match whatever is behind it, which
   breaks the moment this sits on a tinted card. This does not care what is underneath. */
.sp-or {
    display: flex;
    align-items: center;
    gap: 12px;
    /* THE WORD OUTRANKS THE RULE. At 12px in --muted it was quieter than the two lines either
       side of it, so the divider read as a rule that happened to have a gap in it rather than as a
       choice between two ways in. The lines stay hairline; the word is what changed. */
    color: var(--ink);
    font-size: 17px;
    font-weight: 600;
    /* 20px, the one separation gap. It was 16, which put this divider 4px tighter than every
       other block boundary on the same screen -- close enough to look like a mistake rather
       than a decision, and the only place the eye had to measure. */
    margin: 20px 0;
}

/* 2px, not a hairline. The word either side of it is 17px now and the buttons under it are 75;
   a one-pixel rule between them read as the edge of something rather than as a divider drawn on
   purpose. */
.sp-or::before,
.sp-or::after {
    content: "";
    flex: 1;
    height: 2px;
    background: var(--line);
}

.sp-btn-ghost {
    background: var(--surface);
    border-color: var(--line);
    color: var(--ink);
}

.sp-btn-ghost:hover { background: var(--surface-hover); border-color: var(--line-hover); }

/* Both of these are drawn as anchors in places -- a step that links onward rather than submitting.
   Everything else about the two classes already works on an <a> (display:flex, the border, the
   fill); the underline is the one thing a link brings that a button does not. */
a.sp-btn-ghost,
a.sp-btn-primary { text-decoration: none; }

.sp-footer-hint { margin-top: 20px; font-size: 13px; color: var(--muted); text-align: center; }

/* .sp-login-footer removed: it was dark text on the page background, which is the
   inverse of the Panel's footer. The band now comes from .chrome-footer above, so both
   applications render one footer instead of two that disagree. */

.sp-validation-summary {
    background: #fdeaea;
    color: var(--danger);
    border: 1px solid #f5c6cb;
    border-radius: 8px;
    padding: 12px 16px;
    margin-bottom: 16px;
    font-size: 14px;
}

/* asp-validation-summary renders an empty div (validation-summary-valid)
   even with no errors — without this it shows as an empty colored bar
   on every page load, not just after a failed submit. */
.sp-validation-summary.validation-summary-valid { display: none; }

.sp-notice {
    border-radius: 8px;
    padding: 12px 16px;
    /* Same reason as .sp-or: one separation gap, used everywhere. */
    margin: 20px 0;
    font-size: 14px;
}

.sp-notice.good { background: var(--stone-bgcolor); }
.sp-notice.calm { background: var(--stone-bgcolor); }

/* Its own heading and its own sentence: a pair, on the pairing gap. */
.sp-notice strong { display: block; margin-bottom: 8px; }

/* The code row needs air beneath it — it sat flush against Continue, so the
   six boxes read as part of the button rather than a field above it. */
.sp-otp {
    display: flex;
    gap: 10px;
    margin-bottom: 16px;
}

.sp-otp input {
    width: 44px;
    height: 52px;
    text-align: center;
    font-size: 22px;
    border: 1px solid var(--line);
    border-radius: 8px;
}

.sp-otp input:focus { outline: 2px solid var(--bgcolor-theme); }

/* CONNECTED ACCOUNT CARDS.

   Grid rather than flex: the mark, the text and the Disconnect control each get their own column,
   so a long provider name cannot push the control out of alignment between rows. */
.sp-connected { list-style: none; margin: 12px 0 16px; padding: 0; }

/* A FIXED FIRST COLUMN, not `auto`.
   Password and Google carry a provider mark; passkeys and two-step do not. With `auto` the column
   collapsed on the rows without one, so their names started 32px left of the names above them and
   the list read as two lists. 20px reserved whether or not anything sits in it. */
.sp-connected__item {
    display: grid;
    grid-template-columns: 20px 1fr auto;
    align-items: center;
    gap: 14px;
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 14px 16px;
    margin-bottom: 8px;
    background: var(--surface);
    transition: border-color 160ms ease;
}

.sp-connected__item:hover { border-color: var(--line-hover); }

.sp-connected__mark { display: flex; align-items: center; justify-content: center; }
.sp-connected__mark svg,
.sp-connected__mark img { width: 20px; height: 20px; }

.sp-connected__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sp-connected__name { font-weight: 600; font-size: 15px; line-height: 1.3; }

/* The card already says which provider; this line says what that buys you. Quiet, because it is
   the same answer on every card. */
.sp-connected__meta { font-size: 13px; line-height: 1.4; color: var(--muted); }

/* The same box as .sp-btn-ghost, sized by its label instead of the column. Nothing else differs:
   same height, same padding, same radius, same type. */
.sp-connected__action,
.sp-connected__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 44, like every other button. */
    min-height: 44px;
    padding: 0 16px;
    border: 1px solid var(--line);
    /* Follows .sp-btn-primary to the pill. These two are not sign-in buttons and nobody asked for
       them, but "every button is the same box" is a property this product does have and
       VisualCheckTests measures -- and a 999px primary sitting one row above an 8px Disconnect is
       exactly the drift that invariant exists to catch. */
    border-radius: 999px;
    background: var(--surface);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, transform 160ms cubic-bezier(0.23, 1, 0.32, 1);
}

.sp-connected__action:hover { background: var(--surface-hover); border-color: var(--line-hover); color: var(--ink); }
.sp-connected__remove:hover { background: #fdf3f2; border-color: var(--danger); color: var(--danger); }

/* Instant feedback that the press landed. 0.97 is enough to feel and not enough to notice. */
.sp-connected__action:active,
.sp-connected__remove:active { transform: scale(0.97); }

/* NO MARK COLUMN INSIDE A PANEL. The rows nested in an opened row -- a registered passkey and its
   Remove -- carry no provider icon, so the 20px column and its 14px gap were 34px of nothing,
   taken off the only part of the card with words in it. In the embedded dialog that was enough to
   break "Last used 3 Sep 2026" onto four lines, one word each, which is what made the passkey list
   look broken rather than narrow. */
.sp-expando__panel .sp-connected__item { grid-template-columns: 1fr auto; }

/* The list of devices is the panel's content, not a block floating above the button. */
.sp-expando__panel .sp-connected { margin: 0 0 12px; }
.sp-expando__panel .sp-connected:empty { margin: 0; }

/* "Connect Social accounts to sign in", closed by default. */
.sp-connect-more { margin-bottom: 16px; }

/* The native ▸ marker is the other dated thing on this screen, and it cannot be styled. Hidden on
   both engines, replaced with a chevron that rotates -- transform only, 200ms, ease-out, so it is
   GPU-cheap and reads as the panel opening rather than a glyph swapping. */
.sp-connect-more > summary {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    padding: 10px 0;
    list-style: none;
}

.sp-connect-more > summary::-webkit-details-marker { display: none; }

.sp-connect-more[open] > summary { margin-bottom: 8px; }

.sp-connect-more__body .sp-social-btn { margin-bottom: 8px; }

/* TABLET / NARROW DESKTOP — OMA's real page only has one breakpoint (600px),
   so anything between that and where the card actually reaches its 860px
   max-width (a lot of real tablets and resized desktop windows) gets the
   full two-column layout squeezed into ~250px-wide columns — the heading
   wraps to three lines instead of two. Cutoff is 900px, not 860px: the card
   doesn't hit its own max-width until the viewport is noticeably wider than
   860px, since .sp-login-wrap's own 16px+16px padding and the scrollbar
   both eat into it first — measured directly (card was still 815px wide at
   an 862px viewport) rather than assumed. Verified this is a real,
   measurable problem generally, not just a guess: checked actual rendered
   layout at 601–899px viewports against the unmodified OMA CSS before
   adding this. Not present in OMA's own page either — a gap in the source
   being copied, closed here rather than carried forward, since
   "productionized" was the ask, not a literal
   port. */
@media only screen and (max-width: 900px) and (min-width: 601px) {
    /* Still two columns, on tighter padding and a smaller heading, so the front door is shorter
       here than at 1280 and the floor has to follow it. CardHeightTests runs its sweep at 800 as
       well as 1280 for exactly this reason. */
    :root { --sp-card-min: 671px; }

    .sp-login-left { padding: 28px 24px; }
    .sp-login-heading { font-size: 21px; margin: 10px 0 16px; }
    .sp-login-bullets li { padding: 10px 0; }
    .sp-bullet-badge { width: 38px; height: 38px; }
    .sp-bullet-svg { width: 26px; height: 26px; }
    .sp-login-right { padding: 32px 28px; }
    .sp-login-form-wrap { max-width: none; }
}

/* MOBILE — matches OMA's own 600px breakpoint */
@media only screen and (max-width: 600px) {
    .sp-login-wrap { padding: 0; align-items: flex-start; }

    .sp-footer-br { display: block; }

    .sp-login-card { flex-direction: column; border-radius: 0; box-shadow: none; }

    /* The columns are bands now, stacked, so there is no shorter one to pad out to a taller one --
       a floor here would only add empty dark under the last band. */
    .sp-login-card:not(.sp-login-card--dialog) { min-height: 0; }

    /* 20px ON ALL FOUR SIDES. It was 16 / 20 / 0 -- so the band held its gutters but sat its
       content hard against the white card below it, with only the bullet list's own 12px margin
       between the last line and the join. Read as a crop rather than as a band. The sides still
       match .sp-login-right below, which is why 20 is the number the other three take. */
    .sp-login-left { width: 100%; padding: 20px; }
    .sp-login-left::after { display: none; }
    .sp-login-left .sp-login-new-user { display: none; }

    .sp-login-label { font-size: 13px; margin-bottom: 6px; margin-top: 4px; }
    /* Scaled with the label it sits under, and keeps the same gap it has to it. */
    .sp-login-company { font-size: 19px; margin-bottom: 6px; }
    .sp-login-heading { font-size: 20px; margin: 10px 0 12px; }

    /* No bottom margin: the band's own 20px padding is the floor now, and the two together made
       the gap under the last bullet larger than the gutters either side of it. */
    .sp-login-bullets { margin: 8px 0 0; gap: 3px; }

    /* OUT OF THE CORNER ONCE THE CARD STACKS. Absolute positioning belongs to the two-column
       layout; at this width the columns become bands and the mark has to take its own row, or it
       floats over the heading beneath it. The Panel's own three values. */
    .sp-login-logo { position: static; margin-bottom: 12px; text-align: right; }
    .sp-login-logo img { height: 40px; }
    .sp-login-bullets li { font-size: 13px; gap: 9px; padding: 0; border-bottom: none; }
    .sp-bullet-subtitle { display: none; }
    .sp-bullet-title { font-weight: 400; }
    .sp-bullet-badge { width: 26px; height: 26px; }
    .sp-bullet-svg { width: 18px; height: 18px; }

    .sp-login-right { width: 100%; padding: 20px 20px 24px; }

    /* THE UNEVEN GUTTER. .sp-login-form-wrap is max-width: 360px with `margin: 60px 0 0` -- zero
       side margins, so it is pinned LEFT inside a column that is 20px padded on both sides. On any
       phone wider than 400px the form stopped short of the right edge and everything read as
       off-centre, which is the horizontal imbalance rather than either padding being wrong.
       The column already sets the gutter; the cap has nothing left to do here. */
    .sp-login-form-wrap { margin-top: 0; max-width: none; }
    /* THE TYPE SHRINKS, THE RHYTHM DOES NOT. These two used to tighten their gaps as well -- 2px
       under the heading and 12px under the subtitle -- while .sp-or, .sp-notice and
       .sp-footer-hint stayed at 20 on the same screen. So the top of the card was compressed and
       nothing below it was: measured 2 / 12 / 20 / 20 / 20 straight down one column. Smaller text
       is the adaptation a narrow screen needs; a second spacing scale is not. */
    .sp-login-welcome { font-size: 20px; }
    .sp-login-subtitle { font-size: 13px; }


    .sp-login-new-user--mobile { display: block; margin: 20px 0 24px; text-align: center; font-size: 13px; }
    .sp-login-new-user--mobile .sp-drive-sale { color: var(--ink) !important; }

    /* SIGN-IN ONLY: THE LOGO GOES TO THE BOTTOM, under "Learn how to drive sales", so "Welcome!"
       leads the card. The layout renders the logo first, so order moves it without a second copy.
       No bottom margin: the column's 24px padding stays the gap above the footer, as it was under
       the link. 20px above it is the column's rhythm. */
    .sp-login-right:has(.sp-login-new-user--mobile) { display: flex; flex-direction: column; }
    .sp-login-right:has(.sp-login-new-user--mobile) .sp-login-logo { order: 1; margin: 20px 0 0; text-align: center; }

    /* The pill is every width's shape now, so only the SIZE is left here: 48px is a thumb, 44px is
       a mouse. */
    .sp-btn-primary,
    .sp-btn-ghost {
        min-height: 48px;
        font-size: 15px;
    }

    /* Underlined 12px grey is a desktop footnote. On a phone it is a target somebody has to hit,
       so it takes the same weight as the label it sits opposite. */
    .sp-forgot-link { font-size: 13px; font-weight: 600; color: var(--ink); text-decoration: none; }

    .sp-checkbox-row { margin-bottom: 16px; }

    /* Matching the buttons' softer corner. */
    .sp-field--float > input[type="text"],
    .sp-field--float > input[type="email"],
    .sp-field--float > input[type="password"] { border-radius: 10px; }
}

/* ---- MANAGE SCREEN: two named groups, one list, one primary --------------------------------

   Appended rather than woven in above, so this sits clear of the rules being edited around the
   embed padding and the section heading.

   The screen had no hierarchy: two status sentences over three identically-weighted full-width
   boxes, a form, then a fourth identical box that ended the session. A disclosure, a form submit
   and a sign-out all shared one shape, so nothing led and the squint test returned a stack of
   equal rectangles. These rules give it a heading level of its own, a neutral row action to pair
   with Disconnect, and a separated exit. */

/* Smaller than .sp-login-welcome on purpose: these label groups inside the dialog, they do not
   compete with the dialog's own title. 32px above and 8px below is the rhythm the whole screen
   now runs on - generous between groups, tight within one. */
/* A LABEL, NOT A TITLE. At 15px/700 these were close enough to the row names (14px/600) beneath
   them to read as more rows, so the groups did not group anything. Small, tracked and uppercase
   reads as a section marker at a glance and stops competing with the content it names. */
.sp-manage-heading {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 28px 0 10px;
}

/* The first group still needs air under the dialog title. 4px was set when these headings were
   15px/700 and read as part of the title block; as a 12px uppercase label it sits 8px under a 26px
   heading and the two collide. 20px is the same gap the eye reads between the title and the first
   thing it labels. */
.sp-manage-heading--first { margin-top: 20px; }

/* .sp-connected__action is defined with .sp-connected__remove above -- one rule for both, because
   they sit in the same column on adjacent rows and any difference between them shows. */

/* The authenticator secret, for somebody typing it into an app on another device. Monospace and
   spaced out because it is transcribed by hand and l/1 and O/0 have to be told apart. */
/* The enrolment QR. White plate under it because a QR needs light quiet zones to scan, and the
   card behind this is not guaranteed to be white in every portal theme. */
.sp-qr {
    background: var(--surface);
    padding: 12px;
    border-radius: 8px;
    width: fit-content;
    margin: 0 auto 12px;
}

.sp-qr svg { display: block; width: 200px; height: 200px; }

.sp-authenticator-key {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    /* ONE LINE. Eight groups plus separators is 39 characters, which did not fit at 16px and
       wrapped onto a second line ending in a lone group. Sized so the whole key sits on one line
       inside the card, and allowed to scroll rather than wrap on a very narrow phone: a key broken
       across lines is the one that gets typed in wrong. */
    font-size: 13px;
    letter-spacing: 1px;
    white-space: nowrap;
    overflow-x: auto;
    text-align: center;
    padding: 10px 12px;
    background: var(--tint, var(--surface-hover));
    border-radius: 6px;
}

/* Recovery codes. Two columns so eight of them read as a block to copy rather than a long list,
   and monospace for the same transcription reason as the key above. */
.sp-recovery-codes {
    list-style: none;
    /* Centred as a block AND within each column: eight codes hard against the left edge read as
       the start of a list, when they are a single thing to copy down. */
    margin: 0 auto 16px;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(2, auto);
    justify-content: center;
    justify-items: center;
    width: fit-content;
    gap: 6px 24px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 14px;
}

/* Neither of the row controls had a focus style. Both are reachable by keyboard and neither is a
   .sp-btn-*, so nothing else was going to give them one. */
.sp-connected__action:focus-visible,
.sp-connected__remove:focus-visible {
    outline: 2px solid var(--bgcolor-theme, #0b57d0);
    outline-offset: 2px;
    border-radius: 3px;
}

/* The disclosure sat between two bordered buttons looking like a third one. It is a control that
   reveals rather than acts, so it reads as a link now and keeps the group's tight rhythm. */
.sp-connect-more > summary { color: var(--muted); }
.sp-connect-more > summary:hover { color: var(--ink); }

/* ONE CHEVRON, DRAWN NOT INFERRED.
   This was two CSS borders on a rotated box -- the usual trick -- and at 7px, then 9px, the open
   state kept rendering as a corner bracket rather than an angle: two 2px borders leave too little
   daylight between them for the shape to read, and rotating it moves the optical centre off the
   box. Two attempts at tuning the size did not fix it, which is the point at which a drawn shape
   is cheaper than another guess. An SVG rotates unambiguously and is the same weight as the other
   icons on this screen. */
.sp-chevron {
    width: 14px;
    height: 14px;
    flex: none;
    transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1);
}

details[open] > summary > .sp-chevron { transform: rotate(90deg); }

/* ── A row that opens ─────────────────────────────────────────────────────────────────────────
   Two things on this screen used to be a link to somewhere else: two-step went to its own page to
   offer one button, and changing an email address sat permanently open taking a third of the
   dialog whether or not anybody wanted it. Both are now a row that expands where it stands.

   <details> and not a scripted panel, for the same reason the social disclosure is: this is
   open/closed state and nothing else, the browser already does it, and the CSP here allows no
   inline script. It also survives with JavaScript off, which a settings screen should.

   The shell matches .sp-connected__item exactly -- same border, radius and grid -- so an
   expandable row and a plain one are the same object in the list, one of which happens to open. */
.sp-expando {
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
    margin-bottom: 8px;
    transition: border-color 160ms ease;
}

.sp-expando:hover { border-color: var(--line-hover); }
.sp-expando[open] { border-color: var(--line-hover); }

/* FOUR COLUMNS, because the chevron is its own. A row states what the thing is, what state it is
   in, and that it opens; cramming the last two into one "auto" let the chevron drift left or right
   depending on whether that row had a switch beside it. */
.sp-expando__row {
    display: grid;
    grid-template-columns: 20px 1fr auto auto;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    cursor: pointer;
    list-style: none;
}

/* The email row has no state to report, so it leaves the switch column empty rather than filling
   it with something. The chevron still lands where the other two put theirs. */
.sp-expando__row > .sp-chevron { grid-column: 4; color: var(--muted); }

.sp-expando__row::-webkit-details-marker { display: none; }

/* The revealed half. Separated by a rule rather than a gap, so it reads as the same card
   continuing rather than a second card appearing underneath. */
.sp-expando__panel {
    padding: 14px 16px 16px;
    border-top: 1px solid var(--line);
}

.sp-expando__panel > :last-child { margin-bottom: 0; }

/* The panel's own explanation, above whatever button it holds. */
.sp-expando__note {
    margin: 0 0 12px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--muted);
}

/* The change-email disclosure used to be its own shape here: a dashed box with a flex summary, no
   mark column and no chevron, on the reasoning that it starts something rather than stating
   something. Stacked under two rows that DO state something, it just read as a third design. It
   uses .sp-expando__row like its neighbours now, and this block went with the difference. */


.sp-expando__row:focus-visible {
    outline: 2px solid var(--bgcolor-theme, #0b57d0);
    outline-offset: -2px;
    border-radius: 9px;
}

@media (prefers-reduced-motion: reduce) {
    .sp-field--float > label { transition-duration: 1ms; }
    .sp-social-btn { transition-duration: 1ms; }
    .sp-chevron { transition-duration: 1ms; }
}

/* ── The two-step switch ──────────────────────────────────────────────────────────────────────
   A REAL SWITCH, AND ONLY HALF OF IT IS A TOGGLE. Turning two-step OFF is one POST, so that
   direction is a genuine toggle. Turning it ON cannot be: TOTP needs a secret, a QR scan and a
   verifying code, so the same control is a link to that flow. Both render identically and both
   move you toward the other state, which is how GitHub and Google present it -- the knob not
   flipping instantly is explained by the setup screen you land on.

   INSIDE A <summary> IT IS DECORATION, and marked aria-hidden there. The summary already
   announces expanded/collapsed to a screen reader; adding role="switch" on top would announce two
   different states for one control, and the word beside the knob ("On"/"Off") already carries the
   real one in the accessible name of the row. */
/* SCENERY, NOT A CONTROL.
   It reports whether two-step is on and nothing more. It used to carry a pointer cursor and a
   press state, which made it look like the thing that turns two-step on -- next to a button that
   actually does. Two controls for one job, and the one that looked most like the answer was the
   one that did nothing. Default cursor, no hover, no active: it is a readout. */
.sp-switch {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    cursor: inherit;
    pointer-events: none;
}

/* A FIXED WIDTH FOR THE WORD, so the knob sits in the same place on every row.
   "On" and "Off" are different widths, so a list of rows had its knobs stepping left and right
   down the column depending on what each one said. Reserving the wider of the two settles them
   into one line and lets the word sit right up against the knob it belongs to. */
.sp-switch__state {
    width: 22px;
    text-align: right;
    letter-spacing: .01em;
}

.sp-switch__on { display: none; }

.sp-switch__track {
    position: relative;
    width: 36px;
    height: 22px;
    flex: none;
    border-radius: 11px;
    background: #d0d3d8;
    transition: background-color 200ms cubic-bezier(0.23, 1, 0.32, 1);
}

.sp-switch__track::after {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--surface);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
    transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1);
}

/* INK, NOT GREEN. A saturated green was the one colour on this screen that belonged to no brand
   and no token -- it read as a status light dropped into a settings list. The panel's own ink
   states "on" just as plainly and looks the same on all four portals. */
.sp-switch[aria-checked="true"],
details[open] > summary > .sp-switch { color: var(--ink); }

.sp-switch[aria-checked="true"] .sp-switch__track,
details[open] > summary > .sp-switch .sp-switch__track { background: var(--ink); }

.sp-switch[aria-checked="true"] .sp-switch__track::after,
details[open] > summary > .sp-switch .sp-switch__track::after { transform: translateX(14px); }

.sp-switch[aria-checked="true"] .sp-switch__off,
details[open] > summary > .sp-switch .sp-switch__off { display: none; }

.sp-switch[aria-checked="true"] .sp-switch__on,
details[open] > summary > .sp-switch .sp-switch__on { display: inline; }


/* Reduced motion: the state still changes, it just stops sliding. */
@media (prefers-reduced-motion: reduce) {
    .sp-switch__track,
    .sp-switch__track::after,
    .sp-chevron { transition-duration: 1ms; }
}

/* THE ADDRESS IN A SUBTITLE IS THE ONE WORD ON THE PAGE THAT HAS TO BE READ.

   .sp-login-subtitle is --muted (#888), and seven screens bold an email address inside it -- the
   address a code is going to, the one being added to a team, the one somebody is about to sign in
   as. Bolding it made it heavier without making it any darker, so the single most important string
   on those pages was rendered in the lightest ink on them.

   One rule rather than seven, because it is the same decision every time: whatever a subtitle
   bolds is the thing being confirmed, and it belongs in body ink. */
.sp-login-subtitle b,
.sp-login-subtitle strong { color: var(--ink); font-weight: 600; }

/* THE MIGRATION BANNER. Portal-tinted from the tokens the layout already sets on the card, so this
   is IndustrySearch gold on pale gold and MedicalSearch teal on pale teal without naming a colour:
   --stone-bgcolor is the brand's light shade, --bgcolor-theme its accent.

   The rule down the left is what makes it read as a notice rather than a paragraph with a
   background. It is the accent at full strength against the tint, which is the one place on these
   screens the two brand colours meet. */
/* THE PANEL'S TIP BOX, ported. Same 15px radius, same tinted fill, same lightbulb sitting to the
   left of the words -- see .wrapper-c2__tipbox in the Panel's content.css. A supplier meets this
   shape on the product pages already, so the migration notice borrows the shape they have been
   taught rather than introducing a second one.

   The left rule is gone with it. That was this screen's own invention, and a 3px accent bar plus a
   tint plus a rounded corner is two notice styles fighting inside one box.

   Portal-tinted from the tokens the layout already sets on the card: --stone-bgcolor is this
   brand's light shade, so this is pale gold on IndustrySearch and pale teal on MedicalSearch
   without a colour being named here. The Panel injects those inline from Razor; this service has
   them as custom properties, so it does not have to. */
.sp-banner {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin: 0 0 20px;
    padding: 14px 16px;
    background: var(--stone-bgcolor);
    border-radius: 15px;
    font-size: 13px;
    line-height: 1.55;
    color: var(--ink);
}

/* 22px against 13px text, which is the Panel's 26-against-14 ratio at this screen's type size.
   Sized to the first line rather than the block, so it sits beside the opening words instead of
   floating in the middle of a wrapped paragraph. */
.sp-banner__mark { width: 22px; height: 22px; flex: none; margin-top: -1px; color: var(--ink); opacity: .7; }

/* Underlined, like the Panel's tip box. A link inside a tinted box has no colour contrast left to
   distinguish it, so the underline is what marks it. */
.sp-banner a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }

/* Links inside the card's footer hints. Left to the browser they came out default blue and
   underlined, which is the one colour on these screens that belongs to no brand and matches
   nothing else on the page. Muted ink, like the sentence they sit in, with the underline doing the
   work of saying it is a link. */
.sp-footer-hint a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.sp-footer-hint a:hover { color: var(--ink); opacity: .75; }

/* The provider forms and the password button are toggled per address by login.js. Anything with a
   display rule of its own outranks the [hidden] attribute, which has bitten this file twice before
   (.sp-google-button, .ops-leave-form), so the attribute is made to win here explicitly. */
.sp-social-row form[hidden] {
    display: none !important;
}

/* THE ERROR BOX THAT HAS NO ERROR IN IT.
   passkeys.js writes a cancelled or failed ceremony into .sp-validation-summary, so the element has
   to be on the page before there is anything to say. Rendered empty it still drew its own tinted,
   bordered box -- an error card with nothing in it, sitting above the form on arrival, which is the
   first thing somebody saw on the staff security screen.
   The framework's own summary avoids this with .validation-summary-valid; a hand-written one
   carries no such class, so the empty case is answered here instead. */
.sp-validation-summary:empty { display: none; }

/* EasyOps brand line. The service ships one mark and it is EasyAsset's, so the product name is set
   in words beside it rather than claiming a logo that would name the wrong product. */
.sp-ops-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 24px; }
.sp-ops-brand img { height: 28px; width: auto; }
.sp-ops-brand__name { font-size: 20px; font-weight: 700; color: #1f2937; letter-spacing: -0.01em; }

/* ===========================================================================
   EASYOPS SIGN-IN SETTINGS (/Ops/Security)

   Its own block rather than reusing the supplier account screen's classes. Those
   describe a customer's screen and the next change to them is for customers; this
   is the same boundary the layout and the passkey endpoints already draw.

   DATA-DENSE, NOT ORNATE. No icon tiles, no nested cards, no decorative chrome:
   an internal tool read by people who know what a passkey is. Each row is a fact
   and the control that changes it.

   Every colour below is measured against its own background rather than picked:
     ink   #0f172a on white   17.85:1
     body  #475569 on white    7.58:1
     meta  #64748b on white    4.76:1   (the 4.5 floor, not a "muted" grey)
     white on brand-700        5.85:1
     brand-700 on brand-50     5.58:1
   --------------------------------------------------------------------------- */

.sp-ops-head { margin-bottom: 20px; }

.sp-ops-title {
  margin: 0;
  font-size: 22px;
  line-height: 1.25;
  font-weight: 700;
  color: #0f172a;
  letter-spacing: -0.01em;
  /* Keeps a two-word title from breaking to a lone orphan on a narrow window. */
  text-wrap: balance;
}

.sp-ops-sub { margin: 4px 0 0; font-size: 14px; line-height: 1.5; color: #475569; }

/* ---- who you are --------------------------------------------------------- */

.sp-ops-identity {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #f8fafc;
  margin-bottom: 24px;
}

.sp-ops-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  box-shadow: 0 0 0 1px #e2e8f0;
}

/* The fallback when Google gave us no picture, or gave us one that has expired.
   Same size and shape as the photo, so nothing moves between the two states. */
.sp-ops-avatar--initial {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #dff3e9;
  color: #1a7349;          /* 5.05:1 on that tint */
  font-size: 18px;
  font-weight: 700;
}

/* min-width:0 is what lets a long address actually truncate: a flex child
   defaults to min-content width and would otherwise push the row wider. */
.sp-ops-identity__text { min-width: 0; }

.sp-ops-identity__name {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: #0f172a;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-ops-identity__email {
  margin: 2px 0 0;
  font-size: 13px;
  color: #475569;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- sections ------------------------------------------------------------ */

.sp-ops-section { padding-top: 20px; border-top: 1px solid #e2e8f0; margin-top: 20px; }
.sp-ops-section:first-of-type { padding-top: 0; border-top: 0; margin-top: 0; }

/* The heading and its action on one line, wrapping to two when there is no room
   rather than squeezing the button to nothing. */
.sp-ops-section__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.sp-ops-section__title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: #0f172a;
  display: flex;
  align-items: center;
  gap: 8px;
}

.sp-ops-section__lead {
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: #475569;
  max-width: 46ch;   /* inside the 65-75ch reading measure at this size */
}

.sp-ops-section__action { flex-shrink: 0; margin: 0; }

.sp-ops-empty { margin: 12px 0 0; font-size: 13px; line-height: 1.5; color: #475569; }

/* ---- the passkey list ---------------------------------------------------- */

.sp-ops-list { list-style: none; margin: 14px 0 0; padding: 0; }

.sp-ops-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #ffffff;
}

.sp-ops-row + .sp-ops-row { margin-top: 8px; }

.sp-ops-row__text { display: flex; flex-direction: column; min-width: 0; }

.sp-ops-row__name {
  font-size: 14px;
  font-weight: 500;
  color: #0f172a;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-ops-row__meta { font-size: 12px; color: #64748b; margin-top: 2px; }

/* ---- the "On" marker ----------------------------------------------------- */

.sp-ops-pill {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #1a7349;
  background: #effdf5;
  box-shadow: inset 0 0 0 1px #84ffc2;
  border-radius: 999px;
  padding: 2px 8px;
}

/* ---- buttons ------------------------------------------------------------- */

/* 44px, which is the touch-target floor and also what makes a row of controls
   read as one row. Properties are listed rather than `all`, and the whole thing
   stands down for anyone who asked for less motion. */
.sp-ops-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 10px;
  border: 1px solid transparent;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}

.sp-ops-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #1a7349;
}

.sp-ops-btn--primary { background: #1a7349; color: #ffffff; }
.sp-ops-btn--primary:hover { background: #15402e; }

.sp-ops-btn--quiet {
  background: #ffffff;
  color: #334155;
  border-color: #94a3b8;
}
.sp-ops-btn--quiet:hover { background: #f8fafc; color: #0f172a; }

@media (prefers-reduced-motion: reduce) {
  .sp-ops-btn { transition: none; }
}

/* ---- narrow windows ------------------------------------------------------ */

/* The popup this opens in is 520px, and a phone is narrower still. Below that the
   action drops under its heading and goes full width rather than being crushed
   against it. */
@media (max-width: 420px) {
  .sp-ops-section__action,
  .sp-ops-section__action .sp-ops-btn { width: 100%; }
  .sp-ops-row { flex-wrap: wrap; }
}

/* Form blocks on the EasyOps pages. The shared .sp-field carries the input styling;
   this only spaces the form from what precedes it and its own button from the field. */
.sp-ops-form { margin-top: 14px; }
.sp-ops-form .sp-field { margin-bottom: 12px; }
.sp-ops-back { margin-top: 24px; }
.sp-ops-back a { color: #1a7349; }
