/* ==========================================================================
   Bold Seal — forms.css
   Full form styles. Migrated from components.css skeleton (Chat 16).

   Load order: base.css → layout.css → components.css → forms.css
   Only include this file on pages that have forms (contact.html,
   commercial.html, estimator.html). Pages without forms skip it.

   Custom properties referenced here are all defined in base.css.
   No new properties are introduced — all values pull from the design tokens.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Labels — .bs-label
   --------------------------------------------------------------------------
   Migrated from components.css. Labels always sit above their control,
   never beside (simplified layout, better resize behavior on narrow screens).
   -------------------------------------------------------------------------- */

.bs-label {
  display: block;
  margin-bottom: var(--bs-space-2);
  font-weight: var(--bs-font-weight-medium);
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-body);
}

/* Required-field asterisk — injected by FormHandler or authored in HTML.
   Using ::after pseudo on .bs-label--required instead of raw text keeps the
   asterisk out of the accessible label text (screen readers skip ::after). */
.bs-label--required::after {
  content: " *";
  color: #c0392b;
  font-weight: var(--bs-font-weight-bold);
  /* aria-hidden via CSS is not possible; the HTML <span aria-hidden="true">*</span>
     pattern is preferred when rendering in markup. This class is the CSS fallback
     for cases where FormHandler marks labels automatically. */
}


/* --------------------------------------------------------------------------
   2. Base input, select, textarea — .bs-input / .bs-select / .bs-textarea
   --------------------------------------------------------------------------
   Migrated from components.css and expanded with full interaction states.
   All three share a common base via the grouped selector; variants add their
   own rules below.
   -------------------------------------------------------------------------- */

.bs-input,
.bs-select,
.bs-textarea {
  display: block;
  width: 100%;
  padding: var(--bs-space-3) var(--bs-space-4);
  min-height: 48px;                   /* 48px tap target — WCAG 2.5.5 */
  background: var(--bs-surface-page);
  color: var(--bs-text-body);
  border: 1px solid var(--bs-border-strong);
  border-radius: var(--bs-radius-md);
  font-family: inherit;
  font-size: var(--bs-font-size-base);
  line-height: var(--bs-line-height-snug);
  -webkit-appearance: none;           /* normalize across browsers */
  appearance: none;
  transition: border-color var(--bs-transition-fast),
              box-shadow   var(--bs-transition-fast);
}

.bs-input::placeholder,
.bs-textarea::placeholder {
  color: var(--bs-color-neutral-400);
}

.bs-input:hover,
.bs-select:hover,
.bs-textarea:hover {
  border-color: var(--bs-color-neutral-700);
}

.bs-input:focus,
.bs-select:focus,
.bs-textarea:focus {
  outline: 0;
  border-color: var(--bs-color-secondary);
  box-shadow: var(--bs-focus-ring);
}

/* Invalid state — set by FormHandler._showFieldError() via aria-invalid.
   Using [aria-invalid] as the CSS hook keeps the red border tied to the
   real accessibility state rather than a parallel JS-managed class.
   Losing the blue focus ring in favor of the error ring is intentional —
   two different colored rings would be confusing. */
.bs-input[aria-invalid="true"],
.bs-select[aria-invalid="true"],
.bs-textarea[aria-invalid="true"] {
  border-color: #c0392b;
  box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.15);
}

.bs-input[aria-invalid="true"]:focus,
.bs-select[aria-invalid="true"]:focus,
.bs-textarea[aria-invalid="true"]:focus {
  /* Keep the error ring visible on focus — user needs to know the field is
     still invalid even after they start typing. It clears once they fix it. */
  border-color: #c0392b;
  box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.25);
}

/* Disabled state */
.bs-input:disabled,
.bs-select:disabled,
.bs-textarea:disabled {
  background: var(--bs-color-neutral-50);
  color: var(--bs-color-neutral-500);
  cursor: not-allowed;
  opacity: 0.75;
}


/* --------------------------------------------------------------------------
   3. Textarea — .bs-textarea
   -------------------------------------------------------------------------- */

.bs-textarea {
  min-height: 120px;
  resize: vertical;       /* allow height resizing; block width resizing to
                             prevent layout overflow on narrow viewports */
}


/* --------------------------------------------------------------------------
   4. Select — .bs-select
   --------------------------------------------------------------------------
   Appearance: none resets the native arrow (done in the base above).
   We paint our own arrow via background-image so it matches the brand palette.
   The SVG is inline data-URI to avoid an extra HTTP request for one icon.
   -------------------------------------------------------------------------- */

.bs-select {
  padding-right: var(--bs-space-8);   /* make room for custom arrow */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23525252' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--bs-space-4) center;
  cursor: pointer;
}

.bs-select:focus {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23125fd9' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* On invalid selects, tint the arrow red to reinforce the error state. */
.bs-select[aria-invalid="true"] {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23c0392b' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
}


/* --------------------------------------------------------------------------
   5. Field group — .bs-field-group
   --------------------------------------------------------------------------
   Wraps a label + control (+ optional error). Provides bottom spacing between
   consecutive fields. Using a wrapper rather than margin-bottom on individual
   inputs means we can adjust density once without touching every selector.
   -------------------------------------------------------------------------- */

.bs-field-group {
  display: flex;
  flex-direction: column;
  gap: 0;                             /* label/input gap handled by .bs-label mb */
  margin-bottom: var(--bs-space-5);
}

/* Last field in a group has less spacing — it's adjacent to the submit btn. */
.bs-field-group:last-of-type {
  margin-bottom: var(--bs-space-4);
}


/* --------------------------------------------------------------------------
   6. Phone row — .bs-phone-row
   --------------------------------------------------------------------------
   Dual-column layout: phone number input on the left, type dropdown (Landline/
   Mobile/Other) on the right. The dropdown is fixed-width so the number input
   gets maximum space. On mobile, they stay side-by-side — 48px min-height and
   the short dropdown labels keep this usable even on narrow screens.
   -------------------------------------------------------------------------- */

.bs-phone-row {
  display: grid;
  /* Type selector is capped at 120px; number input takes remaining space. */
  grid-template-columns: 1fr 120px;
  gap: var(--bs-space-3);
  align-items: start;                 /* align to top; error spans may push height */
}

/* On very narrow viewports (under 360px), stack rather than squeeze. */
@media (max-width: 359px) {
  .bs-phone-row {
    grid-template-columns: 1fr;
  }
}


/* --------------------------------------------------------------------------
   7. Two-column field layout — .bs-field-row-2up
   --------------------------------------------------------------------------
   Side-by-side fields (e.g. First Name / Last Name). Collapses to 1-column
   on mobile. Uses min-width 0 on children to prevent grid blowout when field
   contents are long.
   -------------------------------------------------------------------------- */

.bs-field-row-2up {
  display: grid;
  grid-template-columns: 1fr;         /* mobile: stacked */
  gap: var(--bs-space-4);
  margin-bottom: var(--bs-space-5);
}

@media (min-width: 640px) {
  .bs-field-row-2up {
    grid-template-columns: 1fr 1fr;   /* tablet+: side by side */
  }

  .bs-field-row-2up > * {
    min-width: 0;                     /* allow text truncation in grid cells */
  }
}

/* Row-level margin reset for field groups inside a 2up row — the row handles
   the spacing; we don't want double margin from both the row and the groups. */
.bs-field-row-2up .bs-field-group {
  margin-bottom: 0;
}


/* --------------------------------------------------------------------------
   8. Inline error message — .bs-field-error
   --------------------------------------------------------------------------
   Migrated from components.css. Inserted by FormHandler._showFieldError()
   immediately after the invalid input using insertAdjacentElement('afterend').
   role="alert" makes screen readers announce the error as soon as it appears.
   -------------------------------------------------------------------------- */

.bs-field-error {
  display: block;
  margin-top: var(--bs-space-1);
  font-size: var(--bs-font-size-sm);
  color: #c0392b;
  font-weight: var(--bs-font-weight-medium);
  /* Intentionally no animation — error messages should be noticed immediately,
     not eased in. A fade-in here could cause AT to miss the role="alert". */
}


/* --------------------------------------------------------------------------
   9. Radio + checkbox groups — .bs-radio-group / .bs-checkbox-group
   --------------------------------------------------------------------------
   Used for sealing-type selection on the estimator and any option-sets.
   The custom indicator is drawn via ::before on the label; the native input
   is visually hidden but remains in the tab order for accessibility.
   -------------------------------------------------------------------------- */

/* Visually-hidden native input that stays keyboard/AT accessible. */
.bs-radio-group input[type="radio"],
.bs-checkbox-group input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Clickable card-style label for each option. */
.bs-radio-group .bs-option-label,
.bs-checkbox-group .bs-option-label {
  display: flex;
  align-items: flex-start;
  gap: var(--bs-space-3);
  padding: var(--bs-space-4);
  background: var(--bs-surface-page);
  border: 2px solid var(--bs-border-subtle);
  border-radius: var(--bs-radius-md);
  cursor: pointer;
  transition: border-color var(--bs-transition-fast),
              background   var(--bs-transition-fast);
  font-size: var(--bs-font-size-base);
  line-height: var(--bs-line-height-snug);
  user-select: none;
}

.bs-radio-group .bs-option-label:hover,
.bs-checkbox-group .bs-option-label:hover {
  border-color: var(--bs-color-secondary);
  background: var(--bs-color-neutral-50);
}

/* Checked state — highlighted border + light tint. */
.bs-radio-group input[type="radio"]:checked + .bs-option-label,
.bs-checkbox-group input[type="checkbox"]:checked + .bs-option-label {
  border-color: var(--bs-color-secondary);
  background: rgba(18, 95, 217, 0.05);
}

/* Keyboard focus — paint a focus ring around the label (the native input
   is hidden, so we forward focus styling to the label). */
.bs-radio-group input[type="radio"]:focus-visible + .bs-option-label,
.bs-checkbox-group input[type="checkbox"]:focus-visible + .bs-option-label {
  outline: 3px solid var(--bs-color-secondary);
  outline-offset: 2px;
}

/* Custom radio dot / checkbox check — ::before on the label. */
.bs-radio-group .bs-option-label::before {
  content: "";
  display: block;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 2px;                    /* optical alignment with first line of text */
  border: 2px solid var(--bs-border-strong);
  border-radius: 50%;
  background: var(--bs-surface-page);
  transition: border-color var(--bs-transition-fast),
              background   var(--bs-transition-fast);
}

.bs-radio-group input[type="radio"]:checked + .bs-option-label::before {
  border-color: var(--bs-color-secondary);
  /* Filled-dot effect via inset box-shadow — no extra element needed. */
  box-shadow: inset 0 0 0 4px var(--bs-color-secondary);
  background: var(--bs-surface-page);
}

.bs-checkbox-group .bs-option-label::before {
  content: "";
  display: block;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border: 2px solid var(--bs-border-strong);
  border-radius: var(--bs-radius-sm);
  background: var(--bs-surface-page);
  transition: border-color var(--bs-transition-fast),
              background   var(--bs-transition-fast);
}

.bs-checkbox-group input[type="checkbox"]:checked + .bs-option-label::before {
  border-color: var(--bs-color-secondary);
  background: var(--bs-color-secondary);
  /* Checkmark via background-image SVG — avoids an ::after pseudo conflict
     if the label uses ::after for something else. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='8' viewBox='0 0 10 8'%3E%3Cpath d='M1 4l3 3 5-6' stroke='white' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
}

/* Option text content wrapper for multi-line labels with a title + description. */
.bs-option-label__text {
  display: flex;
  flex-direction: column;
  gap: var(--bs-space-1);
}

.bs-option-label__title {
  font-weight: var(--bs-font-weight-bold);
  font-size: var(--bs-font-size-base);
  color: var(--bs-text-body);
}

.bs-option-label__desc {
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-muted);
  line-height: var(--bs-line-height-relaxed);
}

/* Stacked radio options (the default). Side-by-side when there are only 2
   and the viewport is wide enough — e.g. "Essential / Premium" on estimator. */
.bs-radio-group,
.bs-checkbox-group {
  display: flex;
  flex-direction: column;
  gap: var(--bs-space-3);
}

.bs-radio-group--inline {
  flex-direction: row;
  flex-wrap: wrap;
}

.bs-radio-group--inline .bs-option-label {
  flex: 1 1 0;                        /* equal widths; wraps if container narrows */
  min-width: 160px;
}


/* --------------------------------------------------------------------------
   10. Form section heading — .bs-form-section
   --------------------------------------------------------------------------
   Optional divider between logical form sections (e.g. "Contact Info" /
   "Project Details"). Lighter than a page <h2> — just a labeled separator.
   -------------------------------------------------------------------------- */

.bs-form-section {
  margin-top: var(--bs-space-8);
  margin-bottom: var(--bs-space-5);
  padding-bottom: var(--bs-space-3);
  border-bottom: 1px solid var(--bs-border-subtle);
  font-size: var(--bs-font-size-base);
  font-weight: var(--bs-font-weight-bold);
  color: var(--bs-color-ink);
  letter-spacing: 0.01em;
}

/* First section heading needs no top margin — it sits directly below the
   form heading or the page lead paragraph. */
.bs-form-section:first-child {
  margin-top: 0;
}


/* --------------------------------------------------------------------------
   11. Submit button row — .bs-form-actions
   --------------------------------------------------------------------------
   Wraps the submit button (and any cancel/reset links). Adds top spacing to
   visually separate the button from the last field. On mobile the button
   spans full width; on tablet+ it's auto-width (fits content).
   -------------------------------------------------------------------------- */

.bs-form-actions {
  margin-top: var(--bs-space-6);
  display: flex;
  flex-direction: column;
  gap: var(--bs-space-3);
  align-items: stretch;               /* mobile: full-width buttons */
}

@media (min-width: 640px) {
  .bs-form-actions {
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
  }

  .bs-form-actions .bs-btn {
    width: auto;                      /* tablet+: shrink to content */
  }
}


/* --------------------------------------------------------------------------
   12. Submit button — loading / disabled states
   --------------------------------------------------------------------------
   FormHandler adds .is-loading to the submit button while the stub is running
   its simulated delay. The spinner is a CSS-only rotating border arc; no JS
   needed after the class is toggled. .is-loading implies disabled behaviour
   too (pointer-events: none) — FormHandler also sets [disabled] for AT.
   -------------------------------------------------------------------------- */

.bs-btn--submit {
  /* Same base as .bs-btn--primary; submit gets its own class so we can hang
     loading state off it without coupling to the generic primary variant. */
  position: relative;
  min-width: 140px;                   /* prevents width jump when text swaps */
}

.bs-btn--submit.is-loading {
  pointer-events: none;
  cursor: wait;
  /* Dim the text to make room for the spinner visually. */
  color: transparent;                 /* hide text while keeping layout stable */
}

/* Spinner — absolutely positioned over the button center so text stays in
   the DOM (ARIA live region may read it; we can't remove it). */
.bs-btn--submit.is-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 20px;
  height: 20px;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: #ffffff;
  border-radius: 50%;
  animation: bsSpinnerRotate 0.7s linear infinite;
}

@keyframes bsSpinnerRotate {
  to { transform: rotate(360deg); }
}

/* Reduced-motion: swap the spinner for a blinking ellipsis on the button text
   instead. The text is transparent during loading, so we override it here. */
@media (prefers-reduced-motion: reduce) {
  .bs-btn--submit.is-loading {
    color: rgba(255, 255, 255, 0.7);  /* show text again — no spinner */
  }

  .bs-btn--submit.is-loading::after {
    display: none;
  }
}


/* --------------------------------------------------------------------------
   13. Success state — .bs-form-success
   --------------------------------------------------------------------------
   FormHandler replaces the form contents with this block on successful submit.
   Friendly confirmation, not a redirect — keeps the user on the page so they
   can navigate to estimator or browse services.
   -------------------------------------------------------------------------- */

.bs-form-success {
  padding: var(--bs-space-8);
  background: var(--bs-color-neutral-50);
  border: 1px solid var(--bs-border-subtle);
  border-radius: var(--bs-radius-lg);
  text-align: center;
}

.bs-form-success__icon {
  /* Large checkmark circle — decorative, aria-hidden in HTML. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(18, 194, 217, 0.12);   /* --bs-color-accent tint */
  color: var(--bs-color-secondary);
  margin-bottom: var(--bs-space-4);
}

.bs-form-success__icon svg {
  width: 28px;
  height: 28px;
}

.bs-form-success__heading {
  font-size: var(--bs-font-size-xl);
  font-weight: var(--bs-font-weight-bold);
  color: var(--bs-color-ink);
  margin-bottom: var(--bs-space-3);
}

.bs-form-success__body {
  font-size: var(--bs-font-size-base);
  color: var(--bs-text-muted);
  margin-bottom: var(--bs-space-6);
  max-width: 36ch;
  margin-left: auto;
  margin-right: auto;
}
