/*
 * Ecox SSO — visual design system.
 *
 * Owner: design-lead (docs/design-language.md has the full rationale).
 * Structural pass landed in T-22 (go-dev); this file is design-lead's
 * styling pass over the SAME class hooks (`sso-*`) — no class names or
 * template structure changed, only token values + rules, per T-22's
 * contract. 2026-07-19 rebrand pass: WeWillApp SSO → Ecox SSO, blue
 * placeholder → green (ECOXTECH logo) identity. 2026-07-20b: emerald
 * admin-redesign POC (operator-supplied concept) — recomputed AA contrast
 * for every new pairing from scratch, see docs/design-language.md's
 * "Phase 2b" section + docs/admin-redesign-assessment.md for the full
 * rationale of every choice below.
 *
 * Hard constraint (unchanged): served as a static asset (go:embed, see
 * static.go), never inlined — the app's `Content-Security-Policy:
 * default-src 'self'` (no 'unsafe-inline') needs no relaxation for
 * style-src as long as EVERY rule lives here, never in a template.
 *
 * Identity: a calm, restrained "trustworthy infrastructure" look (closer to
 * Stripe/Linear-grade auth screens than a marketing surface) — this page is
 * the front door every Ecox employee sees before anything else loads. The
 * admin back-office (Phase 2b) is the same identity applied to a denser,
 * data-table-centric surface — see "Phase 2b" below.
 */

:root {
  color-scheme: light;

  /* ------------------------------------------------------------------
   * BRAND — Ecox identity. 2026-07-20b: retoned from the plain green
   * (#15803d, Tailwind green-700) to Tailwind's EMERALD family, per the
   * operator-supplied admin-redesign concept (docs/admin-redesign-
   * assessment.md, Path A). This is still the ONE place a future rebrand
   * touches:
   *   1. --sso-color-primary (+ -hover/-active) → the brand hue, used for
   *      every primary button, the focus-ring base, and (via the real <img>
   *      logo in layout.html) sits alongside the logo mark.
   *   2. --sso-color-secondary → the logo's teal/blue-green globe accent.
   *      RESERVED, decorative + the Admin role badge only — untouched by
   *      this pass (still #0f766e; a cool teal already harmonizes with
   *      emerald, no clash to resolve).
   *   3. --sso-color-primary-accent → NEW (2026-07-20b): a distinct, lighter
   *      emerald step used ONLY on the dark admin sidebar (see "Phase 2b"
   *      below) — see the AA note next to its value for why this is a
   *      separate token from --sso-color-primary, not a duplicate.
   *   4. Logo — unchanged, `<img class="sso-header__logo" src="/static/
   *      ecox-logo.png">` in layout.html (and now also
   *      `.sso-admin-sidebar__logo` for the sidebar brand lockup).
   * ------------------------------------------------------------------ */

  /* --sso-color-primary is Tailwind emerald-700 (#047857), NOT the
     operator concept's literal emerald-600 (#059669) — recomputed, don't
     reuse: white button-text on emerald-600 only clears 3.77:1 (WCAG
     relative-luminance, computed directly), below the 4.5:1 AA minimum for
     normal text. emerald-700 clears 5.49:1. See docs/design-language.md
     "Phase 2b" for the full contrast table and docs/admin-redesign-
     assessment.md for why this is flagged as a correction, not a rejection,
     of the operator's spec. */
  --sso-color-primary: #047857; /* emerald-700 */
  --sso-color-primary-hover: #065f46; /* emerald-800 — 7.69:1 white-text */
  --sso-color-primary-active: #064e3b; /* emerald-900 — 9.72:1 white-text */
  --sso-color-primary-contrast: #ffffff;
  /* Focus-ring color is a separate token from --sso-color-primary on
     purpose (decoupling, not because they currently diverge): emerald-700's
     own luminance profile already clears 3:1 against both light-mode
     surfaces and (see the dark-mode block below) both dark-mode surfaces, so
     --sso-color-focus equals --sso-color-primary in BOTH themes today. */
  --sso-color-focus: var(--sso-color-primary);

  /* --sso-color-primary-accent (emerald-600, sidebar-only) — RETIRED in the
     Phase 2a Tailwind/DaisyUI admin redesign: the rebuilt sidebar's active
     nav state now uses the SAME --sso-color-primary as every other button/
     CTA in the system (DaisyUI's `bg-primary`/`text-primary-content`
     utilities), per the "one primary action color, never let a second hue
     leak into nav/chrome" rule — a second, narrower-verified shade just for
     the sidebar was no longer needed once nav styling moved to Tailwind
     utility classes reading the same theme token. See
     app/api/tailwind/input.css's `ecox` theme for the current source of
     truth on --color-primary (identical hex, #047857).

     Reserved brand accent — the logo's teal/blue-green globe. Untouched by
     the emerald retone (already independent of the primary hue; still
     decorative + the Admin role-badge fill only, see Components). */
  --sso-color-secondary: #0f766e;
  --sso-color-secondary-hover: #115e56;

  /* --- neutrals --- */
  --sso-color-bg: #f4f6f9;
  --sso-color-surface: #ffffff;
  --sso-color-text: #101828;
  --sso-color-muted: #475569;
  --sso-color-border: #d7dce3;
  --sso-color-border-strong: #b7c0cc;

  /* --- semantic (status) --- */
  --sso-color-error: #b3261e;
  --sso-color-error-bg: #fdecec;
  /* --sso-color-success / -bg: 2026-07-20b — retoned to Tailwind
     green-800/green-100 (#166534 / #dcfce7) per the operator concept's
     literal "green-100/green-800 = success" spec. Recomputed: 6.49:1 (WCAG
     relative luminance), clears AA with a comfortable margin. Deliberately
     a DIFFERENT green family from --sso-color-primary (emerald) — the
     concept's own "status colors separate from primary" rule — so success
     never reads as "the same green as the primary action," reinforced (as
     before the retone) by shape/position: success is always a tinted
     alert/badge, never a solid button fill, so it can never be confused
     with the primary CTA's affordance either. */
  --sso-color-success: #166534; /* green-800 */
  --sso-color-success-bg: #dcfce7; /* green-100 */

  /* --sso-color-warning*/--sso-color-danger* (the old Phase-2 amber-warning-
     box + destructive-action-button token families) are RETIRED as of the
     Phase 2a Tailwind/DaisyUI admin redesign — the entire /admin/* surface
     that used them (username-locked hint, delete-confirm warning, the
     delete/danger button) now reads its warning/error colors straight from
     the DaisyUI "ecox" theme (`--color-warning`/`--color-error` in
     app/api/tailwind/input.css, via `alert-warning`/`btn-error`/`alert-error`
     component classes) — same hex values, one fewer parallel token system
     to keep in sync. Nothing outside /admin/* ever used these two families
     (grep-verified before removal), so this is a pure dead-code cleanup,
     not a behavior change. See docs/design-language.md's Phase 2a section
     for the full before/after. */

  /* --- typography ---
     Thai-safe system stack: no web font is loaded (the CSP has no
     font-src/style-src relaxation and this app deliberately avoids a
     Google Fonts / CDN link, which would be blocked anyway) — every name
     below is a platform system font that already renders Thai glyphs
     correctly on its OS (Segoe UI/Leelawadee UI on Windows, San Francisco/
     Thonburi-backed system UI on macOS/iOS, Noto Sans Thai on Android/
     Linux/Chrome OS). */
  --sso-font-family: system-ui, -apple-system, "Segoe UI", Roboto,
    "Noto Sans Thai", "Noto Sans", sans-serif;
  /* --sso-font-family-mono: reserved for any future money/numeric/code
     column (none exists in SSO today — no money, no settings-table data —
     see docs/admin-redesign-assessment.md's "N/A for SSO" section) but
     defined so a future admin surface (e.g. a session/audit timestamp
     table) has a ready-made monospace stack instead of guessing one. */
  --sso-font-family-mono: ui-monospace, "SFMono-Regular", "Cascadia Code",
    Consolas, "Liberation Mono", monospace;
  --sso-font-size-sm: 0.875rem;
  --sso-font-size-xs: 0.75rem;
  --sso-font-size-base: 1rem;
  --sso-font-size-heading: 1.5rem;
  --sso-line-height-base: 1.6; /* generous — Thai vowel/tone marks need the room */
  --sso-line-height-heading: 1.3;
  --sso-font-weight-label: 500;
  --sso-font-weight-heading: 700;

  /* --- spacing (8pt-ish scale) --- */
  --sso-space-xs: 0.25rem;
  --sso-space-sm: 0.5rem;
  --sso-space-md: 1rem;
  --sso-space-lg: 1.5rem;
  --sso-space-xl: 2.5rem;

  /* --- radius / elevation / layout --- */
  --sso-radius-sm: 0.5rem; /* inputs, buttons, alerts, brand mark */
  --sso-radius-lg: 1rem; /* card */
  --sso-shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04),
    0 12px 32px -8px rgba(16, 24, 40, 0.14);
  --sso-card-max-width: 27rem;
  /* Admin/back-office content widths. The auth pages (login/setup/
     logout/account-password) keep the 27rem width above unchanged — this is
     purely additive. Two more canonical widths, selected per-page via the
     `.sso-main:has(...)` rules further down (a pure-CSS, content-aware
     width switch — no Go template field needed, since `.Page` is a
     different concrete struct per page and `layout.html` can't branch on a
     field only some of them have; see design-language.md for the full
     rationale).
     Phase 2a (Tailwind/DaisyUI admin redesign) widened -admin from 40rem to
     60rem: the new-user/detail pages now render a genuine two-column layout
     (form + a real info/summary side panel) instead of one narrow centered
     form — a lone ~40rem column centered in the full dashboard width read as
     "stranded" on a wide viewport (design-lead craft-bar finding). The
     confirm/step-up page is the deliberate exception — see
     `.sso-admin-shell--narrow` further down — and stays a small, centered,
     modal-like card on purpose. */
  --sso-card-max-width-admin: 60rem; /* new-user / detail (2-column) forms */
  --sso-card-max-width-admin-table: 72rem; /* the user-list table (widened
     from 64rem in the 2026-07-20b pass — the table now sits inside the
     sidebar shell's content column rather than a lone centered card, so it
     can use the extra room instead of scrolling sooner than it needs to) */

  /* --sso-sidebar-* (fixed dark chrome tokens) — RETIRED in the Phase 2a
     Tailwind/DaisyUI admin redesign. The sidebar's color/width/spacing now
     comes directly from Tailwind/DaisyUI utility classes in layout.html
     (`bg-neutral`, `text-neutral-content`, `w-64`, etc., reading the SAME
     "ecox" theme values these tokens used to duplicate — see
     app/api/tailwind/input.css). style.css keeps only the structural
     show/hide toggle for `.sso-admin-sidebar` further down — see that rule's
     comment for why the split is by CSS PROPERTY (display vs. everything
     else), not by which stylesheet "wins": style.css is unlayered and
     app.css's Tailwind utilities live in `@layer utilities`, and CSS
     cascade layers make an unlayered rule always beat a layered one
     regardless of specificity (verified empirically, not assumed) — so the
     two stylesheets must never both set the same property on the same
     element, only different, non-overlapping properties. */

  /* --- motion --- */
  --sso-transition-fast: 120ms ease;
}

/* Dark scheme — same semantics, re-themed for a dark surface. No JS toggle;
   this is `prefers-color-scheme` only, per the task's light+dark
   requirement. Only values that actually depend on page/surface luminance
   are overridden (button-on-primary contrast, e.g., doesn't change with the
   page background, so it's omitted here on purpose). The admin sidebar's
   own colors are no longer sourced from this file at all (see the Phase 2a
   note further down) — the DaisyUI "ecox" theme is light-only, so the
   sidebar stays visually the same dark surface regardless of the visitor's
   OS light/dark preference, same end result as the old fixed-dark-sidebar
   tokens this comment used to describe. */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    /* Emerald behaves like the old plain green here: G-dominant hues have a
       narrow "AA text-contrast ceiling" / "AA non-text floor" band (see
       docs/design-lessons.md), so hover/active are pinned to the same value
       as the base color in dark mode; the hover/active affordance instead
       comes from the inset highlight ring further down
       (`.sso-button--primary:hover`/`:active` inside this same media
       query), not from a luminance shift. Recomputed for emerald-700
       (#047857) specifically, not assumed from the old green-700 numbers:
       vs. dark card (#141b29) = 3.14:1 (≥3:1, passes — a real but slightly
       narrower margin than green-700's old 3.43:1, still comfortably
       above the floor); vs. dark page bg (#0b1220) = 3.41:1 (≥3:1, passes). */
    --sso-color-primary-hover: var(--sso-color-primary);
    --sso-color-primary-active: var(--sso-color-primary);
    /* --sso-color-focus is deliberately NOT overridden here (unlike the old
       blue palette) — --sso-color-primary itself already clears 3:1 against
       both the dark surface and the dark page background (recomputed above
       for emerald-700), so the base emerald doubles as the dark-mode
       focus-ring color with no separate lighter value needed. */

    --sso-color-bg: #0b1220;
    --sso-color-surface: #141b29;
    --sso-color-text: #e7ebf1;
    --sso-color-muted: #94a3b8;
    --sso-color-border: #26303f;
    --sso-color-border-strong: #384457;

    --sso-color-error: #ff9a91;
    --sso-color-error-bg: #3a1512;
    /* Success dark-mode pairing is UNCHANGED by the 2026-07-20b light-mode
       retone (green-800/green-100 → these dark values were never green-700-
       derived to begin with, they're their own independently-verified
       pair): 9.7:1, still comfortably AA. */
    --sso-color-success: #7ee2ac;
    --sso-color-success-bg: #0f2b1e;

    /* --sso-color-danger*/--sso-color-warning* dark-mode overrides retired
       alongside the :root definitions above — see that comment. The
       DaisyUI "ecox" theme these admin colors now come from is light-only
       (Phase 1 scope, tailwind/README.md), which only affects the
       Tailwind-styled /admin/* surface, not this file's own dark-mode
       tokens for the remaining sso-*-styled auth pages. */

    --sso-shadow-card: 0 1px 2px rgba(0, 0, 0, 0.5),
      0 20px 45px -12px rgba(0, 0, 0, 0.6);
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* THE empty-top-band bug fix (2026-07-20d): layout.html's icon-sprite <svg
   hidden> (the inline `<symbol>` definitions used by the admin sidebar/nav
   icons via `<use href="#icon-*">`) is NOT actually removed from layout by
   the `hidden` attribute alone. Verified with a real headless-Chrome render
   + getBoundingClientRect(): the browser's `[hidden] { display: none }` UA
   rule does not reliably apply to elements in the SVG namespace, so the
   sprite rendered as its default replaced-element box (300x150, `display:
   inline`) as the FIRST in-flow child of <body> on every page — pushing the
   entire .sso-shell (and therefore the admin sidebar + content) down by
   ~158px on every render. On the auth pages this just nudged the
   already-centered card down a bit (easy to miss); on /admin, where the
   shell is supposed to be pinned to the very top, it read as a large empty
   band above the sidebar/content — exactly the bug the operator caught at a
   real desktop height (invisible in a short 450px-tall screenshot, obvious
   at 900px+). Force it out of layout explicitly instead of relying on the
   attribute alone — do not remove this rule when touching the icon sprite. */
svg[hidden] {
  display: none;
}

html,
body {
  height: 100%;
}

.sso-body {
  margin: 0;
  font-family: var(--sso-font-family);
  font-size: var(--sso-font-size-base);
  line-height: var(--sso-line-height-base);
  color: var(--sso-color-text);
  background-color: var(--sso-color-bg);
  -webkit-font-smoothing: antialiased;
}

/* Visually-hidden until focused — keyboard/screen-reader users can jump
   past the header straight to the form. Text color is the neutral
   --sso-color-text token, not --sso-color-primary: this fixed a latent
   contrast gap found during the 2026-07-19 rebrand (the brand hue only
   clears the 3:1 NON-text minimum against the dark-mode surface, not the
   4.5:1 TEXT minimum this label actually needs; --sso-color-text already
   clears 15:1+ in both themes, see design-language.md). */
.sso-skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--sso-color-surface);
  color: var(--sso-color-text);
  font-weight: var(--sso-font-weight-label);
  text-decoration: none;
  border-radius: var(--sso-radius-sm);
  box-shadow: var(--sso-shadow-card);
  padding: var(--sso-space-sm) var(--sso-space-md);
  z-index: 10;
}

.sso-skip-link:focus {
  left: var(--sso-space-md);
  top: var(--sso-space-md);
}

.sso-shell {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

/* Body wrapper around header/main/footer (layout.html, 2026-07-20b). On
   every AUTH page (the vast majority of renders) this is `display: contents`
   — fully transparent to layout, so header/main/footer behave as direct
   flex children of .sso-shell exactly as before this wrapper existed
   (byte-identical visual result, zero risk to the 5 auth templates). Only
   overridden (below, under the .sso-shell:has(.sso-admin-shell) rules) when
   the page is part of the admin back-office, to make room for the sidebar. */
.sso-shell__body {
  display: contents;
}

.sso-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sso-space-md);
  padding: var(--sso-space-md) var(--sso-space-lg);
  border-bottom: 1px solid var(--sso-color-border);
}

/* #9 language switcher (auth pages + /docs — layout.html's header; the
   admin sidebar's copy is styled entirely with Tailwind utility classes
   inline instead, matching the rest of that Tailwind/DaisyUI-styled
   surface).
   i18n2 polish (design-lead): replaced the earlier plain-text "EN | ไทย"
   links (no button chrome, easy to miss/mistap) with a real segmented
   control — a pill-shaped track (bg = --sso-color-border, the same neutral
   already used for hairline borders elsewhere, so this introduces no new
   token) holding two toggle segments. The CURRENT locale gets a solid
   --sso-color-surface "chip" lifted off the track with --sso-shadow-card
   (the SAME shadow token every other elevated surface in this file uses —
   not a one-off), so "you are here" reads instantly instead of relying on
   font-weight alone. Each segment is a real ≥44x44px target (min-width/
   min-height, not just padding-as-an-afterthought) — the previous ~26x26px
   text link was well under any reasonable tap-target guidance. Kept as
   plain <a> elements (no <button>/JS) — same zero-JS, CSP `default-src
   'self'` posture as the rest of this stylesheet; the pill/active-state
   visuals are pure CSS reacting to the `--active` class + `aria-current`
   the template already sets. */
.sso-lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background-color: var(--sso-color-border);
}

.sso-lang-switch__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.875rem;
  border-radius: 999px;
  font-size: var(--sso-font-size-sm);
  font-weight: 600;
  color: var(--sso-color-muted);
  text-decoration: none;
  transition: color var(--sso-transition-fast), background-color var(--sso-transition-fast), box-shadow var(--sso-transition-fast);
}

.sso-lang-switch__link:hover {
  color: var(--sso-color-text);
}

.sso-lang-switch__link:focus-visible {
  outline: 2px solid var(--sso-color-focus);
  outline-offset: 2px;
}

.sso-lang-switch__link--active {
  background-color: var(--sso-color-surface);
  color: var(--sso-color-text);
  box-shadow: var(--sso-shadow-card);
}

.sso-lang-switch__link--active:hover {
  color: var(--sso-color-text);
}

.sso-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sso-space-sm);
  font-weight: var(--sso-font-weight-heading);
  font-size: 1.0625rem;
  color: var(--sso-color-text);
}

/* Real ECOXTECH logo image. `alt=""` is deliberate (decorative — the
   adjacent visible AppName text already names the app; see
   docs/design-lessons.md). `object-fit: contain` + `max-width` keep an
   unknown-aspect-ratio asset from ever stretching or overflowing. */
.sso-header__logo {
  display: block;
  height: 1.75rem;
  width: auto;
  max-width: 8rem;
  object-fit: contain;
}

.sso-main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sso-space-xl) var(--sso-space-md);
}

.sso-card {
  width: 100%;
  max-width: var(--sso-card-max-width);
  background-color: var(--sso-color-surface);
  border: 1px solid var(--sso-color-border);
  border-radius: var(--sso-radius-lg);
  box-shadow: var(--sso-shadow-card);
  padding: var(--sso-space-xl) var(--sso-space-lg);
}

.sso-heading {
  margin: 0 0 var(--sso-space-md);
  font-size: var(--sso-font-size-heading);
  font-weight: var(--sso-font-weight-heading);
  line-height: var(--sso-line-height-heading);
  letter-spacing: -0.01em;
}

.sso-prompt {
  margin: 0 0 var(--sso-space-md);
  color: var(--sso-color-muted);
}

/* Subtle static hint shown alongside the login error alert (e.g. "Locked
   out? Contact IT") — same muted/small treatment as .sso-prompt, distinct
   class so its purpose (a hint, not the primary prompt copy) stays clear at
   the call site. docs/design-language.md UX findings. */
.sso-hint {
  margin: 0 0 var(--sso-space-md);
  color: var(--sso-color-muted);
  font-size: var(--sso-font-size-sm);
}

/* Alert / message states (error, success, empty) — TS-095. A left accent
   bar gives each state a scannable identity beyond color-fill alone (helps
   color-blind users distinguish error/success by more than hue). */
.sso-alert {
  margin: 0 0 var(--sso-space-md);
  padding: var(--sso-space-sm) var(--sso-space-md);
  border-radius: var(--sso-radius-sm);
  border-left: 3px solid transparent;
  font-size: var(--sso-font-size-sm);
  line-height: 1.5;
}

.sso-alert--error {
  color: var(--sso-color-error);
  background-color: var(--sso-color-error-bg);
  border-left-color: var(--sso-color-error);
}

.sso-alert--success {
  color: var(--sso-color-success);
  background-color: var(--sso-color-success-bg);
  border-left-color: var(--sso-color-success);
}

/* .sso-alert--warning retired alongside --sso-color-warning* above — the
   Phase 2a admin redesign uses DaisyUI's `alert alert-warning` instead. */

/* Empty state is rendered `hidden` (no message to show) — kept in the DOM
   so the layout never collapses/jumps between states; this display:none is
   belt-and-braces alongside the `hidden` attribute itself. */
.sso-alert--empty {
  display: none;
}

.sso-form {
  display: flex;
  flex-direction: column;
  gap: var(--sso-space-md);
}

.sso-field {
  display: flex;
  flex-direction: column;
  gap: var(--sso-space-xs);
}

.sso-label {
  font-weight: var(--sso-font-weight-label);
  font-size: var(--sso-font-size-sm);
  color: var(--sso-color-text);
}

/* Every text/password input on every page shares this one styled
   primitive — never a bare, unstyled native control. Sized so the tap
   target (padding + line-height) clears 44px for comfortable mobile touch,
   not just the 24px WCAG 2.2 AA minimum. */
.sso-input {
  font: inherit;
  color: var(--sso-color-text);
  background-color: var(--sso-color-surface);
  border: 1px solid var(--sso-color-border);
  border-radius: var(--sso-radius-sm);
  padding: 0.75rem var(--sso-space-md);
  transition: border-color var(--sso-transition-fast),
    box-shadow var(--sso-transition-fast);
}

.sso-input::placeholder {
  color: var(--sso-color-muted);
}

.sso-input:hover {
  border-color: var(--sso-color-border-strong);
}

.sso-input:focus-visible {
  outline: 2px solid var(--sso-color-focus);
  outline-offset: 1px;
  border-color: var(--sso-color-focus);
}

/* Autofilled fields keep the theme's surface/text colors instead of the
   browser's default yellow/blue autofill tint (Chromium/WebKit). */
.sso-input:-webkit-autofill,
.sso-input:-webkit-autofill:hover,
.sso-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--sso-color-text);
  -webkit-box-shadow: 0 0 0 1000px var(--sso-color-surface) inset;
  transition: background-color 9999s ease-in-out 0s;
}

/* Every action row (one button, or a button + a secondary link like
   logout_confirm's Cancel / admin_user_new's Back-to-list / the confirm
   page's Cancel) lays out consistently: gap between multiple actions, and
   wraps rather than overflowing on a narrow phone screen. */
.sso-field--actions {
  margin-top: var(--sso-space-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sso-space-sm);
}

/* FIX (visual QA pass, 2026-07-20): every `.sso-button` in this app is
   rendered as an `<a>` (primary "New user"/pagination/back/grant/revoke/
   delete/cancel links — a real `<button>` is only used for actual form
   submits), so the browser's default link underline was leaking straight
   through onto every button-styled link, including the primary CTA. `text-
   decoration: none` here is the ONE place that fixes it for every button
   variant + state at once (no `:hover`/`:focus`/`:visited` override anywhere
   in this file re-introduces an underline — verified by grep, see
   docs/design-lessons.md's dated entry on why an HTML-string assertion never
   would have caught this: the defect is only visible once rendered). */
.sso-button {
  font: inherit;
  font-weight: 600;
  padding: 0.75rem var(--sso-space-md);
  border: 1px solid transparent;
  border-radius: var(--sso-radius-sm);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--sso-transition-fast),
    border-color var(--sso-transition-fast);
}

.sso-button:focus-visible {
  outline: 2px solid var(--sso-color-focus);
  outline-offset: 2px;
}

/* ONE primary action color, used for every primary button on every page —
   there is no second accent color in this system competing with it for
   actions/chrome: --sso-color-secondary (the logo's teal globe accent) is
   reserved strictly for decorative/role-badge use and never appears on a
   button, link, or focus ring; --sso-color-primary-accent (the sidebar-only
   emerald-600 step) never appears on a button either — see the :root
   comments for both. */
.sso-button--primary {
  background-color: var(--sso-color-primary);
  color: var(--sso-color-primary-contrast);
}

.sso-button--primary:hover {
  background-color: var(--sso-color-primary-hover);
}

.sso-button--primary:active {
  background-color: var(--sso-color-primary-active);
}

/* Dark-mode-only hover/active affordance. --sso-color-primary-hover/-active
   equal the base color in dark mode (see the :root dark-mode override's
   comment for why), so the interactive cue here comes from a subtle inset
   highlight ring instead of a fill-color shift. */
@media (prefers-color-scheme: dark) {
  .sso-button--primary:hover {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18) inset;
  }

  .sso-button--primary:active {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.28) inset;
  }
}

/* Bordered/ghost secondary action — Cancel/back/View-row links. */
.sso-button--secondary {
  background-color: transparent;
  color: var(--sso-color-text);
  border-color: var(--sso-color-border);
}

.sso-button--secondary:hover {
  border-color: var(--sso-color-border-strong);
  background-color: var(--sso-color-bg);
}

/* .sso-button--icon / .sso-button--danger retired — the Phase 2a admin
   redesign uses DaisyUI's `btn`/`btn-error`/`btn-ghost` etc. directly
   instead (see app/api/internal/web/templates/admin/*.html). Neither class
   was ever used outside /admin/*, so this is a pure dead-code cleanup. */

/* Native "loading" affordance: this is a plain (non-JS-enhanced) HTML form
   submit — the browser's own navigation/loading indicator covers the
   loading state between submit and the server's redirect/re-render
   response (TS-095); no client-side script is added to simulate one,
   consistent with the CSP's `default-src 'self'` (no inline JS). */
.sso-button:disabled,
.sso-button[aria-disabled="true"] {
  opacity: 0.65;
  cursor: progress;
}

/* ====================================================================
 * Admin / back-office shell mechanics — setup + the /admin/* shared shell.
 * Phase 2a (Tailwind/DaisyUI admin redesign, this pass) rebuilt every
 * /admin/* template's actual CONTENT in Tailwind utilities + DaisyUI
 * components (see app/api/internal/web/templates/admin/*.html) — the rules
 * below are what's LEFT in style.css: the shared, structural show/hide +
 * width/layout mechanics for the marker-class `:has()` system (unchanged
 * technique from earlier phases), which must stay here because it's shared
 * with the auth pages' own layout (`.sso-shell`/`.sso-main`/`.sso-card`).
 * Every rule keyed on `.sso-admin-shell`/`--table`/`--narrow` /
 * `.sso-setup-shell` applies automatically to every page carrying that
 * marker class — go-dev owns the marker class itself in the templates,
 * design-lead owns the values here (docs/design-language.md).
 * ==================================================================== */

/* First-run /setup: same calm auth-card treatment as login (27rem,
   unchanged), plus a one-time visual cue that this is a distinct,
   consequential moment (creating the root admin) — a top accent bar in the
   RESERVED secondary hue. Untouched by the 2026-07-20b sidebar work: /setup
   is a pre-auth, one-time bootstrap page with no admin session yet, so it
   deliberately does NOT get the authenticated sidebar shell. */
.sso-main:has(.sso-setup-shell) .sso-card {
  border-top: 3px solid var(--sso-color-secondary);
}

/* Admin back-office pages get two wider canonical widths instead of the
   27rem auth-card width. Selected via `:has()` on a marker class each admin
   content template wraps itself in — see design-language.md for the full
   `:has()` rationale (browser-baseline note etc., unchanged by this pass).

   2026-07-20c (full rollout): EVERY admin content page (list/new/detail/
   confirm) now pulls its h1+description OUT of the card via
   .sso-page-header (the pattern the 2026-07-20b POC introduced on the list
   page only) and wraps its actual form/section content in a NEW inner card
   — .sso-table-card on the list page, .sso-form-card on the other three. So
   for every admin variant, the outer .sso-card this layout.html always
   renders becomes a plain, unstyled layout container — the real card look
   now comes from the inner .sso-table-card/.sso-form-card, never a
   competing double-card look. Every auth page keeps the normal
   white/bordered/shadowed .sso-card look untouched (this rule is scoped to
   .sso-admin-shell, which auth pages never carry). */
.sso-main:has(.sso-admin-shell) .sso-card {
  max-width: var(--sso-card-max-width-admin);
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 0;
}

.sso-main:has(.sso-admin-shell--table) .sso-card {
  max-width: var(--sso-card-max-width-admin-table);
}

/* GET /docs — the STANDALONE (non-admin-chrome) chrome for the public API
   reference page (handlers.Docs / .Page.AdminChrome==false — design-lead,
   2026-07-24, "/docs API-reference page — context-aware chrome"; the
   admin-chrome case instead emits the literal `.sso-admin-shell.sso-admin-
   shell--table` marker and is covered by the rules above, same as every
   other admin list page). Needs the same wide, read-heavy layout as the
   admin table view (72rem: several full-width tables + a mockup-code
   block), but is a PUBLIC, unauthenticated page with no session — it must
   NOT pull in the authenticated admin sidebar. Every sidebar/row-layout
   rule above is deliberately keyed on the literal `.sso-admin-shell` marker
   class; `.sso-docs-shell` is a distinct class that only the rules below
   match, so this page gets the width without any of the
   `:has(.sso-admin-shell)` side effects (sidebar show, header/footer hide,
   row layout).

   2026-07-24 fix (docs/design-lessons.md's "partial marker-class :has()
   override" lesson): this rule used to touch ONLY max-width, copy-pasted
   from the admin pattern but dropping the `background: transparent; border:
   none; box-shadow: none; padding: 0;` lines the admin rule ALSO carries —
   so .sso-card's base white/bordered/shadowed/padded auth-card look kept
   applying underneath a page that is already six of its own carded
   sections (docs.html's `.grid.gap-6` of `.card` blocks), producing a
   double-boxed "card inside a card" look. Now strips the outer card chrome
   exactly like the admin rule does, so the six inner cards are the only
   visible card surface — one level of card chrome, matching every other
   page in the app. */
.sso-main:has(.sso-docs-shell) .sso-card {
  max-width: var(--sso-card-max-width-admin-table);
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 0;
}

/* Top-anchor the reference document instead of vertically centering it like
   a compact auth card (2026-07-24 fix, same pass as the double-card fix
   above — `.sso-main`'s base `align-items: center` is correct for a short
   login card, but was never overridden for this tall, multi-section
   document, so it visibly floated mid-viewport on a tall/short-content
   combination instead of reading as a top-anchored document) — matches the
   admin shell's own top-alignment (`.sso-shell:has(.sso-admin-shell)
   .sso-main` above). justify-content is deliberately left at its base
   `center` (unlike the admin rule, which also sets
   `justify-content: flex-start`) — /docs has no sidebar competing for the
   left edge, so horizontal centering in the full viewport width is still
   correct; only the vertical axis was wrong. The padding bump from the base
   rule's `--sso-space-md` horizontal gutter to `--sso-space-lg` matches the
   admin shell's own gutter — this page is exactly as wide/dense as an admin
   table now, not an auth card, so it should get the same breathing room. */
.sso-shell:has(.sso-docs-shell) .sso-main {
  align-items: flex-start;
  padding: var(--sso-space-xl) var(--sso-space-lg);
}

/* --- App portal (GET /apps) — standalone chrome (Increment 0.5,
   docs/design-app-portal.md §1) ---

   A THIRD chrome, and both of the existing two are wrong for this page:
   the 27rem auth-card is too narrow for a card grid, and the admin sidebar
   shell is wrong for a user who may not be an admin — and wrong even for
   one who is, per sa's §5.6.6 ruling ("an admin sees their OWN grants on
   the portal, and nothing more; /admin is not an app"). The portal is
   deliberately NOT rendered inside the admin management surface.

   Modeled on .sso-docs-shell above PROPERTY FOR PROPERTY, not just the one
   property that motivated it — that is the 2026-07-24 "partial marker-class
   :has() override" lesson (docs/design-lessons.md), where copying only
   max-width and dropping the card-chrome resets left a page of cards
   double-boxed inside an auth card.

   Width reuses --sso-card-max-width-admin (60rem) rather than minting a
   fourth width tier: roomier than an auth card, calmer than the 72rem
   admin-table tier, and this page is not table-dense. */
.sso-main:has(.sso-portal-shell) .sso-card {
  max-width: var(--sso-card-max-width-admin);
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 0;
}

.sso-shell:has(.sso-portal-shell) .sso-main {
  align-items: flex-start;
  padding: var(--sso-space-xl) var(--sso-space-lg);
}

/* Admin pages are a logged-in back-office surface, not the calm centered
   front door — top-aligned instead of vertically centered, with room for
   the sidebar to sit alongside. Left-aligned (not centered) horizontally
   too, so content sits directly under the left-aligned page header instead
   of floating centered in the remaining viewport width — a lone centered
   narrow column read as "stranded" on a wide screen next to the sidebar
   (design-lead craft-bar finding, Phase 2a Tailwind/DaisyUI redesign). */
.sso-shell:has(.sso-admin-shell) .sso-main {
  align-items: flex-start;
  justify-content: flex-start;
  padding: var(--sso-space-xl) var(--sso-space-lg);
}

/* The confirm/step-up screen is the deliberate exception: a small,
   centered, modal-like moment, not a wide dashboard page — see
   admin_user_confirm.html's own comment on `.sso-admin-shell--narrow`. */
.sso-shell:has(.sso-admin-shell--narrow) .sso-main {
  justify-content: center;
}

/* Actions-column-clipping fix (kept from the prior pass): the table view
   gets a touch less horizontal chrome padding than the form pages — every
   pixel here goes straight to the table's available width, and the
   Actions column must stay visible without horizontal scroll at 1440px. */
.sso-shell:has(.sso-admin-shell--table) .sso-main {
  padding-left: var(--sso-space-md);
  padding-right: var(--sso-space-md);
}

/* --- Sidebar shell -------------------------------------------------------
   Phase 2a (Tailwind/DaisyUI admin redesign): style.css now owns ONLY the
   structural show/hide + row/column mechanics for the shared shell — every
   VISUAL property (color, width, spacing, responsive breakpoints) lives as
   Tailwind/DaisyUI utility classes directly in layout.html's <aside>. This
   split is deliberate, not an oversight: style.css is unlayered CSS and
   app.css's Tailwind utilities live in `@layer utilities` — CSS cascade
   layers make an unlayered rule always beat a layered one regardless of
   specificity (verified empirically with a throwaway Playwright check, not
   assumed), so the two stylesheets must never both set the SAME property
   on the SAME element — only non-overlapping properties. */

/* Switches .sso-shell from its default single (auth-page) column to a
   sidebar + content row at >=64rem/1024px — matching Tailwind's `lg`
   breakpoint, which the sidebar's own responsive utility classes also use,
   so both halves of the collapse stay in sync from one shared breakpoint
   value. Stacked (sidebar as a top bar) below that. Also un-collapses
   .sso-shell__body back into a real flex column (it's `display: contents`
   by default — see that rule's comment above) so header/main/footer stack
   correctly inside the content column. */
.sso-shell:has(.sso-admin-shell) {
  flex-direction: column;
  align-items: stretch;
}

@media (min-width: 64rem) {
  .sso-shell:has(.sso-admin-shell) {
    flex-direction: row;
  }
}

.sso-shell:has(.sso-admin-shell) .sso-shell__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0; /* lets the table's own horizontal-scroll container work
     instead of the whole column refusing to shrink below the table's
     intrinsic width */
}

/* The top .sso-header brand bar is redundant once the sidebar carries the
   same brand lockup — hidden for admin pages so the brand appears exactly
   once, not twice stacked. The shared .sso-footer is likewise redundant
   next to the sidebar's own version footer. */
.sso-shell:has(.sso-admin-shell) .sso-header,
.sso-shell:has(.sso-admin-shell) .sso-footer {
  display: none;
}

/* Presence toggle ONLY — every other property (position, width,
   flex-direction, responsive collapse, color) is Tailwind/DaisyUI utility
   classes on the <aside> itself in layout.html; see the block comment
   above for why this split is load-bearing, not stylistic. */
.sso-admin-sidebar {
  display: none;
}

.sso-shell:has(.sso-admin-shell) .sso-admin-sidebar {
  display: flex;
}

/* --- Everything below this line (page header, table-card/form-card,
   badges, empty state, table, pagination, search form, actions cell) is
   RETIRED as of the Phase 2a Tailwind/DaisyUI admin redesign — every
   /admin/* template now uses Tailwind utility classes + DaisyUI components
   (`card`, `table`, `badge`, `btn`, `alert`, `menu`, `input`, `fieldset`)
   directly, reading the SAME "ecox" theme values these sso-* rules used to
   duplicate (app/api/tailwind/input.css). See
   app/api/internal/web/templates/admin/*.html for the current markup and
   docs/design-language.md's Phase 2a section for the full rationale
   (including the AA re-verification of every DaisyUI theme-slot pairing
   used). This is a pure dead-code removal — grep-verified nothing outside
   /admin/* ever referenced any of these classes. */

.sso-footer {
  padding: var(--sso-space-md) var(--sso-space-lg);
  color: var(--sso-color-muted);
  font-size: var(--sso-font-size-sm);
  text-align: center;
}

/* Small screens: tighten card/header padding rather than shrinking type —
   legibility matters more than density on a phone. */
@media (max-width: 30rem) {
  .sso-card {
    padding: var(--sso-space-lg) var(--sso-space-md);
  }

  .sso-header {
    padding: var(--sso-space-sm) var(--sso-space-md);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sso-input,
  .sso-button {
    transition: none;
  }
}
