/*
 * Roll Call - Application Styles
 */

/* === Reset & Base === */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

:root {
  --color-bg: #151720;
  --color-surface: #1a1d27;
  --color-surface-raised: #1e2232;
  --color-surface-hover: #242836;
  --color-border: #2e3347;
  --color-text: #e4e4e7;
  --color-text-muted: #9ca3af;
  --color-primary: #7c3aed;
  --color-primary-rgb: 124, 58, 237;
  --color-primary-hover: #6d28d9;
  --color-accent: #a78bfa;
  --color-danger:  #ef4444;
  --color-amber:   #f59e0b;
  --color-success: #4EEAC2;
  --color-auto:    var(--color-success);
  /* Text on a primary button. Admin's amber primary takes dark text (calendar.css). */
  --color-on-primary: #fff;
  /* White on --color-danger (#ef4444) is 3.76:1; a solid danger button fills with
     this step instead (4.83:1). Tinted danger text stays on --color-danger. */
  --color-danger-solid: #dc2626;
  /* Light and motion for the pill buttons (.btn). One light source, above. */
  --ease-out: cubic-bezier(.2, .9, .3, 1);
  --dur-press: 80ms;
  --dur-hover: 180ms;
  --dur-colour: 260ms;
  --dur-lift: 280ms;
  --shadow-raised: 0 10px 28px rgba(0, 0, 0, .45), 0 1px 2px rgba(0, 0, 0, .4);
  --color-control: color-mix(in srgb, var(--color-surface-hover), #fff 4%);
  --radius: 8px;
  --radius-lg: 12px;
  --shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
  /* Rendered heights of the headers pinned to the top of the page, which the
     scroll offset below clears. --app-header-height is a first-paint default:
     app_header_controller sets it to the header's measured height, so large
     text or a changed header cannot leave it stale. The landing header has no
     controller, so update --landing-header-height when its padding or contents
     change; it measures 76px on desktop and less on phones. A trial account's
     header is taller on phones; ui.css overrides --app-header-height there
     (the .trial-cta rules). */
  --app-header-height: 65px;
  --landing-header-height: 76px;
  /* Phone spacing. The page gutter (.container) and a card's padding read
     these, so a phone tightens them in one place (the 480px block below), for
     a card that is already on the token: a new 24/28px card reads it rather
     than repeating 1.5rem, but the Design Lead's 2026-10-18 audit named only
     the top-level page cards and their own nested cards, and this spec did
     not convert the modal/dialog content boxes (*-dialog__content), which
     stay as they were. --card-pad-roomy is the 28px card (wizard step, tool
     card, sign-up call to action); --card-pad-nested is a card inside a
     card. */
  --space-page-x: 1.5rem;
  --card-pad: 1.5rem;
  --card-pad-roomy: 1.75rem;
  --card-pad-nested: 1rem;
}

@media (max-width: 480px) {
  :root {
    --space-page-x: 1rem;
    --card-pad: 1rem;
    --card-pad-roomy: 1rem;
    --card-pad-nested: 0.75rem;
  }
}

/* === Web Fonts (self-hosted) === */
@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/space-grotesk-400-b217bce7.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/space-grotesk-500-4b7aea1d.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/space-grotesk-600-ab3ffa9f.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/space-grotesk-700-36c6c9a5.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/inter-400-1e94a563.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/inter-500-5949f5fc.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/inter-600-a957bd0d.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/inter-700-cbc762c0.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
/* The guides' Sourcebook look: Fraunces for display, Literata for reading.
   Latin subsets of single instances (Fraunces at 72pt optical size, fully
   soft; Literata at 18pt), 104 KB in all. SIL Open Font License 1.1: see
   fraunces-OFL.txt and literata-OFL.txt beside the files. */
@font-face {
  font-family: "Fraunces";
  src: url("/assets/fraunces-600-0b4598e3.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("/assets/fraunces-400-italic-f0e40f0f.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("/assets/literata-400-14d8f958.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("/assets/literata-600-4ca65d1f.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("/assets/literata-400-italic-27e59efe.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
}

/* .app-header is sticky and .landing-header fixed, so whatever the browser
   scrolls into view (a wizard step change, a focused or invalid field, a
   #fragment) would stop with its top behind the header. */
html:has(.app-header) {
  scroll-padding-top: calc(var(--app-header-height) + 1rem);
}

/* A header that has scrolled away (.app-header--static) is not in the way. */
html:has(.app-header--static) {
  scroll-padding-top: 1rem;
}

/* Being fixed rather than sticky, .landing-header is out of flow and overlays
   the top of the document, so whatever the layout draws up there has to be
   pushed clear of it or it is not read at all: that is the same swallowed
   message as a view with no flash block, in a different form. A page that
   introduces another fixed header sets this beside its own height, and
   everything drawn at the top of the document follows. */
html:has(.landing-header) {
  --fixed-header-clearance: calc(var(--landing-header-height) + 1rem);
  scroll-padding-top: var(--fixed-header-clearance);
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background-color: var(--color-bg);
  color: var(--color-text);
  line-height: 1.6;
  min-height: 100vh;
}

.dice-bg {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
  contain: strict;
  -webkit-mask-image: radial-gradient(ellipse at center, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.25) 40%, rgba(0,0,0,0.4) 65%, rgba(0,0,0,0.7) 85%, rgba(0,0,0,1) 100%);
  mask-image: radial-gradient(ellipse at center, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.25) 40%, rgba(0,0,0,0.4) 65%, rgba(0,0,0,0.7) 85%, rgba(0,0,0,1) 100%);
}

.dice-bg::before {
  content: "";
  position: absolute;
  inset: -25%;
  background-size: 425px 300px;
  opacity: 0;
  transform: rotate(-12deg);
  transition: opacity 0.3s ease;
}

.dice-bg--loaded::before {
  background-image: url("/assets/dice-pattern-d08514d2.webp");
  opacity: 0.06;
}

a {
  color: var(--color-accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* === Accessibility === */
.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  z-index: 9999;
  padding: 0.75rem 1.25rem;
  background: var(--color-primary);
  color: #fff;
  font-weight: 600;
  border-radius: var(--radius);
  text-decoration: none;
}

.skip-link:focus {
  top: 1rem;
}

/* Global focus-visible ring for interactive elements */
a:focus-visible,
.btn:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Dialog close buttons: suppress outline (programmatic focus from showModal triggers focus-visible) */
dialog [class$="__close"]:focus,
dialog [class$="__close"]:focus-visible {
  outline: none;
}

/* Disabled buttons should never show a focus ring */
button:disabled:focus,
button:disabled:focus-visible,
.btn:disabled:focus,
.btn:disabled:focus-visible {
  outline: none;
}

/* Screen-reader only text */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Reset fieldset styling when used as a form group */
.form-fieldset {
  border: none;
  padding: 0;
  margin: 0;
}

/* === Layout === */
.container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 var(--space-page-x);
}

/* === Logo === */
.logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.logo:hover {
  text-decoration: none;
}

.logo-img {
  height: 40px;
  width: auto;
  transition: transform 0.2s ease, filter 0.2s ease;
}

.logo:hover .logo-img {
  transform: scale(1.05);
  filter: brightness(1.1);
}

.logo-img--login {
  height: 72px;
  margin-bottom: 0.25rem;
}

/* === Buttons ===
   Lit pill buttons (Design Lead's PillButtonsProposal, 2026-10-18). The fill is
   flat and the edge carries the light: ::before is a 1px gradient ring, ::after
   a 1px highlight inside it, and the shadow sits underneath. A variant only sets
   the --btn-* custom properties. Heights are exact: 56 (lg), 44 and 32 (sm, xs). */
.btn,
.btn-icon,
.attendance-btn,
.member-request-btn {
  /* rem, not px: a label and its button must grow with the user's text size
     (WCAG 1.4.4). Only the 1px rings, shadows and 2px focus ring stay in px. */
  --btn-h: 2.75rem;
  --btn-px: 1.25rem;
  --btn-fs: .9375rem;
  --btn-min: 7rem;
  --btn-bg: var(--color-control);
  --btn-fg: var(--color-text);
  --btn-ring: linear-gradient(90deg, rgba(255, 255, 255, .14) 0%, rgba(255, 255, 255, .04) 30%, rgba(255, 255, 255, .04) 70%, rgba(255, 255, 255, .12) 100%);
  --btn-top: linear-gradient(to bottom, rgba(255, 255, 255, .3) 0%, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0) 75%, rgba(255, 255, 255, .12) 100%);
  --btn-drop: var(--shadow-raised);
  --btn-wash: rgba(255, 255, 255, .07);
  --btn-press: rgba(0, 0, 0, .14);
  position: relative;
  isolation: isolate;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: var(--btn-h);
  min-width: min(var(--btn-min), 100%);
  max-width: 100%;
  padding-inline: var(--btn-px);
  padding-block: .5em;
  border: 0;
  border-radius: 9999px;
  background-color: var(--btn-bg);
  color: var(--btn-fg);
  font-family: inherit;
  font-size: var(--btn-fs);
  font-weight: 600;
  line-height: 1.2;
  white-space: normal;
  text-wrap: balance;
  text-decoration: none;
  cursor: pointer;
  text-box: trim-both cap alphabetic;
  box-shadow: inset 0 0 0 100px transparent, var(--btn-drop);
  transition: box-shadow var(--dur-hover) var(--ease-out), transform var(--dur-press) ease, color var(--dur-colour) ease;
}

.btn::before,
.btn::after,
.btn-icon::before,
.btn-icon::after,
.attendance-btn::before,
.attendance-btn::after,
.member-request-btn::before,
.member-request-btn::after {
  content: "";
  position: absolute;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
}

.btn::before,
.btn-icon::before,
.attendance-btn::before,
.member-request-btn::before {
  inset: 0;
  background: var(--btn-ring);
}

.btn::after,
.btn-icon::after,
.attendance-btn::after,
.member-request-btn::after {
  inset: 1px;
  background: var(--btn-top);
}

.btn:hover,
.btn-icon:hover,
.attendance-btn:hover,
.member-request-btn:hover {
  box-shadow: inset 0 0 0 100px var(--btn-wash), var(--btn-drop);
  text-decoration: none;
}

.btn:active,
.btn-icon:active,
.attendance-btn:active,
.member-request-btn:active {
  box-shadow: inset 0 0 0 100px var(--btn-press);
  transform: scale(.97);
}

.btn:focus-visible,
.btn-icon:focus-visible,
.attendance-btn:focus-visible,
.member-request-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.btn:disabled,
.btn[aria-disabled="true"],
.btn-disabled,
.btn-icon:disabled,
.attendance-btn:disabled,
.member-request-btn:disabled {
  opacity: .5;
  cursor: not-allowed;
  transform: none;
  box-shadow: inset 0 0 0 100px transparent, var(--btn-drop);
}

/* A link styled as disabled has no :disabled to stop the click. */
a.btn-disabled,
a.btn[aria-disabled="true"] {
  pointer-events: none;
}

.btn-lg {
  --btn-h: 3.5rem;
  --btn-px: 1.75rem;
  --btn-fs: 1.0625rem;
  --btn-min: 11rem;
}

.btn-sm,
.btn-xs {
  --btn-h: 2rem;
  --btn-px: .875rem;
  --btn-fs: .8125rem;
  --btn-min: 4rem;
}

@media (max-width: 640px) {
  .btn-lg {
    --btn-h: 2.75rem;
    --btn-px: 1.25rem;
    --btn-fs: .9375rem;
    --btn-min: 7rem;
  }
}

/* Primary, danger and amber share one recipe; only the hue differs. */
.btn-primary,
.btn-danger,
.btn-amber {
  --btn-hue: var(--color-primary);
  --btn-hue-hover: var(--color-primary-hover);
  --btn-bg: var(--btn-hue);
  --btn-fg: var(--color-on-primary);
  --btn-ring: linear-gradient(105deg, color-mix(in srgb, var(--btn-hue) 85%, transparent) 0%, color-mix(in srgb, color-mix(in srgb, var(--btn-hue) 30%, #fff) 75%, transparent) 50%, color-mix(in srgb, var(--btn-hue-hover) 85%, transparent) 100%);
  --btn-top: linear-gradient(to bottom, rgba(255, 255, 255, .4) 0%, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0) 80%, rgba(255, 255, 255, .1) 100%);
  --btn-drop: 0 6px 20px color-mix(in srgb, var(--btn-hue) 32%, transparent), 0 1px 2px rgba(0, 0, 0, .4);
  --btn-wash: rgba(255, 255, 255, .1);
  --btn-press: rgba(0, 0, 0, .1);
}

.btn-danger {
  --btn-hue: var(--color-danger-solid);
  --btn-hue-hover: color-mix(in srgb, var(--color-danger-solid) 85%, #000);
  --btn-fg: #fff;
}

.btn-amber {
  --btn-hue: var(--color-amber);
  --btn-hue-hover: color-mix(in srgb, var(--color-amber) 85%, #000);
  --btn-fg: #1a1a1a;
}

/* The tonal ring/highlight shape, shared by every tonal-family control
   (.btn-tonal, .btn-outline-warning, the RSVP .attendance-btn and
   .member-request-btn in campaigns.css/scheduling.css): one formula,
   parameterized by whichever rule sets --btn-tone-fg on the same element.
   Declared directly on each selector rather than inherited from :root, so
   the var(--btn-tone-fg) substitution resolves per element, not per
   ancestor (an inherited custom property substitutes at the element where
   it is DECLARED, not where it is consumed). */
.btn-tonal,
.btn-outline-warning,
.attendance-btn,
.member-request-btn {
  --btn-ring: linear-gradient(90deg, color-mix(in srgb, var(--btn-tone-fg) 30%, transparent) 0%, color-mix(in srgb, var(--btn-tone-fg) 6%, transparent) 30%, color-mix(in srgb, var(--btn-tone-fg) 6%, transparent) 70%, color-mix(in srgb, var(--btn-tone-fg) 22%, transparent) 100%);
  --btn-top: linear-gradient(to bottom, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0) 75%, rgba(255, 255, 255, .06) 100%);
}

/* Tonal: a violet tint over a frosted blur. Outline-warning is the same in amber. */
.btn-tonal,
.btn-outline-warning {
  --btn-tone: var(--color-primary);
  --btn-tone-fg: var(--color-accent);
  --btn-bg: color-mix(in srgb, var(--btn-tone) 20%, transparent);
  --btn-fg: var(--btn-tone-fg);
  --btn-drop: 0 0 #0000;
  --btn-wash: color-mix(in srgb, var(--btn-tone-fg) 12%, transparent);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

.btn-outline-warning {
  --btn-tone: var(--color-amber);
  --btn-tone-fg: var(--color-amber);
}

/* Plain: text only. */
.btn-plain {
  --btn-bg: transparent;
  --btn-ring: none;
  --btn-top: none;
  --btn-drop: 0 0 #0000;
  --btn-min: 0px;
  --btn-press: rgba(255, 255, 255, .04);
}

/* Danger-outline: a red tint with a red sheen ring. */
.btn-danger-outline {
  --btn-bg: color-mix(in srgb, var(--color-danger) 14%, transparent);
  --btn-fg: #f87171;
  --btn-ring: linear-gradient(105deg, color-mix(in srgb, var(--color-danger) 60%, transparent) 0%, color-mix(in srgb, color-mix(in srgb, var(--color-danger) 40%, #fff) 55%, transparent) 50%, color-mix(in srgb, var(--color-danger) 55%, transparent) 100%);
  --btn-top: linear-gradient(to bottom, rgba(255, 255, 255, .18) 0%, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0) 75%, rgba(255, 255, 255, .06) 100%);
  --btn-drop: 0 6px 16px color-mix(in srgb, var(--color-danger) 18%, transparent);
  --btn-wash: color-mix(in srgb, var(--color-danger) 12%, transparent);
}

/* .btn-outline is the default secondary: the plain .btn recipe above, with
   none of its own custom properties to set. Named here so
   button_variant_styles_test.rb still finds it defined (.btn-ghost is the
   same secondary, and keeps its own rule in components/ui.css). */
.btn-outline {
}

@media (prefers-reduced-motion: reduce) {
  .btn,
  .btn-icon,
  .attendance-btn,
  .member-request-btn {
    transition: none;
  }

  .btn:active,
  .btn-icon:active,
  .attendance-btn:active,
  .member-request-btn:active {
    transform: none;
  }
}

.btn-spinner {
  display: inline-block;
  width: 0.875em;
  height: 0.875em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: btn-spin 0.6s linear infinite;
  vertical-align: -0.1em;
  margin-right: 0.2em;
}

/* Icon-only buttons show just the spinner, so drop the label gap and let it
   sit centered in the button rather than nudged left. */
.btn-spinner--bare {
  margin-right: 0;
}

@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

