/* The address-to-estimate pipeline: map confirmation, the processing sequence,
   and the report.
 *
 * Builds on the tokens and primitives in application.css and reuses the
 * estimator's `.estimate-lines` rows wholesale - the itemised list is the same
 * component doing the same job, and the report is where it finally has real
 * numbers in it.
 *
 * Two house rules this sheet is deliberately careful about:
 *
 * 1. NO NEW SHADOW. Exactly two elements on the site are allowed to cast one
 *    (.estimator and .plan-featured) and the scarcity is what makes elevation
 *    mean "this is the thing". The report's total gets an accent border and
 *    the surface ladder instead.
 *
 * 2. This is the first motion on the site beyond one button transition, so the
 *    prefers-reduced-motion block at the bottom is not optional - it arrives
 *    in the same commit as the animation it turns off.
 *
 * Mobile-first `min-width` queries throughout, checked at 375px first: the
 * design target is a phone held in a driveway, possibly in the rain, possibly
 * over a roof that is actively leaking. */

/* ---------------------------------------------------------------------------
   Step 1b - map confirmation

   The map is the biggest thing on this page on purpose. The question is "is
   this your house?", and a postage stamp cannot answer it - so the card sits
   in the full .wrap and the viewport takes most of the viewport height.
   --------------------------------------------------------------------------- */

.confirm-card {
  padding: 1.25rem;
}

.confirm-head {
  max-width: 44rem;
  margin-bottom: 1rem;
}

.confirm-address {
  margin: 0 0 0.35rem;
  font-weight: 600;
}

/* A wide frame cropping a square image. The image is always as wide as the
   frame, which is what makes the drag arithmetic in confirm_map_controller
   simple: one CSS pixel is a fixed fraction of a map tile. */
.map-viewport {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-alt);
  aspect-ratio: 4 / 3;
  max-height: 70vh;
  cursor: grab;
  /* Stops a touch drag scrolling the page instead of panning the map. */
  touch-action: none;
  user-select: none;
}

.is-dragging .map-viewport { cursor: grabbing; }

.map-viewport img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  transform: translate(-50%, -50%);
  display: block;
  pointer-events: none;
}

.is-loading .map-viewport img { opacity: 0.75; }

/* Fixed at the centre of the frame. It marks the coordinate, and the map
   slides underneath it - which is why the image carries no baked-in marker
   that would drift away from the middle as soon as anyone dragged. */
.crosshair {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2.6rem;
  height: 2.6rem;
  margin: -1.3rem 0 0 -1.3rem;
  border: 2px solid var(--accent);
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.65),
              inset 0 0 0 1px rgba(255, 255, 255, 0.65);
  pointer-events: none;
}

/* The one crosshair marking, not a second colour: a dot in the middle of the
   ring, so it reads at a glance which pixel is meant. */
.crosshair::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  background: var(--accent);
  border-radius: 50%;
}

.map-controls {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  display: grid;
  gap: 0.35rem;
}

/* 2.75rem so it is a real touch target on the phone this is used on. */
.map-button {
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: var(--ink);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.map-button:hover:not(:disabled) { background: var(--bg-alt); }

.map-button:disabled {
  opacity: 0.4;
  cursor: default;
}

.map-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.map-failed {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 1.25rem;
  text-align: center;
  color: var(--ink-soft);
  font-size: 0.9rem;
  background: var(--bg-alt);
}

.map-moved {
  margin: 0.6rem 0 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.confirm-form {
  margin-top: 1rem;
  max-width: 26rem;
}

/* ---------------------------------------------------------------------------
   Step 2 - the $18

   Nothing on this screen casts a shadow, including the pay button. The two
   elements allowed to (.estimator, .plan-featured) keep the scarcity that
   makes elevation mean something.
   --------------------------------------------------------------------------- */

.checkout-card {
  padding: 1.5rem 1.25rem;
}

.checkout-head {
  margin-bottom: 1.5rem;
}

.checkout-address {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 0.35rem;
  font-weight: 600;
}

.checkout-subheading {
  margin: 0 0 0.6rem;
  font-size: 1rem;
}

.checkout-list {
  margin: 0;
  padding-left: 1.1rem;
  color: var(--ink-soft);
}

.checkout-list li {
  margin-bottom: 0.35rem;
  text-wrap: pretty;
}

.checkout-includes {
  margin-bottom: 1.75rem;
}

.checkout-credit {
  margin-bottom: 1.25rem;
  padding: 1rem 1.1rem;
  background: var(--bg-alt);
  border-radius: var(--radius);
}

.checkout-form {
  max-width: 26rem;
}

.checkout-payhint {
  margin-top: 0.6rem;
  font-size: 0.88rem;
}

/* Neither an error nor a success: the buyer backed out of Stripe and nothing
   happened. Reassurance, in the quietest voice the sheet has. */
.checkout-notice {
  margin-bottom: 1.25rem;
  padding: 0.8rem 1rem;
  background: var(--bg-alt);
  border-radius: var(--radius);
  color: var(--ink-soft);
  font-size: 0.92rem;
}

@media (min-width: 40rem) {
  .checkout-card { padding: 2rem; }
}

/* ---------------------------------------------------------------------------
   Processing sequence

   Follows docs/processing-sequence-prototype.html, rebuilt on this site's own
   tokens rather than the prototype's palette: the prototype's teal scan and
   orange signal become --accent, and its card shadow is dropped because only
   .estimator and .plan-featured are allowed to cast one.
   --------------------------------------------------------------------------- */

.progress-track {
  display: flex;
  gap: 5px;
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
}

.seg {
  flex: 1;
  height: 3px;
  border-radius: 2px;
  background: var(--line);
  overflow: hidden;
  position: relative;
}

.seg .fill {
  position: absolute;
  inset: 0;
  background: var(--accent);
  transform: translateX(-100%);
  transition: transform 0.5s ease;
}

.seg.active .fill { transform: translateX(0); }
.seg.done .fill { transform: translateX(0); background: var(--ink); }
.seg.failed .fill { transform: translateX(0); background: #e0a800; }

.stage-label {
  margin: 0 0 0.15rem;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  min-height: 1.5rem;
}

.stage-sub {
  margin: 0 0 1rem;
  font-size: 0.82rem;
  color: var(--ink-soft);
  min-height: 1.1rem;
}

/* The facet plan. Real extents at real relative positions - see the caption,
   which is load-bearing: square corners on a hip roof are an artefact of
   bounding boxes, not a claim about the building. */
.plan-wrap {
  position: relative;
  border-radius: var(--radius);
  background: var(--bg-alt);
  border: 1px solid var(--line);
  overflow: hidden;
  padding: 0.75rem;
}

.plan-wrap svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 15rem;
}

.facet {
  fill: transparent;
  stroke: #9aa3b0;
  stroke-width: 0.35;
  stroke-dasharray: 1 0.8;
  transition: fill 0.5s ease, stroke 0.4s ease;
}

.facet.on {
  fill: rgba(180, 68, 31, 0.12);
  stroke: var(--ink);
  stroke-width: 0.45;
  stroke-dasharray: 0;
}

.facet-label {
  opacity: 0;
  transition: opacity 0.35s ease;
}

.facet-label.on { opacity: 1; }

.facet-label text {
  text-anchor: middle;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.facet-label .area { fill: var(--ink); }
.facet-label .pitch { fill: var(--accent-dark); }

/* The beam only ever passes over the plan once, as the facets are found. It
   is a progress cue, not decoration that loops forever. */
.scan-beam {
  position: absolute;
  left: 0;
  right: 0;
  height: 3.5rem;
  top: -3.5rem;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(180, 68, 31, 0) 0%,
    rgba(180, 68, 31, 0.18) 45%,
    rgba(180, 68, 31, 0.32) 50%,
    rgba(180, 68, 31, 0.18) 55%,
    rgba(180, 68, 31, 0) 100%
  );
}

.scan-beam.sweeping { animation: sweep 2.6s cubic-bezier(0.55, 0.02, 0.35, 1) 1; }

@keyframes sweep {
  from { top: -3.5rem; }
  to   { top: 100%; }
}

.plan-caption {
  margin: 0.5rem 0 0;
  font-size: 0.72rem;
  color: var(--ink-soft);
  text-align: center;
}

.facet-counter {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-soft);
  text-align: center;
  font-variant-numeric: tabular-nums;
  min-height: 1.1rem;
}

.area-readout {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  margin: 0.9rem 0 0;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}

.area-readout .k {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}

.area-readout .v {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Receipt */
.receipt-lines {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.receipt-line {
  opacity: 0;
  transform: translateY(0.35rem);
  transition: opacity 0.35s ease, transform 0.35s ease;
  padding: 0.3rem 0;
  border-bottom: 1px dashed var(--line);
}

.receipt-line.on {
  opacity: 1;
  transform: none;
}

.receipt-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 2px solid var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Reveal */
.reveal-stage {
  text-align: center;
  padding: 1.5rem 0 0.5rem;
}

.reveal-eyebrow {
  margin: 0;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-soft);
}

.reveal-total {
  margin: 0.35rem 0 0;
  font-size: clamp(1.9rem, 6vw, 2.6rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.reveal-sub {
  margin: 0.25rem 0 1.1rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.measuring-failed {
  margin-top: 1.25rem;
  padding: 0.75rem 0.9rem;
  border: 1px solid #e0a800;
  border-radius: var(--radius);
  background: #fff4d6;
  color: #5c4600;
  font-size: 0.9rem;
}

/* ---------------------------------------------------------------------------
   The report
   --------------------------------------------------------------------------- */

.report-header {
  margin-bottom: 1.5rem;
}

.report-header h1 {
  font-size: clamp(1.8rem, 4vw, 2.5rem);
}

/* The headline number. Accent border rather than a shadow - see the note at
   the top of this file. */
.report-total {
  border-color: var(--accent);
  text-align: center;
  margin-bottom: 1.5rem;
}

.report-total-label {
  margin: 0;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-soft);
}

.report-total-value {
  margin: 0.35rem 0 0;
  font-size: clamp(1.6rem, 5vw, 2.4rem);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.report-confidence {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.confidence {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* What the score is built from. The number needs it: on its own, "60%" next to
   a dollar range reads as a probability that the price is right, which is not
   what it measures. */
.report-confidence-hint {
  margin: 0.3rem 0 0;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.confidence-high   { color: var(--ink); }
.confidence-medium { color: #5c4600; }
.confidence-low    { color: var(--accent-dark); }

/* The disclosure. On the alternate surface with an accent rule so it reads as
   part of the estimate rather than as a dismissible aside - it is neither a
   banner nor fine print, and it sits above the numbers it qualifies. */
.report-limits {
  margin-bottom: 1.75rem;
  padding: 1.1rem 1.25rem;
  background: var(--bg-alt);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  color: var(--ink-soft);
  font-size: 0.92rem;
}

.report-limits p {
  margin: 0 0 0.6rem;
  text-wrap: pretty;
}

.report-limits p:last-child { margin-bottom: 0; }

.report-limits-heading {
  margin: 0 0 0.5rem;
  font-size: 1rem;
  color: var(--ink);
}

.report-section {
  padding-top: 1.75rem;
  margin-top: 1.75rem;
  border-top: 1px solid var(--line);
}

.report-section h2 {
  font-size: 1.2rem;
  margin-bottom: 0.9rem;
}

/* Measured facts. Single column on a phone, two up once there is room. */
.report-facts {
  margin: 0;
  display: grid;
  gap: 0.9rem;
}

.report-fact dt {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: var(--ink-soft);
}

.report-fact dd {
  margin: 0.15rem 0 0;
  font-size: 1.05rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* The basis tag. The most load-bearing markup on the page: it is what lets a
   reader tell a measured number from an assumed one without reading a legend
   first. Deliberately quiet - it labels, it does not shout - but each variant
   is distinguishable by its border and fill together, never by hue alone. */
.basis-tag {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg-alt);
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  white-space: nowrap;
}

.basis-measured {
  border-color: var(--ink-soft);
  color: var(--ink);
}

.basis-provided {
  border-color: var(--accent);
  background: rgba(180, 68, 31, 0.07);
  color: var(--accent-dark);
}

/* Assumed and allowance are the two that mean "we did not measure this", so
   they share the warning triad the rest of the site uses for unfinished
   things. They should catch the eye; that is the whole point of them. */
.basis-assumed,
.basis-allowance {
  border-color: #e0a800;
  background: #fff4d6;
  color: #5c4600;
}

.report-lines {
  margin-top: 0;
  border-top: none;
  padding-top: 0;
  font-size: 0.95rem;
}

.report-lines li {
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
}

.report-lines .estimate-line-label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.report-subtotal {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 2px solid var(--ink);
  font-weight: 600;
}

.report-subtotal-value {
  font-variant-numeric: tabular-nums;
}

.report-tiers-heading {
  margin: 1.75rem 0 0.75rem;
  font-size: 1rem;
}

.report-tiers {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.report-tier {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.15rem 0.75rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.report-tier-current {
  border-color: var(--accent);
  background: rgba(180, 68, 31, 0.07);
}

.report-tier-name { font-weight: 600; }

.report-tier-note {
  grid-column: 1;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.report-tier-value {
  grid-row: 1 / span 2;
  grid-column: 2;
  align-self: center;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.legend-list {
  margin: 0;
  display: grid;
  gap: 0.6rem;
}

.legend-entry {
  display: grid;
  gap: 0.25rem;
}

.legend-entry dd {
  margin: 0;
  font-size: 0.88rem;
  color: var(--ink-soft);
}

.report-refine .refine-form {
  display: grid;
  gap: 1rem;
  margin-top: 1rem;
  max-width: 32rem;
}

/* The answered state: what they told us, then a quiet way back in. */
.refine-answers {
  margin: 1rem 0 0;
  display: grid;
  gap: 0.75rem;
}

.refine-answer dt {
  font-size: 0.82rem;
  color: var(--ink-soft);
}

.refine-answer dd {
  margin: 0.15rem 0 0;
  font-weight: 600;
}

/* Reads as a link, not a second primary action - the report is finished, and
   changing an answer is the exception. Matches the FAQ's native <details>. */
.refine-reopen {
  margin-top: 1.25rem;
}

.refine-reopen summary {
  cursor: pointer;
  color: var(--accent);
  font-size: 0.9rem;
  font-weight: 600;
  width: fit-content;
  list-style: none;
}

.refine-reopen summary::-webkit-details-marker { display: none; }

.refine-reopen summary::after {
  content: " +";
  font-variant-numeric: tabular-nums;
}

.refine-reopen[open] summary::after { content: " −"; }

.refine-reopen summary:hover { text-decoration: underline; }

.refine-reopen summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.report-precision {
  color: var(--ink-soft);
}

.report-precision h2 { color: var(--ink); }

.report-footer {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}

/* Disambiguation choices - the same selectable-card control as the shingle
   tiers, stacked. */
.address-choices {
  border: 0;
  margin: 0 0 1.25rem;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.address-choice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  min-height: 2.9rem;
}

.address-choice:has(input:checked) {
  border-color: var(--accent);
  background: rgba(180, 68, 31, 0.07);
}

.address-choice:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.address-choice-label {
  font-size: 0.95rem;
}

@media (min-width: 40rem) {
  .report-facts {
    /* 13rem, not 11: a label plus its basis tag needs the room, and at 11 the
       tag wrapped under the label and left the row ragged. */
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    align-items: start;
  }

  .legend-entry {
    grid-template-columns: 7rem 1fr;
    align-items: baseline;
  }

  .refine-answers {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .report-tiers {
    grid-template-columns: repeat(3, 1fr);
  }

  .report-tier {
    grid-template-columns: 1fr;
  }

  .report-tier-value {
    grid-row: auto;
    grid-column: 1;
    align-self: start;
    font-size: 1.1rem;
  }
}

/* The site had exactly one transition before this sheet existed, and so had no
   reason for this block. It does now. Nothing here is load-bearing: the
   processing sequence still runs, still reports each stage, and still lands on
   the same report - it simply stops moving. The JavaScript checks the same
   query and skips the paced reveal, so a user who asked for less motion also
   gets to their number faster rather than watching a frozen sequence. */
.report-download .download-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 1rem 0 0;
}

/* Dashboard rows are links now - the address is what people reach for, so the
   address is what they can hit. */
.request-row {
  padding: 0;
}

.request-link {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  padding: 1.1rem 1.25rem;
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius);
}

.request-link:hover { background: var(--bg-alt); }

.request-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.request-row-main,
.request-status {
  display: grid;
  gap: 0.15rem;
}

.request-status { text-align: right; }

.request-source,
.request-step {
  font-weight: 600;
  font-size: 0.95rem;
}

.request-detail,
.request-status .muted { font-size: 0.85rem; }

.request-total {
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  color: var(--accent-dark);
}

/* Print, which is also "save as PDF" - the browser's own export, rather than
   a PDF dependency in the production image.
 *
 * The disclosure survives. A printed copy is the one that gets forwarded and
 * argued over, so the section explaining what is not measured and what is
 * assumed is exactly the section that must not be dropped to save a page. */
@media print {
  .site-header,
  .site-footer,
  .report-refine,
  .report-download,
  .report-precision,
  .report-footer {
    display: none !important;
  }

  .section { padding: 0; }

  .report-total,
  .report-limits,
  .report-lines li,
  .report-tier {
    break-inside: avoid;
  }

  .report-limits {
    background: none;
    border-left: 2px solid #000;
  }

  .basis-tag {
    border-color: #000 !important;
    background: none !important;
    color: #000 !important;
  }

  a[href]::after { content: ""; }
}

@media (prefers-reduced-motion: reduce) {
  .seg .fill,
  .facet,
  .facet-label,
  .receipt-line {
    transition: none;
  }

  .scan-beam.sweeping {
    animation: none;
  }

  /* The beam is the one element with no still state worth keeping - parked
     off-canvas it is just an invisible strip, so hide it outright. */
  .scan-beam {
    display: none;
  }
}
