/* ============================================================================
   components.css — the 17 components of the Energia Coop build contract (§5)
   Load order is load-bearing: main -> components -> responsive
   Tokens live in main.css. Every @media lives in responsive.css. Neither is
   redefined or duplicated here.
   ========================================================================= */


/* ===== 1. StakeholderCard ===== */
/* Migrated from the inline sheet. Visual output is unchanged; only the
   modifier names moved to BEM (.card-blue -> .stakeholder-card--blue). */
.stakeholder-card {
  border-radius: var(--radius-lg);
  padding: 40px 32px;
  color: var(--white);
  position: relative;
  overflow: hidden;
  transition: transform var(--transition), box-shadow var(--transition);
}

.stakeholder-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg);
}

.stakeholder-card::after {
  content: '';
  position: absolute;
  top: -50%;
  right: -30%;
  width: 200px;
  height: 200px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 50%;
}

.stakeholder-card--blue   { background: linear-gradient(145deg, var(--deep-blue), #0a3d75); }
.stakeholder-card--forest { background: linear-gradient(145deg, var(--forest-green), #557a3d); }
.stakeholder-card--green  { background: linear-gradient(145deg, var(--light-green), var(--forest-green)); }

.stakeholder-card__icon {
  width: 56px;
  height: 56px;
  background: rgba(255, 255, 255, 0.15);
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--sp-5);
}

.stakeholder-card__icon svg {
  width: 28px;
  height: 28px;
  fill: none;
  stroke: var(--white);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.stakeholder-card h3 {
  font-size: 1.3rem;
  font-weight: 700;
  margin-bottom: var(--sp-3);
}

.stakeholder-card__desc {
  font-size: 0.92rem;
  opacity: 0.85;
  margin-bottom: 20px;
  line-height: 1.6;
}

.stakeholder-card ul { margin-bottom: 28px; }

.stakeholder-card ul li {
  font-size: 0.88rem;
  padding: 6px 0;
  padding-left: 20px;
  position: relative;
  opacity: 0.9;
}

.stakeholder-card ul li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 13px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
}

.stakeholder-card .btn {
  width: 100%;
  justify-content: center;
  background: rgba(255, 255, 255, 0.15);
  border: 1.5px solid rgba(255, 255, 255, 0.3);
  color: var(--white);
  padding: var(--sp-3) var(--sp-5);
}

.stakeholder-card .btn:hover {
  background: var(--white);
  color: var(--deep-blue);
  border-color: var(--white);
}

/* Flip variant. The card itself is the perspective box; the two faces are
   absolutely positioned so they overlap and carry the padding the flat card
   would otherwise own. min-height keeps the box from collapsing to zero. */
.stakeholder-card[data-flip] {
  padding: 0;
  min-height: 420px;
  perspective: 1200px;
  overflow: visible;
}

.stakeholder-card[data-flip]::after { display: none; }

.stakeholder-card__front,
.stakeholder-card__back {
  position: absolute;
  inset: 0;
  padding: 40px 32px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  background: inherit;
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
  display: flex;
  flex-direction: column;
}

.stakeholder-card__front { transform: rotateY(0deg); }
.stakeholder-card__back  { transform: rotateY(180deg); }

/* Desktop affordance. responsive.css kills this under @media (hover:none) and
   interactive.js takes over with a click-toggled .is-flipped. */
.stakeholder-card[data-flip]:hover .stakeholder-card__front,
.stakeholder-card[data-flip].is-flipped .stakeholder-card__front { transform: rotateY(-180deg); }

.stakeholder-card[data-flip]:hover .stakeholder-card__back,
.stakeholder-card[data-flip].is-flipped .stakeholder-card__back { transform: rotateY(0deg); }

.stakeholder-card[data-flip]:hover { transform: none; }


/* ===== 2. StatCounter ===== */
.stat-counter {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  color: var(--deep-blue);
  letter-spacing: -0.5px;
  display: inline-block;
}

.stat-counter--lg { font-size: 2.6rem; line-height: 1.1; }
.stat-counter--on-dark { color: var(--white); }

.stat-counter__label {
  display: block;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--body-text);
  opacity: 0.7;
  letter-spacing: 0;
}


/* ===== 3. InfoCard ===== */
/* Migrated from .knowledge-card. Visuals unchanged. The icon color moved from
   :nth-child(1..6) to [data-accent] — nth-child silently recolors every card
   after any reorder or insertion, which the contract (§5) forbids. */
.info-card {
  background: var(--white);
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
  transition: all var(--transition);
  display: flex;
  flex-direction: column;
}

.info-card:hover {
  border-color: var(--sky-blue);
  box-shadow: var(--shadow-md);
  transform: translateY(-4px);
}

.info-card__icon {
  width: 52px;
  height: 52px;
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 20px;
  background: var(--deep-blue);
}

.info-card__icon svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: var(--white);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.info-card[data-accent="deep-blue"] .info-card__icon { background: var(--deep-blue); }
.info-card[data-accent="forest"]    .info-card__icon { background: var(--forest-green); }
.info-card[data-accent="sky"]       .info-card__icon { background: var(--sky-blue); }
.info-card[data-accent="green"]     .info-card__icon { background: var(--light-green); }

.info-card h3 {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--deep-blue);
  margin-bottom: var(--sp-2);
}

.info-card p {
  font-size: 0.88rem;
  color: var(--body-text);
  opacity: 0.75;
  line-height: 1.6;
  margin-bottom: 20px;
  flex: 1;
}

.info-card__link {
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--sky-blue);
  transition: color var(--transition);
}

.info-card:hover .info-card__link { color: var(--deep-blue); }


/* ===== 4. ProcessTimeline ===== */
/* Migrated from .steps-timeline/.step. The 4-step case is pixel-identical to
   the original; the component is no longer capped at 4.
   nth-child survives here — and ONLY here — because a timeline's order IS its
   meaning: step 3 is third because it happens third. Reordering is a content
   change, not a reshuffle. Everywhere else (.info-card, .download-card) the
   contract requires data-attributes instead. */
.process-timeline {
  display: grid;
  /* Step-count agnostic. minmax(0,1fr) — not 1fr — or a long step title sets
     the column's min-content width and blows the row out. */
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: var(--sp-5);
  position: relative;
  margin-top: 20px;
  --pt-inset: 12.5%;
}

/* The connector must start/end at the centre of the first/last circle, i.e.
   inset by half a column = 50%/N. CSS can't count children, so :has() sets it
   per step-count. Ascending order matters: a 6-step timeline matches both the
   5- and 6-step rules, and the last match wins. */
.process-timeline:has(> :nth-child(5))  { --pt-inset: 10%; }
.process-timeline:has(> :nth-child(6))  { --pt-inset: 8.333%; }
.process-timeline:has(> :nth-child(7))  { --pt-inset: 7.143%; }
.process-timeline:has(> :nth-child(8))  { --pt-inset: 6.25%; }

.process-timeline::before {
  content: '';
  position: absolute;
  top: 48px;
  left: var(--pt-inset);
  right: var(--pt-inset);
  height: 3px;
  background: linear-gradient(90deg, var(--deep-blue), var(--sky-blue), var(--forest-green), var(--light-green));
  border-radius: 2px;
}

.process-timeline__step {
  text-align: center;
  position: relative;
  z-index: var(--z-base);
}

.process-timeline__head {
  display: block;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: center;
}

.process-timeline__number {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 20px;
  font-size: 1.8rem;
  font-weight: 800;
  color: var(--white);
  position: relative;
  transition: transform var(--transition), box-shadow var(--transition);
}

/* 4n+k cycles the four phase colours indefinitely, so steps 1-4 keep their
   original colours exactly and step 5+ continues the cycle instead of
   rendering an invisible (background-less) circle. */
.process-timeline__step:nth-child(4n+1) .process-timeline__number { background: var(--phase-1); }
.process-timeline__step:nth-child(4n+2) .process-timeline__number { background: var(--phase-2); }
.process-timeline__step:nth-child(4n+3) .process-timeline__number { background: var(--phase-3); }
.process-timeline__step:nth-child(4n+4) .process-timeline__number { background: var(--phase-4); }

.process-timeline__head:hover .process-timeline__number,
.process-timeline__step.is-current .process-timeline__number {
  transform: scale(1.06);
  box-shadow: var(--shadow-md);
}

.process-timeline__icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.process-timeline__icon svg {
  width: 36px;
  height: 36px;
  fill: none;
  stroke: var(--white);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.process-timeline__step h3 {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--deep-blue);
  margin-bottom: var(--sp-2);
}

.process-timeline__step p {
  font-size: 0.88rem;
  color: var(--body-text);
  opacity: 0.75;
  line-height: 1.5;
  max-width: 220px;
  margin: 0 auto var(--sp-2);
}

.process-timeline__time {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--forest-green);
  background: rgba(157, 195, 132, 0.15);
  padding: var(--sp-1) 14px;
  border-radius: 20px;
  display: inline-block;
}

.process-timeline__panel {
  overflow: hidden;
  font-size: 0.85rem;
  color: var(--body-text);
  opacity: 0.85;
  text-align: left;
  margin-top: var(--sp-3);
}

.process-timeline__head[aria-expanded="false"] + .process-timeline__panel { display: none; }


/* ===== 5. DataTable ===== */
.data-table__wrap {
  overflow-x: auto;
  overflow-y: auto;
  max-height: 70vh;
  -webkit-overflow-scrolling: touch;
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius);
  background: var(--white);
}

.data-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 0.88rem;
  min-width: 640px;
}

.data-table thead th {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: var(--light-bg);
  color: var(--deep-blue);
  font-weight: 700;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 1px;
  text-align: left;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 2px solid var(--light-cyan);
  white-space: nowrap;
}

.data-table th[data-sort="text"],
.data-table th[data-sort="num"] {
  cursor: pointer;
  user-select: none;
  padding-right: var(--sp-6);
  position: sticky;
}

.data-table th[data-sort="text"]::after,
.data-table th[data-sort="num"]::after {
  content: '\2195';
  position: absolute;
  right: var(--sp-3);
  opacity: 0.35;
  font-size: 0.9rem;
}

.data-table th[aria-sort="ascending"]::after  { content: '\2191'; opacity: 1; color: var(--sky-blue); }
.data-table th[aria-sort="descending"]::after { content: '\2193'; opacity: 1; color: var(--sky-blue); }

.data-table td {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid rgba(12, 73, 138, 0.06);
  vertical-align: top;
}

.data-table tbody tr:hover { background: var(--light-bg); }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr[hidden] { display: none; }

.data-table__num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table__null { opacity: 0.5; font-style: italic; }

.data-table__caption {
  font-size: 0.78rem;
  color: var(--body-text);
  opacity: 0.7;
  margin-top: var(--sp-2);
}



/* ===== 6. AccordionFAQ ===== */
.accordion {
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--white);
}

.accordion__item + .accordion__item { border-top: 1px solid rgba(12, 73, 138, 0.08); }

.accordion__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--deep-blue);
  cursor: pointer;
  list-style: none;
  transition: background var(--transition);
}

/* Kill the native disclosure triangle in both engines */
.accordion__head::-webkit-details-marker { display: none; }
.accordion__head::marker { content: ''; }

.accordion__head:hover { background: var(--light-bg); }

/* The +/- indicator. Content-driven so it needs no JS to be correct. */
.accordion__head::after {
  content: '+';
  flex: none;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--light-cyan);
  color: var(--deep-blue);
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1;
  transition: background var(--transition), color var(--transition);
}

.accordion__item[open] > .accordion__head::after {
  content: '\2212';
  background: var(--deep-blue);
  color: var(--white);
}

.accordion__item[open] > .accordion__head { background: var(--light-bg); }

/* Optional authored glyph. interactive.js writes +/− into it when present, so
   the CSS ::after indicator would double up — suppress it in that case. */
.accordion__icon {
  flex: none;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--light-cyan);
  color: var(--deep-blue);
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1;
  transition: background var(--transition), color var(--transition);
}

.accordion__head:has(.accordion__icon)::after { content: none; }

.accordion__item[open] > .accordion__head .accordion__icon {
  background: var(--deep-blue);
  color: var(--white);
}

.accordion__body {
  padding: 0 var(--sp-5) var(--sp-5);
  font-size: 0.9rem;
  line-height: 1.7;
  color: var(--body-text);
}


/* ===== 7. CalculatorPanel ===== */
/* Migrated from .calc-card. All range-thumb rules (webkit + moz) are carried
   over byte-for-byte — thumb styling has no shorthand and no fallback. */
.calc-panel {
  background: var(--white);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.calc-panel__header {
  padding: 28px 32px;
  color: var(--white);
}

.calc-panel__header--solar     { background: linear-gradient(135deg, var(--deep-blue), var(--sky-blue)); }
.calc-panel__header--municipal { background: linear-gradient(135deg, var(--forest-green), var(--light-green)); }

.calc-panel__header h3 {
  font-size: 1.3rem;
  font-weight: 700;
  margin-bottom: var(--sp-1);
}

.calc-panel__header p {
  font-size: 0.85rem;
  opacity: 0.85;
}

.calc-panel__body { padding: 28px 32px; }

.calc-panel__field { margin-bottom: 20px; }

.calc-panel__field label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--deep-blue);
  margin-bottom: var(--sp-2);
}

.calc-panel__field label span {
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--body-text);
  opacity: 0.6;
}

.calc-panel__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.calc-panel__row input[type="range"] {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  border-radius: 3px;
  background: var(--light-cyan);
  outline: none;
}

.calc-panel__row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--deep-blue);
  cursor: pointer;
  border: 3px solid var(--white);
  box-shadow: 0 2px 6px rgba(12, 73, 138, 0.3);
}

.calc-panel__row input[type="range"]::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--deep-blue);
  cursor: pointer;
  border: 3px solid var(--white);
  box-shadow: 0 2px 6px rgba(12, 73, 138, 0.3);
}

.calc-panel__row input[type="number"] {
  width: 90px;
  padding: var(--sp-2) 10px;
  border: 1.5px solid rgba(12, 73, 138, 0.15);
  border-radius: var(--sp-2);
  font-family: 'Open Sans', 'PT Sans', sans-serif;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--deep-blue);
  text-align: right;
  outline: none;
  transition: border-color var(--transition);
}

.calc-panel__row input[type="number"]:focus { border-color: var(--sky-blue); }

.calc-panel__row input[type="number"].has-error { border-color: var(--status-alert); }

.calc-panel__select {
  width: 100%;
  padding: 10px var(--sp-3);
  border: 1.5px solid rgba(12, 73, 138, 0.15);
  border-radius: var(--sp-2);
  font-family: 'Open Sans', 'PT Sans', sans-serif;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--deep-blue);
  background: var(--white);
  outline: none;
  transition: border-color var(--transition);
}

.calc-panel__select:focus { border-color: var(--sky-blue); }

.calc-panel__results {
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 2px solid var(--light-cyan);
}

.calc-panel__results h4 {
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--forest-green);
  margin-bottom: var(--sp-4);
  font-weight: 700;
}

.calc-panel__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-3);
}

.calc-panel__out {
  background: var(--light-bg);
  border-radius: var(--radius);
  padding: var(--sp-4);
  text-align: center;
}

.calc-panel__value {
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--deep-blue);
  display: block;
  margin-bottom: var(--sp-1);
  font-variant-numeric: tabular-nums;
}

.calc-panel__out-label {
  font-size: 0.75rem;
  color: var(--body-text);
  opacity: 0.7;
  font-weight: 500;
  line-height: 1.3;
}

.calc-panel__out--highlight {
  background: linear-gradient(135deg, var(--deep-blue), var(--sky-blue));
  color: var(--white);
}

.calc-panel__out--highlight .calc-panel__value { color: var(--white); }

.calc-panel__out--highlight .calc-panel__out-label {
  color: rgba(255, 255, 255, 0.8);
  opacity: 1;
}

/* irr() returns null -> "n/a". Never a fabricated number, and never styled to
   look like a real figure. */
.calc-panel__value.is-na {
  font-size: 1rem;
  font-weight: 600;
  opacity: 0.55;
  font-style: italic;
}

.calc-panel__note {
  font-size: 0.75rem;
  color: var(--body-text);
  opacity: 0.7;
  margin-top: var(--sp-3);
  line-height: 1.5;
}

.calc-panel__actions {
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-top: var(--sp-4);
}


/* ===== 8. CaseStudyCard ===== */
.case-card {
  background: var(--white);
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
}

.case-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--sky-blue);
}

.case-card__media {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--light-bg);
  overflow: hidden;
}

.case-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.case-card__body {
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  flex: 1;
}

/* Flag from the data attribute — no per-card markup, no image request. */
.case-card__flag::before {
  content: '\1F3F3';
  margin-right: var(--sp-2);
  font-size: 1.1rem;
  line-height: 1;
}

.case-card[data-country="nl"] .case-card__flag::before { content: '\1F1F3\1F1F1'; }
.case-card[data-country="dk"] .case-card__flag::before { content: '\1F1E9\1F1F0'; }
.case-card[data-country="de"] .case-card__flag::before { content: '\1F1E9\1F1EA'; }
.case-card[data-country="co"] .case-card__flag::before { content: '\1F1E8\1F1F4'; }

.case-card__flag {
  display: inline-flex;
  align-items: center;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--forest-green);
  margin-bottom: var(--sp-2);
}

.case-card h3 {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--deep-blue);
  margin-bottom: var(--sp-2);
}

.case-card p {
  font-size: 0.88rem;
  color: var(--body-text);
  opacity: 0.75;
  line-height: 1.6;
  margin-bottom: var(--sp-4);
  flex: 1;
}

.case-card__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-2);
  padding-top: var(--sp-4);
  border-top: 1px solid rgba(12, 73, 138, 0.08);
}

.case-card__stat { text-align: center; }

.case-card__stat-value {
  display: block;
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--deep-blue);
  font-variant-numeric: tabular-nums;
}

.case-card__stat-label {
  font-size: 0.68rem;
  color: var(--body-text);
  opacity: 0.65;
  line-height: 1.3;
}


/* ===== 9. ProgressTracker ===== */
.progress-tracker {
  display: flex;
  flex-direction: column;
  gap: 0;
  counter-reset: pt;
}

.progress-tracker__step {
  position: relative;
  padding-left: var(--sp-7);
  padding-bottom: var(--sp-5);
  counter-increment: pt;
}

/* The connective spine. Stops at the last step so the line never dangles. */
.progress-tracker__step::before {
  content: '';
  position: absolute;
  left: 15px;
  top: 32px;
  bottom: 0;
  width: 2px;
  background: rgba(12, 73, 138, 0.12);
}

.progress-tracker__step:last-child::before { display: none; }

.progress-tracker__step::after {
  content: counter(pt);
  position: absolute;
  left: 0;
  top: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--white);
  border: 2px solid rgba(12, 73, 138, 0.15);
  color: var(--body-text);
  font-size: 0.8rem;
  font-weight: 700;
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}

/* `__label` is overloaded: inside a step it is the step title (--static), but
   at tracker level interactive.js writes "3 de 8 completados" into it
   (--checklist). Scope to the step so the two don't inherit each other. */
.progress-tracker__step .progress-tracker__label {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--deep-blue);
  line-height: 32px;
}

.progress-tracker__label {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--forest-green);
  font-variant-numeric: tabular-nums;
}

.progress-tracker__pct {
  font-size: 0.8rem;
  font-weight: 800;
  color: var(--deep-blue);
  font-variant-numeric: tabular-nums;
}

.progress-tracker__desc {
  font-size: 0.85rem;
  color: var(--body-text);
  opacity: 0.75;
  line-height: 1.6;
}

/* --static: CSS-only. data-current="3" means steps 1..2 are done, 3 is active.
   General sibling combinator gives "everything after the current one". */
.progress-tracker--static .progress-tracker__step {
  color: var(--forest-green);
}

.progress-tracker--static .progress-tracker__step::after {
  content: '\2713';
  background: var(--forest-green);
  border-color: var(--forest-green);
  color: var(--white);
}

.progress-tracker--static .progress-tracker__step::before { background: var(--forest-green); }

.progress-tracker--static[data-current="1"] .progress-tracker__step:nth-child(1),
.progress-tracker--static[data-current="2"] .progress-tracker__step:nth-child(2),
.progress-tracker--static[data-current="3"] .progress-tracker__step:nth-child(3),
.progress-tracker--static[data-current="4"] .progress-tracker__step:nth-child(4),
.progress-tracker--static[data-current="5"] .progress-tracker__step:nth-child(5),
.progress-tracker--static[data-current="6"] .progress-tracker__step:nth-child(6) {
  color: var(--deep-blue);
}

.progress-tracker--static[data-current="1"] .progress-tracker__step:nth-child(1)::after,
.progress-tracker--static[data-current="2"] .progress-tracker__step:nth-child(2)::after,
.progress-tracker--static[data-current="3"] .progress-tracker__step:nth-child(3)::after,
.progress-tracker--static[data-current="4"] .progress-tracker__step:nth-child(4)::after,
.progress-tracker--static[data-current="5"] .progress-tracker__step:nth-child(5)::after,
.progress-tracker--static[data-current="6"] .progress-tracker__step:nth-child(6)::after {
  content: counter(pt);
  background: var(--deep-blue);
  border-color: var(--deep-blue);
  color: var(--white);
  box-shadow: 0 0 0 4px rgba(12, 73, 138, 0.12);
}

.progress-tracker--static[data-current="1"] .progress-tracker__step:nth-child(1) ~ .progress-tracker__step,
.progress-tracker--static[data-current="2"] .progress-tracker__step:nth-child(2) ~ .progress-tracker__step,
.progress-tracker--static[data-current="3"] .progress-tracker__step:nth-child(3) ~ .progress-tracker__step,
.progress-tracker--static[data-current="4"] .progress-tracker__step:nth-child(4) ~ .progress-tracker__step,
.progress-tracker--static[data-current="5"] .progress-tracker__step:nth-child(5) ~ .progress-tracker__step,
.progress-tracker--static[data-current="6"] .progress-tracker__step:nth-child(6) ~ .progress-tracker__step {
  color: var(--body-text);
}

.progress-tracker--static[data-current="1"] .progress-tracker__step:nth-child(1) ~ .progress-tracker__step::after,
.progress-tracker--static[data-current="2"] .progress-tracker__step:nth-child(2) ~ .progress-tracker__step::after,
.progress-tracker--static[data-current="3"] .progress-tracker__step:nth-child(3) ~ .progress-tracker__step::after,
.progress-tracker--static[data-current="4"] .progress-tracker__step:nth-child(4) ~ .progress-tracker__step::after,
.progress-tracker--static[data-current="5"] .progress-tracker__step:nth-child(5) ~ .progress-tracker__step::after,
.progress-tracker--static[data-current="6"] .progress-tracker__step:nth-child(6) ~ .progress-tracker__step::after {
  content: counter(pt);
  background: var(--white);
  border-color: rgba(12, 73, 138, 0.15);
  color: var(--body-text);
  box-shadow: none;
}

.progress-tracker--static[data-current="1"] .progress-tracker__step:nth-child(1) ~ .progress-tracker__step::before,
.progress-tracker--static[data-current="2"] .progress-tracker__step:nth-child(2) ~ .progress-tracker__step::before,
.progress-tracker--static[data-current="3"] .progress-tracker__step:nth-child(3) ~ .progress-tracker__step::before,
.progress-tracker--static[data-current="4"] .progress-tracker__step:nth-child(4) ~ .progress-tracker__step::before,
.progress-tracker--static[data-current="5"] .progress-tracker__step:nth-child(5) ~ .progress-tracker__step::before,
.progress-tracker--static[data-current="6"] .progress-tracker__step:nth-child(6) ~ .progress-tracker__step::before {
  background: rgba(12, 73, 138, 0.12);
}

/* Also mark the current step's own outgoing spine as not-yet-done. */
.progress-tracker--static[data-current="1"] .progress-tracker__step:nth-child(1)::before,
.progress-tracker--static[data-current="2"] .progress-tracker__step:nth-child(2)::before,
.progress-tracker--static[data-current="3"] .progress-tracker__step:nth-child(3)::before,
.progress-tracker--static[data-current="4"] .progress-tracker__step:nth-child(4)::before,
.progress-tracker--static[data-current="5"] .progress-tracker__step:nth-child(5)::before,
.progress-tracker--static[data-current="6"] .progress-tracker__step:nth-child(6)::before {
  background: rgba(12, 73, 138, 0.12);
}

/* --checklist: JS binds to a checklist and writes .is-done/.is-current. */
.progress-tracker--checklist .progress-tracker__step.is-done::after {
  content: '\2713';
  background: var(--forest-green);
  border-color: var(--forest-green);
  color: var(--white);
}

.progress-tracker--checklist .progress-tracker__step.is-done::before { background: var(--forest-green); }

.progress-tracker--checklist .progress-tracker__step.is-current::after {
  background: var(--deep-blue);
  border-color: var(--deep-blue);
  color: var(--white);
  box-shadow: 0 0 0 4px rgba(12, 73, 138, 0.12);
}

.progress-tracker__bar {
  height: var(--sp-2);
  border-radius: var(--sp-1);
  background: rgba(12, 73, 138, 0.10);
  overflow: hidden;
  margin-bottom: var(--sp-5);
}

.progress-tracker__fill {
  height: 100%;
  width: 0%;
  border-radius: var(--sp-1);
  background: linear-gradient(90deg, var(--deep-blue), var(--sky-blue));
  transition: width var(--transition);
}

.progress-tracker__count {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--forest-green);
  margin-bottom: var(--sp-2);
  font-variant-numeric: tabular-nums;
}


/* ===== 10. ChartPanel ===== */
/* The canvas MUST have a parent with a resolved height. Chart.js responsive
   mode reads the parent box, resizes the canvas, which resizes the parent,
   which retriggers the resize — an infinite loop that pegs a core. Fixing the
   aspect ratio on the panel and taking the canvas out of flow breaks the
   feedback cycle. Per the §5 contract the canvas is a DIRECT child of
   .chart-panel, so the panel itself is the aspect box; titles and captions are
   siblings outside it. */
.chart-panel {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--white);
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius);
  padding: var(--sp-4);
  overflow: hidden;
}

.chart-panel > canvas {
  position: absolute;
  inset: var(--sp-4);
  width: auto;
  height: auto;
  max-width: 100%;
}

.chart-panel--square { aspect-ratio: 1 / 1; }
.chart-panel--tall   { aspect-ratio: 4 / 5; }

.chart-panel__toggle {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  z-index: var(--z-base);
  display: flex;
  gap: var(--sp-1);
  background: rgba(255, 255, 255, 0.9);
  border-radius: 50px;
  padding: 3px;
}

/* charts.js emits .chart-panel__toggle-btn (via className, which lint.py's
   class="..." scan cannot see — this one was found by hand). */
.chart-panel__toggle-btn {
  border: none;
  background: transparent;
  cursor: pointer;
  font-family: 'Open Sans', 'PT Sans', sans-serif;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--deep-blue);
  padding: var(--sp-1) var(--sp-3);
  border-radius: 50px;
  transition: background var(--transition), color var(--transition);
}

.chart-panel__toggle-btn.is-active {
  background: var(--deep-blue);
  color: var(--white);
}

.chart-panel.is-loading::after {
  content: 'Cargando…';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--light-bg);
  color: var(--body-text);
  font-size: 0.85rem;
  opacity: 0.8;
}

.chart-panel__title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--deep-blue);
  margin-bottom: var(--sp-3);
}

.chart-panel__caption {
  font-size: 0.75rem;
  color: var(--body-text);
  opacity: 0.7;
  line-height: 1.5;
  margin-top: var(--sp-3);
}


/* ===== 11. MapPanel ===== */
/* Leaflet measures its container on init. A container with height:auto is 0px
   tall, the map renders into nothing, and no error is thrown — an explicit
   height is not cosmetic here, it is the component working at all. */
.map-panel {
  position: relative;
  width: 100%;
  height: 560px;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  background: var(--light-bg);
  z-index: var(--z-base);
}

/* NB: responsive.css sets `.map-panel { height }` at 1024/768/480 and wins by
   source order at equal specificity, so --tall is a desktop-only lift. That is
   the intended behaviour (a 720px map on a phone is unusable), not an accident. */
.map-panel--tall { height: 720px; }

.map-panel.is-loading::after {
  content: 'Cargando mapa…';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--body-text);
  font-size: 0.85rem;
  opacity: 0.75;
}

.map-panel.has-error::after {
  content: 'No se pudieron cargar los datos del mapa.';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
  text-align: center;
  background: var(--light-bg);
  color: var(--body-text);
  font-size: 0.85rem;
}

/* "No documentado" inside popups. Must read as an absent value, never as a
   number the reader might mistake for real (§1). */
.map-panel__nodata {
  font-style: italic;
  opacity: 0.6;
}

/* ----- Pins ---------------------------------------------------------------
   maps.js passes a `className` to L.divIcon, which REPLACES Leaflet's default
   `leaflet-div-icon` — but that default ships a white box + grey border, so if
   the option is ever dropped the pins gain a card behind them. Neutralising it
   here costs nothing and removes the trap. Colour/shape live in the inline SVG
   and in the circleMarker options; CSS must not restate them. */
.map-pin {
  background: none;
  border: none;
}

.map-pin svg { display: block; }

/* ----- Legend -------------------------------------------------------------
   A Leaflet control. Leaflet's `.leaflet-bottom.leaflet-right` container owns
   the positioning and the z-index, so this must NOT set position/inset — doing
   so fights the control layer and detaches the legend from the map corner. */
.map-legend {
  background: rgba(255, 255, 255, 0.96);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: var(--sp-3) var(--sp-4);
  max-width: 280px;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--body-text);
}

.map-legend--choropleth { max-width: 220px; }

.map-legend__block + .map-legend__block {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid rgba(12, 73, 138, 0.08);
}

.map-legend__title {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--deep-blue);
  margin-bottom: var(--sp-2);
}

.map-legend__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.map-legend__item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Status swatch. maps.js writes `background` inline from its own STATUS
   constants — the single source of truth for pin colour, shared with the
   markers. Setting a background here would either be ignored or, worse, drift
   from the pins it is supposed to explain. Shape and border only. */
.map-legend__swatch {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--white);
  box-shadow: 0 0 0 1px rgba(12, 73, 138, 0.2);
}

/* Precision swatch: the same span, but wrapping an SVG glyph rather than
   carrying a colour. Undo the dot treatment so the glyph reads. */
.map-legend__swatch--shape {
  width: auto;
  height: 18px;
  border: none;
  border-radius: 0;
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

.map-legend__swatch--shape svg { display: block; }

/* The approximation disclosure. This is sourced-data honesty (§8), not a
   footnote: every pin on this map is an offline geocode, and a reader who
   misses this line will read municipal centroids as installation sites. So it
   gets full-opacity body text and a callout rule — deliberately NOT the
   shrunk-and-faded treatment a caption would get. */
.map-legend__disclosure {
  margin-top: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-left: 3px solid var(--status-warn);
  border-radius: 0 var(--sp-1) var(--sp-1) 0;
  background: rgba(232, 163, 61, 0.10);
  color: var(--body-text);
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.45;
}

/* ----- Popup --------------------------------------------------------------
   `map-popup-wrap` is the className maps.js hands to bindPopup, so it lands on
   Leaflet's own `.leaflet-popup`. Reach through it to restyle Leaflet's
   internals rather than restyling `.leaflet-popup*` globally. */
.map-popup-wrap .leaflet-popup-content-wrapper {
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 0;
}

.map-popup-wrap .leaflet-popup-content {
  margin: 0;
  width: auto !important;
  line-height: 1.5;
}

.map-popup-wrap .leaflet-popup-tip { box-shadow: none; }

/* min-width because `.leaflet-popup-content { width: auto }` above frees the
   box to shrink-wrap, which collapsed the value column to ~80px. maps.js caps
   the popup at maxWidth:320, so this only sets the floor. */
.map-popup {
  min-width: 264px;
  padding: var(--sp-4);
  font-size: 0.8rem;
  color: var(--body-text);
}

.map-popup__title {
  font-size: 0.98rem;
  font-weight: 800;
  color: var(--deep-blue);
  line-height: 1.3;
  margin-bottom: var(--sp-3);
}

.map-popup__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* dt/dd pairs. A fixed label column keeps seven rows scannable; auto columns
   would ragged-edge on "Beneficiarios" vs "Estado". */
.map-popup__row {
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: var(--sp-3);
  align-items: baseline;
}

.map-popup__row dt {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--body-text);
  opacity: 0.65;
  line-height: 1.4;
}

.map-popup__row dd {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--dark-text);
}

/* maps.js sets `color` inline from the same STATUS constants as the pin. */
.map-popup__status { font-weight: 700; }

/* status_note carries the sourced failure detail — exploded batteries at
   Múcura, pending faults at Isla Grande. It is the most valuable content in
   the record (§8) and must not read as a disclaimer. */
.map-popup__note {
  margin-top: var(--sp-1);
  padding: var(--sp-2);
  border-left: 3px solid var(--status-alert);
  border-radius: 0 var(--sp-1) var(--sp-1) 0;
  background: rgba(217, 83, 79, 0.08);
  font-size: 0.72rem;
  font-weight: 500;
  line-height: 1.45;
  color: var(--body-text);
}

.map-popup__highlight {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid rgba(12, 73, 138, 0.08);
  font-size: 0.75rem;
  font-style: italic;
  line-height: 1.5;
  opacity: 0.85;
}

/* Per-pin precision disclosure. Same honesty rule as the legend: this states
   whether the dot the reader just clicked is a site or a centroid. */
.map-popup__precision {
  margin-top: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-left: 3px solid var(--status-neutral);
  border-radius: 0 var(--sp-1) var(--sp-1) 0;
  background: var(--light-bg);
  font-size: 0.7rem;
  line-height: 1.45;
  color: var(--body-text);
}

.map-popup__precision strong { color: var(--dark-text); }

/* Confidence grading mirrors the pin language: solid = sited, dashed/faded =
   approximate. */
.map-popup__precision--exacto {
  border-left-color: var(--status-ok);
  background: rgba(102, 147, 72, 0.08);
}

.map-popup__precision--corregimiento {
  border-left-color: var(--status-warn);
  background: rgba(232, 163, 61, 0.10);
}

.map-popup__precision--municipio {
  border-left-color: var(--status-warn);
  background: rgba(232, 163, 61, 0.14);
}

/* ----- Choropleth hover tooltip -------------------------------------------
   Leaflet's default tip arrow is white and is drawn from border colours we do
   not control here, so the background stays white and only type/border/shadow
   are brought onto brand. Recolouring the box would leave a white arrow. */
.map-tooltip {
  border: 1px solid rgba(12, 73, 138, 0.12);
  border-radius: var(--sp-2);
  box-shadow: var(--shadow-md);
  padding: var(--sp-2) var(--sp-3);
  font-family: 'Open Sans', 'PT Sans', sans-serif;
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--dark-text);
}

.map-tooltip strong { color: var(--deep-blue); }


/* ===== 12. VideoEmbed ===== */
/* Poster + button only. The iframe is injected on click — a bare YouTube embed
   is ~900KB and sets cookies before the visitor has asked for the video. */
.video-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--dark-text);
  cursor: pointer;
  border: none;
  padding: 0;
  display: block;
}

.video-embed__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition), filter var(--transition);
}

.video-embed:hover .video-embed__poster {
  transform: scale(1.03);
  filter: brightness(0.8);
}

.video-embed__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  background: rgba(12, 73, 138, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), transform var(--transition);
}

/* interactive.js puts a ▶ glyph inside the button, so the play triangle is
   real content — a ::after triangle here would double it. */
.video-embed__play-icon {
  color: var(--white);
  font-size: 1.5rem;
  line-height: 1;
  margin-left: 3px;
}

.video-embed:hover .video-embed__play {
  background: var(--sky-blue);
  transform: translate(-50%, -50%) scale(1.08);
}

.video-embed__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--sp-5) var(--sp-4) var(--sp-3);
  background: linear-gradient(180deg, transparent, rgba(7, 30, 54, 0.85));
  color: var(--white);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: left;
}

/* The injected iframe. interactive.js clears the root's innerHTML before
   appending it, so the poster/button/caption are gone by then — .is-playing is
   the hook for the container only, not a way to hide leftovers. */
.video-embed__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
}

.video-embed.is-playing { cursor: default; }


/* ===== 13. DownloadCard ===== */
.download-card {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  background: var(--white);
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5);
  transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition);
}

.download-card:hover {
  border-color: var(--sky-blue);
  box-shadow: var(--shadow-sm);
  transform: translateY(-2px);
}

.download-card__icon {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--white);
  background: var(--status-neutral);
}

.download-card__body { flex: 1; min-width: 0; }

.download-card__title {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--deep-blue);
  margin-bottom: 2px;
}

.download-card__meta {
  font-size: 0.75rem;
  color: var(--body-text);
  opacity: 0.7;
}

.download-card__badge {
  flex: none;
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 3px 10px;
  border-radius: 20px;
  background: var(--light-cyan);
  color: var(--deep-blue);
}

/* Format drives both chips. Attribute selectors, not nth-child — a download
   list is reordered by content edits constantly. */
.download-card[data-format="docx"] .download-card__icon  { background: #2B579A; }
.download-card[data-format="docx"] .download-card__badge { background: rgba(43, 87, 154, 0.12); color: #2B579A; }

.download-card[data-format="pdf"] .download-card__icon  { background: #C0392B; }
.download-card[data-format="pdf"] .download-card__badge { background: rgba(192, 57, 43, 0.12); color: #C0392B; }

.download-card[data-format="xlsx"] .download-card__icon  { background: var(--forest-green); }
.download-card[data-format="xlsx"] .download-card__badge { background: rgba(102, 147, 72, 0.14); color: var(--forest-green); }

.download-card[data-format="html"] .download-card__icon  { background: var(--sky-blue); }
.download-card[data-format="html"] .download-card__badge { background: rgba(105, 181, 250, 0.16); color: var(--deep-blue); }

.download-card[data-format="docx"] .download-card__icon::after { content: 'DOCX'; }
.download-card[data-format="pdf"]  .download-card__icon::after { content: 'PDF'; }
.download-card[data-format="xlsx"] .download-card__icon::after { content: 'XLSX'; }
.download-card[data-format="html"] .download-card__icon::after { content: 'HTML'; }


/* ===== 14. QuizWidget ===== */
.quiz {
  background: var(--white);
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
  box-shadow: var(--shadow-sm);
}

/* initQuiz owns the whole subtree: it renders ONE question at a time into
   .quiz__body via innerHTML. There is no reveal state to drive — an earlier
   version of this block hid .quiz__question/.quiz__result behind an .is-active
   class the JS never sets, which rendered all three quizzes as a blank box.
   Nothing here may hide a quiz element by default. */

/* Wrapper for the bar + the "Pregunta 3 de 10" readout. */
.quiz__progress { margin-bottom: var(--sp-5); }

.quiz__progress-bar {
  height: 6px;
  border-radius: 3px;
  background: rgba(12, 73, 138, 0.10);
  overflow: hidden;
}

.quiz__progress-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--deep-blue), var(--sky-blue));
  transition: width var(--transition);
}

.quiz__progress-text {
  margin-top: var(--sp-2);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--forest-green);
}

.quiz__body { display: block; }

/* <fieldset> — needs the UA border/padding stripped. */
.quiz__question {
  border: none;
  padding: 0;
  margin: 0;
  min-width: 0;
}

/* <legend> — the question text. JS gives it tabindex=-1 and focuses it on
   step change, so the focus ring must not look like an error. */
.quiz__q {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--deep-blue);
  line-height: 1.4;
  margin-bottom: var(--sp-3);
  padding: 0;
}

.quiz__q:focus { outline: none; }
.quiz__q:focus-visible { outline: 3px solid var(--sky-blue); outline-offset: 4px; }

.quiz__help {
  font-size: 0.82rem;
  color: var(--body-text);
  opacity: 0.75;
  line-height: 1.6;
  margin-bottom: var(--sp-4);
}

/* Grid, because responsive.css collapses this to `1fr` at 768 — flex-column
   would make that override a no-op. auto-fit keeps long Spanish option labels
   readable instead of forcing two cramped columns. */
.quiz__options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-3);
}

/* <label> wrapping a real radio + .quiz__option-label. */
.quiz__option {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  background: var(--light-bg);
  border: 1.5px solid transparent;
  border-radius: var(--radius);
  padding: var(--sp-4);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--body-text);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition), box-shadow var(--transition);
}

.quiz__option:hover {
  border-color: var(--sky-blue);
  background: var(--white);
}

/* JS toggles .is-active on the chosen label (NOT .is-selected). */
.quiz__option.is-active {
  border-color: var(--deep-blue);
  background: var(--white);
  color: var(--deep-blue);
  box-shadow: var(--shadow-sm);
}

/* The radio is real and stays real — it carries the keyboard model and the
   accessible name. Style it, never replace it with a faked marker span. */
.quiz__option input[type="radio"] {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  accent-color: var(--deep-blue);
  cursor: pointer;
}

.quiz__option:focus-within {
  border-color: var(--deep-blue);
  box-shadow: var(--shadow-sm);
}

.quiz__option-label { line-height: 1.5; }

.quiz__nav {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 1px solid rgba(12, 73, 138, 0.08);
}

/* JS sets .hidden on both buttons at the result step. */
.quiz__nav .btn[hidden] { display: none; }

.quiz__back:disabled,
.quiz__next:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
}

.quiz__back:disabled:hover,
.quiz__next:disabled:hover {
  transform: none;
  box-shadow: none;
}

/* ----- Result ------------------------------------------------------------- */
.quiz__result {
  text-align: center;
  padding: var(--sp-2) 0;
}

.quiz__result:focus { outline: none; }

.quiz__result-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--forest-green);
  margin-bottom: var(--sp-2);
}

.quiz__result-score {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--body-text);
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.quiz__result-score strong {
  display: block;
  font-size: 3rem;
  font-weight: 800;
  color: var(--deep-blue);
  line-height: 1.1;
}

.quiz__result-band {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--deep-blue);
  margin: var(--sp-3) 0 var(--sp-2);
}

.quiz__result-rec {
  font-size: 0.92rem;
  color: var(--body-text);
  opacity: 0.85;
  line-height: 1.7;
  max-width: 520px;
  margin: 0 auto var(--sp-4);
}

.quiz__result-note {
  font-size: 0.78rem;
  color: var(--body-text);
  opacity: 0.7;
  line-height: 1.6;
  max-width: 520px;
  margin: 0 auto var(--sp-4);
  font-style: italic;
}

.quiz__result-actions {
  display: flex;
  gap: var(--sp-3);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--sp-5);
}

.quiz__cta,
.quiz__restart { flex: none; }


/* ===== 15. TabPanel ===== */
.tabs { width: 100%; }

.tabs__list {
  display: flex;
  gap: var(--sp-1);
  border-bottom: 2px solid rgba(12, 73, 138, 0.10);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.tabs__list::-webkit-scrollbar { display: none; }

.tabs__tab {
  flex: none;
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  margin-bottom: -2px;
  padding: var(--sp-3) var(--sp-5);
  font-family: 'Open Sans', 'PT Sans', sans-serif;
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--body-text);
  opacity: 0.65;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--transition), opacity var(--transition), border-color var(--transition);
}

.tabs__tab:hover {
  opacity: 1;
  color: var(--deep-blue);
}

.tabs__tab[aria-selected="true"] {
  opacity: 1;
  color: var(--deep-blue);
  border-bottom-color: var(--sky-blue);
}

.tabs__panel {
  padding-top: var(--sp-5);
}

.tabs__panel[hidden] { display: none; }
/* interactive.js drives panels with .is-active, not [hidden] — without
   this every panel renders at once and the tabs do nothing. */
.tabs__panel:not(.is-active) { display: none; }

.tabs--pills .tabs__list {
  border-bottom: none;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.tabs--pills .tabs__tab {
  border: 1.5px solid rgba(12, 73, 138, 0.12);
  border-radius: 50px;
  margin-bottom: 0;
  padding: var(--sp-2) var(--sp-4);
}

.tabs--pills .tabs__tab[aria-selected="true"] {
  background: var(--deep-blue);
  border-color: var(--deep-blue);
  color: var(--white);
}


/* ===== 16. ModalDetail ===== */
/* Native <dialog> + showModal(). Backdrop, ESC, focus trap and inertness come
   from the platform — none of it is reimplemented here. */
.modal {
  width: min(680px, calc(100vw - var(--sp-6)));
  max-height: min(85vh, 900px);
  padding: 0;
  border: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  background: var(--white);
  color: var(--body-text);
  overflow: hidden;
  z-index: var(--z-modal);
}

.modal::backdrop {
  background: rgba(7, 30, 54, 0.55);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

.modal:not([open]) { display: none; }

.modal--wide { width: min(960px, calc(100vw - var(--sp-6))); }

.modal__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-6);
  border-bottom: 1px solid rgba(12, 73, 138, 0.08);
  background: var(--light-bg);
}

.modal__title {
  font-size: 1.25rem;
  font-weight: 800;
  color: var(--deep-blue);
  line-height: 1.3;
}

.modal__close {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--deep-blue);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), color var(--transition);
}

.modal__close::before { content: '\00D7'; }

.modal__close:hover {
  background: var(--deep-blue);
  color: var(--white);
}

.modal__body {
  padding: var(--sp-6);
  overflow-y: auto;
  max-height: 60vh;
  font-size: 0.92rem;
  line-height: 1.7;
}

.modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-6);
  border-top: 1px solid rgba(12, 73, 138, 0.08);
  background: var(--light-bg);
}


/* ===== 17. TooltipInfo ===== */
/* interactive.js appends a real <span class="tooltip__bubble"> holding
   .tooltip__term + .tooltip__def, resolved from glosario.json.
   An earlier version of this block rendered the bubble as
   `.tooltip::after { content: var(--tooltip-text) }` — a custom property the JS
   never sets. The injected span had no rule, so every definition dumped into
   the page as raw text (~43 of them on comunidades/entender.html). The bubble
   MUST be styled as an element; a ::after bubble cannot show a two-part
   term/definition structure anyway. */
.tooltip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-left: var(--sp-1);
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--light-cyan);
  color: var(--deep-blue);
  font-family: 'Open Sans', 'PT Sans', sans-serif;
  font-size: 0.62rem;
  font-weight: 800;
  line-height: 1;
  cursor: help;
  vertical-align: super;
  transition: background var(--transition), color var(--transition);
}

.tooltip::before {
  content: 'i';
}

.tooltip:hover,
.tooltip.is-open {
  background: var(--deep-blue);
  color: var(--white);
}

/* The injected bubble. Hidden by default and revealed on hover/focus (CSS) or
   .is-open (JS, for touch). visibility+opacity rather than display so the
   transition runs and so aria-describedby keeps resolving. */
.tooltip__bubble {
  position: absolute;
  bottom: calc(100% + var(--sp-2));
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  z-index: var(--z-tooltip);
  width: max-content;
  max-width: 280px;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--sp-2);
  background: var(--dark-text);
  color: var(--white);
  font-size: 0.78rem;
  font-weight: 400;
  line-height: 1.5;
  text-align: left;
  letter-spacing: 0;
  white-space: normal;
  cursor: auto;
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--transition), transform var(--transition), visibility var(--transition);
}

.tooltip__term {
  display: block;
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--light-cyan);
  margin-bottom: var(--sp-1);
}

.tooltip__def {
  display: block;
  font-weight: 400;
  color: var(--white);
}

.tooltip:hover .tooltip__bubble,
.tooltip:focus-visible .tooltip__bubble,
.tooltip.is-open .tooltip__bubble {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

/* The term was missing from glosario.json, or the fetch failed. JS removes the
   bubble and sets .is-inert — the trigger must stop advertising a definition
   it cannot show, rather than opening an empty box. */
.tooltip.is-inert {
  background: transparent;
  color: var(--body-text);
  opacity: 0.45;
  cursor: default;
  pointer-events: none;
}


/* ===== APPENDIX A. Checklist ===== */
/* NOT one of the contract's 17 — §3 says this file owns "exactly the 17 …
   nothing else", so this block is a deliberate, flagged exception rather than
   an oversight. [data-checklist] is implemented in interactive.js and used
   heavily (55-item DD list on inversionistas/esg.html, 45-item pre-inversión
   on municipios, several on comunidades), it is the data source ProgressTracker
   --checklist binds to, and it was shipping completely unstyled.
   §5 should be amended to adopt it as #18. */
/* Scope notes, verified against the built pages rather than assumed:
   - There is NO `.checklist` root class. The root is `[data-checklist="id"]`
     and is variously `.prose`, `.stack`, or an unclassed <div>. So this block
     styles ITEMS ONLY — giving the root layout (e.g. flex) would silently
     restructure the .prose article wrapped around it.
   - The progress bar reuses `.progress-tracker__bar/__fill/__count` with
     `data-checklist-*` hooks; interactive.js accepts either. Those rules
     already exist under #9, so there is deliberately no `.checklist__bar` here.
   - `.checklist__item` is authored BOTH as <li> and as <label>. Both forms are
     handled below.
   - The `ul li.checklist__item` branch is not redundant: main.css's
     `.prose ul li` (0,1,2) out-specifies a bare `.checklist__item` (0,1,0) and
     would restore disc bullets and its own padding. Matching specificity lets
     source order settle it. */
.checklist__item,
ul li.checklist__item {
  display: block;
  list-style: none;
  background: var(--white);
  border: 1.5px solid rgba(12, 73, 138, 0.08);
  border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4);
  transition: border-color var(--transition), background var(--transition);
}

.checklist__item + .checklist__item { margin-top: var(--sp-2); }

/* The row: either the <label> inside an <li>, or the item when it IS a label. */
.checklist__item > label,
label.checklist__item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: 0.88rem;
  line-height: 1.6;
  cursor: pointer;
}

.checklist__item input[type="checkbox"] {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 3px;
  accent-color: var(--forest-green);
  cursor: pointer;
}

.checklist__item strong { color: var(--deep-blue); }

/* JS toggles .is-checked on the item (via closest()), for both forms. Ticked
   rows recede so the eye lands on what is still outstanding — the whole point
   of a 55-item due-diligence list. */
.checklist__item.is-checked {
  background: var(--light-bg);
  border-color: rgba(102, 147, 72, 0.35);
}

.checklist__item.is-checked > label,
label.checklist__item.is-checked { opacity: 0.6; }

/* interactive.js resolves the bar as `.checklist__fill, [data-checklist-fill]`
   (likewise __count). Today every page uses the progress-tracker classes with
   the data-* hooks, so these class aliases are unused — but they are a
   supported half of the JS contract, and an author reaching for the obvious
   name must not land back in the unstyled-component trap this pass exists to
   close. Aliased onto the #9 bar rather than duplicated. */
.checklist__fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: var(--sp-1);
  background: linear-gradient(90deg, var(--forest-green), var(--light-green));
  transition: width var(--transition);
}

.checklist__count {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--forest-green);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--sp-4);
}


/* ===== APPENDIX B. Carousel ===== */
/* NOT one of the contract's 17 — same §3 exception as Appendix A.
   [data-carousel] is implemented in interactive.js and now used on the landing
   page (#lecciones). §5 should adopt it as #19.

   THIS IS NOT A TESTIMONIAL CAROUSEL, and it must not be styled as one.
   Research_European_Cases.md contains no first-person quotes, so there is
   nothing to put in a testimonial. What rotates here is five sourced lessons,
   each naming the case it was drawn from. The <cite> is therefore the load
   -bearing half of the slide, not a caption under a nice quote: it is what
   separates "a finding from Bürgerwerke's 2024 numbers" from "a slogan we
   wrote". So the social-proof treatment main.css gives <cite> (0.85rem at 0.7
   opacity — correct for a decorative pull-quote elsewhere) is deliberately
   overridden below, scoped to the carousel so other .pull-quote uses keep it. */
.carousel {
  position: relative;
  max-width: 860px;
  margin: 0 auto;
}

/* Every slide occupies the SAME grid cell. Two consequences, both wanted:
   the track's height is the tallest slide, so rotation never jumps the page;
   and the slides cross-fade in place, which is what initCarousel expects — it
   toggles .is-active and sets no inline transform, so there is no track to
   slide sideways. */
.carousel__track {
  display: grid;
  align-items: center;
}

.carousel__slide {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--transition), visibility var(--transition);
}

/* visibility (not just opacity) so an inactive slide leaves the a11y tree and
   the tab order — belt and braces with the aria-hidden and tabIndex=-1 that
   initCarousel already sets. */
.carousel__slide.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* No-JS guard, same reasoning as main.css's AOS guard (§11): .is-active is
   written only by initCarousel, so until it runs — and forever, if the module
   fails to load — every slide is hidden and the section is a blank box that
   silently swallows all five sourced lessons.
   initCarousel's go() stamps aria-hidden on EVERY slide, so this rule shows the
   first lesson pre-JS and then stops matching the moment JS takes over. It
   cannot fight .is-active: post-init no slide lacks the attribute. */
.carousel__slide:not([aria-hidden]):first-child {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* main.css gives .pull-quote a var(--sp-6) block margin, which inside a
   stacked grid cell just pads the track. */
.carousel__slide .pull-quote {
  margin: 0;
  font-size: 1.25rem;
}

/* The attribution. Full opacity, up-weighted, and separated by a rule so it
   reads as a source citation rather than a footnote. */
.carousel__slide .pull-quote cite {
  opacity: 1;
  color: var(--body-text);
  font-size: 0.88rem;
  font-weight: 600;
  line-height: 1.55;
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid rgba(12, 73, 138, 0.12);
}

.carousel__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}

.carousel__prev,
.carousel__next {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1.5px solid rgba(12, 73, 138, 0.15);
  background: var(--white);
  color: var(--deep-blue);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}

.carousel__prev:hover,
.carousel__next:hover {
  background: var(--deep-blue);
  border-color: var(--deep-blue);
  color: var(--white);
}

.carousel__dots {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Injected by initCarousel as <button class="carousel__dot" role="tab">. */
.carousel__dot {
  width: 10px;
  height: 10px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(12, 73, 138, 0.2);
  cursor: pointer;
  transition: background var(--transition), width var(--transition);
}

.carousel__dot:hover { background: rgba(12, 73, 138, 0.45); }

.carousel__dot.is-active {
  width: 28px;
  border-radius: 5px;
  background: var(--deep-blue);
}
