/* Test-page specific: the expandable measurement rows. */

#results { margin: 1.5rem 0 0; }

.row, .prow {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-soft);
  margin-bottom: 0.6rem;
}

.row > summary, .prow > summary {
  cursor: pointer;
  padding: 0.8rem 1rem;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1.5rem;
  list-style: none;
  border-radius: var(--radius);
}

/* A metric row keeps its three parts on one line at desktop widths.
   Wrapping is decided from each part's flex-basis before any of them
   shrinks, so three comfortable bases plus the gaps came to more than
   the row is wide and "Population: ..." dropped to a second line at
   every width under about 1400px. Nowrap makes them share the line and
   shrink instead: the label truncates, the value breaks, the population
   text wraps inside its own column. The protected rows are left wrapping
   -- their summary holds two value columns, not three parts, and it is
   meant to reflow. */
.row > summary { flex-wrap: nowrap; }

.row > summary::-webkit-details-marker,
.prow > summary::-webkit-details-marker { display: none; }
.row > summary:hover, .prow > summary:hover { color: var(--accent); }
.row > summary:focus-visible,
.prow > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.row[open] > summary, .prow[open] > summary {
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}

/* The name is allowed a generous basis and then truncates with an
   ellipsis rather than wrapping, because a name like
   maxDynamicStorageBuffersPerPipelineLayout is one unbreakable word that
   used to push "Population: ..." onto a second line and leave the row
   three lines tall. The full name is in the title attribute and again in
   the row's body. */
.row-label {
  font-weight: 650;
  font-size: 1rem;
  /* Shrinkable, with a floor. Measured across the real page at row
     widths of 1354, 952, 880, 800 and 720 CSS pixels: the label lands
     between 18rem and the floor, and without the floor the two columns
     beside it squeeze it to a few pixels below about 700. The floor is
     the fixed width this column used to have, so the label is never
     narrower than it was before it could truncate. */
  flex: 0 1 18rem;
  min-width: 9rem;
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  color: var(--fg);
}

.row-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-value { flex: 1 1 15rem; min-width: 0; }
.row-value code { word-break: break-all; }

.row-pop {
  flex: 1 1 20rem;
  min-width: 0;
  color: var(--fg-soft);
  font-size: 0.94rem;
}

.row-value .k, .row-pop .k {
  color: var(--fg-faint);
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  font-weight: 600;
  margin-right: 0.15rem;
}

/* Disclosure triangle, drawn so it can rotate on open. */
.row-chevron {
  flex: 0 0 auto;
  margin-left: auto;
  width: 0.6rem;
  height: 0.6rem;
  border-right: 2px solid var(--fg-faint);
  border-bottom: 2px solid var(--fg-faint);
  transform: rotate(45deg);
  transition: transform 120ms ease;
}

.row[open] .row-chevron,
.prow[open] .row-chevron { transform: rotate(-135deg); }

.row-body {
  padding: 0.35rem 1rem 1.1rem;
  border-top: none;
}

.row-body > h3:first-of-type { margin-top: 1.25rem; }
.row-body p { max-width: var(--measure); }

#status {
  color: var(--fg-soft);
  font-size: 0.94rem;
  min-height: 1.5em;
  margin: 0.5rem 0 0;
}

/* Sits at the foot of the page, after the results and notes. */
#context {
  margin-top: 3rem;
  font-size: 0.94rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1.1rem;
  background: var(--bg-soft);
  max-width: var(--measure);
}

#context p { margin: 0 0 0.4rem; }

@media (max-width: 40rem) {
  /* On a phone there is no line to share: the label takes one of its
     own, in full, and the value and population follow underneath. */
  .row > summary { flex-wrap: wrap; }
  .row-label { flex-basis: 100%; }
  .row-name { overflow: visible; white-space: normal; }
  .row-chevron { display: none; }
}

/* ---------- Greyed "not measured" boxes ---------- */
/* Named for the section that first used them; that section is gone, but
   gatedBox() in app.js still renders this shape for every metric the page
   declines to compare. */

.unmeasurable-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.75rem;
}

.unmeasurable-label { font-weight: 600; font-size: 0.95rem; }

/* Deliberately grey, not red: this is not a failure or a bad result. */
.unmeasurable-tag {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 650;
  color: var(--fg-faint);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.08rem 0.5rem;
  white-space: nowrap;
}

.unmeasurable-note {
  font-size: 0.9rem;
  color: var(--fg-soft);
  max-width: var(--measure);
  margin-top: 0.2rem;
}

.unmeasurable-ids { margin-top: 0.3rem; }

.unmeasurable-ids code {
  font-size: 0.76rem;
  color: var(--fg-faint);
  margin-right: 0.2rem;
}

/* Gated by an active protection: rendered but explicitly not measured. */
#etp-gated { margin-top: 3rem; }
#etp-gated > div:first-child h2 { margin-top: 0; }

.card.gated {
  opacity: 0.6;
  background: var(--bg-inset);
  padding: 0.85rem 1.1rem;
}

.card.gated .unmeasurable-label { color: var(--fg-soft); }

/* Marks a row whose values are inferred rather than reproduced exactly. */
/* Remembered rather than measured this visit. Same shape as the
   approximated badge -- it is the same kind of claim about the value --
   but its own colour, because the reason is different. */
.row-remembered { background: var(--aqua-soft); color: var(--aqua); }

.row-approx, .badge-exp, .row-protected {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  color: var(--warn);
  background: var(--warn-soft);
  border-radius: 3px;
  padding: 0.05rem 0.3rem;
  vertical-align: 0.1em;
}

/* A value a protection altered, which is compared all the same. Lime
   rather than the amber of an approximation: nothing here is uncertain,
   and the row says which value it is showing. */
.row-protected {
  color: var(--prot);
  background: var(--prot-soft);
}

/* Distribution rows for values not seen on the visitor's own OS. Hidden by
   default and revealed by the toggle in the paragraph above the table.
   Uses a class rather than the `hidden` attribute so that a future
   `display` rule on `tr` cannot silently un-hide them. */
.data-table tr.dist-other-os { display: none; }
.data-table tr.dist-other-os.is-revealed { display: table-row; }

/* A control that reads as a link. */
/* "Learn more" disclosure inside a callout. */
.learn-more { margin-top: 0.5rem; }

.learn-more > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9rem;
  width: fit-content;
}

.learn-more > summary:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: 3px;
}

.learn-more-body { margin-top: 0.6rem; font-size: 0.94rem; }
.learn-more-body > p:last-of-type { margin-bottom: 0.35rem; }

.learn-more-body ul {
  margin: 0;
  padding-left: 1.2rem;
}

.learn-more-body li { margin-bottom: 0.4rem; }
.learn-more-body li:last-child { margin-bottom: 0; }

/* ---------- Collapsible metric-family sections ---------- */

.section {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 0.6rem;
  background: var(--bg-inset);
}

.section > summary {
  cursor: pointer;
  padding: 0.85rem 1rem;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 1.25rem;
  list-style: none;
  border-radius: var(--radius);
}

.section > summary::-webkit-details-marker { display: none; }
.section > summary:hover .section-label { color: var(--accent); }
.section > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.section[open] > summary {
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}

.section-label {
  font-weight: 680;
  font-size: 1.02rem;
  /* A 9rem minimum, so the counts beside each section line up, but it
     grows past that rather than wrapping: "Protected Attributes" is
     wider than 9rem and was breaking across two lines. */
  flex: 0 0 auto;
  min-width: 9rem;
}

.section-count,
.section-rarest {
  font-size: 0.9rem;
  color: var(--fg-soft);
}

.section-body { padding: 0.75rem 0.75rem 0.35rem; }

/* Rows nested in a section sit on the section's inset background, so give
   them the page background back to keep the nesting legible. */
.section-body .row, .section-body .prow { background: var(--bg); }

.section-blurb {
  margin: 0 0.25rem 0.7rem;
  font-size: 0.92rem;
  color: var(--fg-soft);
}

/* Intro for the skipped-measurement boxes, which now sit at the end of the
   results list rather than in a section of their own. */
.gated-intro { margin: 2rem 0 0.75rem; }
.gated-intro h3 { margin: 0 0 0.35rem; }
.gated-intro p { margin: 0; max-width: var(--measure); font-size: 0.94rem; }

/* The visitor's own operating system column, which leads the table. */
.data-table th.is-your-os,
.data-table td.is-your-os {
  background: var(--accent-soft);
}

.data-table th.is-your-os { color: var(--fg); }

/* Keep the highlight readable on the visitor's own row, which is already
   tinted the same colour. */
.data-table tr.is-you td.is-your-os { background: transparent; }

/* A blurb that explains how a row's question was put. Prose, not a
   callout: it warns about nothing, so it gets no rule and no tint. Same
   size and spacing as the provenance line below it. */
.metric-info {
  margin: 0 0 0.9rem;
  max-width: var(--measure);
  font-size: 0.94rem;
}

/* Where a value comes from, and how faithfully it is reproduced. */
.provenance { margin: 0 0 0.9rem; font-size: 0.92rem; }
.provenance p { margin: 0 0 0.25rem; max-width: var(--measure); }
.provenance-warn { color: var(--warn); }
/* The database metric name, at the foot of each box. */
.metric-id {
  margin: 0.75rem 0 0;
  text-align: right;
  font-size: 0.78rem;
  color: var(--fg-faint);
}

.metric-id code { font-size: 0.95em; }

/* ---------- Combined (derived) metrics ---------- */

/* A combined metric is computed from other metrics rather than measured, so
   it is marked with diagonal stripes. Applied to the summary bar only: the
   expanded body holds a table, and striping behind it hurts legibility for
   no gain. */
.row.is-combined > summary {
  background-image: repeating-linear-gradient(
    45deg,
    transparent 0 7px,
    var(--stripe) 7px 14px
  );
}

/* Keep the value chip and labels readable over the stripes. */
.row.is-combined > summary .row-label,
.row.is-combined > summary .row-value code {
  background: var(--bg-soft);
  padding: 0 0.25rem;
  border-radius: 3px;
}

/* Rank column, and the gap row standing in for the ranks not shown. */
.data-table td.dist-rank { color: var(--fg-soft); font-weight: 600; }
.data-table tr.dist-ellipsis td { text-align: center; letter-spacing: 0.2em; }
.data-table tr.dist-ellipsis { background: var(--bg-inset); }

/* The visitor's own value across all four populations, above the ranked
   single-population table. */
.cross-pop { font-size: 0.9rem; margin: 0.75rem 0 0.35rem; }
.cross-pop .is-your-os {
  background: var(--accent-soft);
  border-radius: 3px;
  padding: 0 0.3rem;
  color: var(--fg);
  font-weight: 600;
}

/* ---------- Progress ---------- */

#progress { margin: 0.75rem 0 1rem; max-width: var(--measure); }

.progress-track {
  height: 8px;
  border-radius: 999px;
  background: var(--bg-inset);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: 999px;
  transition: width 120ms linear;
}

.progress-label {
  margin: 0.4rem 0 0;
  font-size: 0.88rem;
  color: var(--fg-soft);
  font-variant-numeric: tabular-nums;
}

/* ---------- What is measured (previews) ---------- */

.preview {
  margin: 0 0 1rem;
  padding: 0;
  max-width: var(--measure);
}

/* Canvas scenes are drawn on a transparent background, so the checkerboard
   is load-bearing: without it a transparent region is indistinguishable
   from a white one, and several of these scenes are mostly transparent. */
.preview-image {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 4px;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #e9ecf1 25%, transparent 25%, transparent 75%,
                    #e9ecf1 75%),
    linear-gradient(45deg, #e9ecf1 25%, transparent 25%, transparent 75%,
                    #e9ecf1 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
  image-rendering: pixelated;
}

/* Previews of laid-out elements render in their own document. It is white
   regardless of the page theme: these are reproductions of something drawn
   on Firefox's own collection page, which has a light background, and
   recolouring them would misreport what was measured. */
.preview-frame {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: #fff;
  color-scheme: light;
}

.preview figcaption {
  margin: 0.4rem 0 0;
  font-size: 0.88rem;
  color: var(--fg-soft);
  max-width: var(--measure);
}

/* ---------- Distribution reveal controls ---------- */

.dist-controls .link-button { text-align: left; }

/* The gap marker is also the control that fills the gap. It has to look
   clickable, or it reads as decoration and the affordance is invisible. */
.data-table tr.dist-ellipsis-click td { cursor: pointer; }

.data-table tr.dist-ellipsis-click:hover {
  background: var(--accent-soft);
}

.data-table tr.dist-ellipsis-click:hover td {
  color: var(--accent);
  letter-spacing: 0.2em;
}

.data-table tr.dist-ellipsis-click:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The own-OS share is the figure that matters on a collapsed row: a
   fingerprinter can tell your platform trivially, so that is the population
   you are actually hiding in. The all-users figure is context, and context
   belongs in the expanded view -- it appears after the own-OS share once the
   row is open, in the same Population line. */
.row > summary .row-pop .pop-all { display: none; }
.row[open] > summary .row-pop .pop-all { display: inline; }

/* ---------- Combined-metric explainer ---------- */

/* Informational rather than cautionary: the yellow warn callouts on these
   rows mean "this number may be wrong", and this box means the opposite --
   here is what the number is made of. */
.callout.info {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.combined-covers {
  margin: 0.5rem 0 0;
  font-size: 0.92rem;
  color: var(--fg-soft);
}

/* ---------- The measured value behind a digest ---------- */

.measured-value {
  margin: 0 0 0.9rem;
  padding: 0.55rem 0.7rem;
  background: var(--bg-inset);
  border-radius: 4px;
}

.measured-value .k {
  display: block;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-soft);
  margin-bottom: 0.25rem;
}

/* These are JSON blobs of sub-pixel coordinates, so they have to wrap
   rather than stretch the box. */
.measured-value code {
  display: block;
  font-size: 0.85rem;
  word-break: break-all;
  line-height: 1.45;
}

.measured-value p {
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
  max-width: var(--measure);
}

/* The reveal offer under the table is a control, not prose: smaller than
   body text so the table stays the thing being read. */
.dist-controls-row {
  margin: 0.5rem 0 0.9rem;
  font-size: 0.85rem;
}

.dist-controls-row .link-button { font-size: inherit; }

/* A reason shared by every declined row in a section, given once above
   them rather than repeated in each box. */
.section-declined {
  margin: 0 0 0.9rem;
  padding: 0.6rem 0.75rem;
  background: var(--bg-inset);
  border-left: 3px solid var(--fg-faint);
  border-radius: 4px;
  font-size: 0.92rem;
  color: var(--fg-soft);
  max-width: var(--measure);
}

/* ---------- Protected Attributes ---------- */

.section-protected .prow > summary {
  display: grid;
  grid-template-columns: 1fr auto 1.2rem;
  gap: 0.5rem 1rem;
  align-items: baseline;
}

.protected-label { font-weight: 600; font-size: 0.95rem; }

.protected-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(7rem, 14rem));
  gap: 0.75rem;
}

.protected-cell { display: flex; flex-direction: column; gap: 0.15rem; }
.protected-cell .k {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fg-faint);
}
.protected-cell code {
  font-size: 0.78rem;
  overflow-wrap: anywhere;
}
/* The share, not the value. Its colour comes from the population classes
   every other row uses, so "common" and "rare" read the same here. */
.protected-share { font-size: 0.85rem; }

/* What the protection is worth on this row, applied to the unprotected
   column: amber for any loss of the protected crowd, red past 15% of it,
   and past 30% the shade otherwise kept for a value that is not in the
   published data. The scale is the page's own commonness scale, so it
   means the same thing here as everywhere else. Two classes deep, to win
   against the pop-* colour on the span inside. */
/* Two figures that print the same: the protection changed nothing
   visible here, and both sides say so. */
.protected-share.prot-agree span { color: var(--pop-good); }

.protected-share.prot-worse-1 span { color: var(--pop-mid); }
.protected-share.prot-worse-2 span { color: var(--pop-bad); }
.protected-share.prot-worse-3 span { color: var(--pop-none); }

/* Not an absence: the protection working. Green, and not italic like the
   greyed notes beside it. */
.protected-randomized {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--good);
}

/* The words in a greyed half that take the reader to the next step. It
   sits inside a <summary>, so its own click handler stops the row from
   opening as well. */
.protected-jump {
  font-size: inherit;
  font-style: normal;
}

.protected-missing {
  font-size: 0.78rem;
  color: var(--fg-faint);
  font-style: italic;
}

/* Two populations, side by side by default and stacked on request. */
.protected-tables {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin: 0.75rem 0;
}
.protected-tables.is-stacked { grid-template-columns: 1fr; }
.protected-table { min-width: 0; }
.protected-table h4 {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg-soft);
  margin: 0 0 0.35rem;
}
/* Shrunk only while two of them share the width. Stacked, each table has
   the whole column and there is nothing to save. */
.protected-table .data-table { font-size: 0.78rem; }
.protected-tables.is-stacked .data-table { font-size: 0.9rem; }

/* The numbered hand-off. Tight, because it sits inside a callout that is
   already indented, and the third step is a sentence rather than a
   label. */
.protected-steps {
  margin: 0.5rem 0 0;
  padding-left: 1.4rem;
  font-size: 0.9rem;
}
.protected-steps li { margin: 0.35rem 0; }
.protected-steps kbd {
  font-family: var(--mono, monospace);
  font-size: 0.85em;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.05rem 0.3rem;
  background: var(--bg-soft);
}
/* The link the reader right-clicks. It carries a long fragment, so it is
   labelled rather than shown: the href is what matters, and a wall of
   percent-encoding would bury the instruction above it. */
.import-anchor {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.btn-small {
  font-size: 0.8rem;
  padding: 0.25rem 0.6rem;
}

@media (max-width: 40rem) {
  .section-protected .prow > summary {
    grid-template-columns: 1fr 1.2rem;
  }
  .protected-cols { grid-column: 1 / -1; }
  .protected-tables { grid-template-columns: 1fr; }
}

/* ---------- Display-scale change banner ---------- */

/* Fixed to the bottom of the viewport: the visitor is zooming or moving
   the window when it appears, not reading the top of the results. */
.scale-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem clamp(1rem, 4vw, 2.5rem);
  background: var(--aqua-soft);
  border-top: 2px solid var(--aqua);
  box-shadow: 0 -2px 12px rgba(22, 25, 29, 0.12);
}
/* The text takes whatever is left, which pushes the button and the
   dismiss control to the right edge. Capping the text's measure instead
   would leave the controls stranded mid-banner with empty space beyond
   them. `min-width: 0` lets it shrink past its longest word rather than
   forcing the controls off the end. */
.scale-banner-text {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.92rem;
  line-height: 1.5;
}
.scale-banner .btn { flex: none; }

.scale-banner-close {
  flex: none;
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--fg-soft);
  font-size: 1.5rem;
  line-height: 1;
  padding: 0.1rem 0.5rem;
  border-radius: 6px;
  cursor: pointer;
}
.scale-banner-close:hover {
  background: var(--bg);
  color: var(--fg);
}
.scale-banner-close:focus-visible {
  outline: 2px solid var(--aqua);
  outline-offset: 1px;
}

/* Without this the banner covers the end of the page and no amount of
   scrolling reaches the footer. */
body.has-scale-banner { padding-bottom: 7rem; }

@media (max-width: 640px) {
  .scale-banner {
    flex-wrap: wrap;
    gap: 0.6rem;
  }
  .scale-banner-text { flex-basis: 100%; }
  .scale-banner-close { margin-left: auto; }
  body.has-scale-banner { padding-bottom: 11rem; }
}
/* The confirmation before a diagnostic report is sent (ritter.vg only). */
.send-panel { margin-top: 0.75rem; }
.send-panel textarea { width: 100%; box-sizing: border-box; font: inherit; }
.send-panel #send-preview { font-family: var(--mono); font-size: 0.8rem; }
.send-note-label { display: block; margin: 0.5rem 0 0.25rem; }
.send-panel details { margin: 0.5rem 0; }

/* The hidden attribute must win over any display a rule sets: the copy
   fallback textarea (.test-tools textarea is display: block) showed as an
   empty box under the buttons until the clipboard had actually failed. */
[hidden] { display: none !important; }
