/* ===========================================================================
   degree-board · shared mobile fix layer  (DE / UK / US)
   One file, loaded last, for all three geos. Every block is traceable to a
   numbered finding in the mobile-feedback audit (2026-07-25).
   Author: Claude · staged, NOT deployed.
   =========================================================================== */

:root{
  /* the sticky furniture heights become tokens instead of magic numbers */
  --db-hdr-h: 56px;
  --db-subnav-h: 54px;
  --db-cta-h: 96px;          /* DE 2-line bar is the tallest; UK/US 1-line ~64px */
  --db-fab-gap: 12px;
}

/* ---------------------------------------------------------------------------
   B1 — horizontal overflow. THE root mobile bug: the page pans sideways.

   Measured on the live UK course page at 375pt: document scrollWidth 586px
   (211px of pan). Traced to a single CSS mechanism, not to one widget:

     #who-gets-in .tbar { grid-template-columns: 1fr auto }   <- no minmax
       `1fr` is `minmax(auto, 1fr)`, and `auto` floors at MIN-CONTENT.
       Its label ("More than 240 pts · A*A*A*+") has no break opportunity,
       so min-content = 507px -> track 507px -> row 562px.
     .split { grid-template-columns: 1fr }  (<=840px)         <- also no minmax
       inherits the 562px child and expands too -> .wrap scrollWidth 586px.

   So the correct fix is `minmax(0, …)` + `min-width:0` on the grid items,
   NOT `overflow-x:hidden` on the document — that only hides the right-hand
   column (the % values) instead of laying it out. Every grid track below is
   clamped, and long unbreakable data labels are allowed to wrap.
   --------------------------------------------------------------------------- */

/* every grid item may shrink below its min-content width */
.split > *, .sbar > *, .sbars > *, .band > *, .grid > * { min-width: 0; }

/* the two tracks that actually overflowed */
.split { grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr); }
@media (max-width: 840px){
  .split { grid-template-columns: minmax(0, 1fr) !important; }
}
.sbar { grid-template-columns: 148px minmax(0, 1fr) auto; }
@media (max-width: 560px){
  .sbar { grid-template-columns: minmax(0, 1fr) auto !important; }
}
#who-gets-in .tbar { grid-template-columns: minmax(0, 1fr) auto !important; }

/* the unbreakable tariff/grade labels ("240 pts · A*A*A*+") must be allowed
   to wrap rather than setting a 507px floor */
.sbar-k, .sbar-v, .tbar .sbar-k {
  min-width: 0; overflow-wrap: anywhere; word-break: break-word;
}
.sbar-track, .tbar .sbar-track { min-width: 0; }

/* wide data tables scroll inside their own box instead of pushing the page */
.tbl-wrap, .table-scroll, .dt-wrap {
  max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.tbl-wrap > table, .tbl-wrap > .dt { min-width: max-content; }
.dt, .tbl, table { max-width: none; }
.tbl-note { max-width: 100%; overflow-wrap: anywhere; }

/* a CTA/button must never exceed the viewport (measured 558px) */
.btn, .btn-ink, .actionbar .btn { max-width: 100%; box-sizing: border-box; }

/* backstop only — if a NEW widget regresses, contain it at the section level
   rather than letting the whole document pan. Deliberately not on <body>:
   overflow on the root breaks position:sticky, which this design relies on. */
main > section, .block { overflow-x: clip; }

/* B6 — ranked-chart labels truncated to ambiguity.
   Measured on the US majors chart: .bnm gets 87px but needs 123-162px, with
   white-space:nowrap + ellipsis. "Computer Engineering" (149px) and
   "Computer Science" (123px) BOTH render as "Computer …" — two different rows
   with the same visible label. Give the name column room and let it wrap. */
.rgnm { min-width: 0; }
.bnm, .rgnm .bnm {
  white-space: normal !important;
  overflow: visible !important;
  text-overflow: clip !important;
  line-height: 1.22;
  overflow-wrap: break-word;
}
@media (max-width: 700px){
  /* the ranked rows are `# | name | bar | value`; give the name the slack and
     let the bar shrink, since the bar is decorative and the name is the data */
  .rgrid, .rankrow, .majorrow {
    grid-template-columns: 1.6rem minmax(7.5rem, 1fr) minmax(0, 1fr) auto !important;
    column-gap: 8px !important; align-items: center;
  }
}

/* B8 — DE cost table: "Studiengebühr/en" mid-word break.
   Allow sensible wrapping, forbid ugly hyphen splits. */
.dt td, .dt th, .tbl td, .tbl th {
  hyphens: none; overflow-wrap: break-word; word-break: normal;
}
.dt th:first-child, .dt td:first-child { min-width: 9.5rem; }

/* ---------------------------------------------------------------------------
   B2 — sticky header/subnav let page text ghost through.
   Verified: rgba(255,255,255,.97) on two stacked sticky layers with a gap
   between them. Make them fully opaque and close the gap.
   --------------------------------------------------------------------------- */
header.nav, .nav, .dbhdr { background: #fff !important; }
.subnav { background: #fff !important; top: var(--db-hdr-h) !important; }
@supports (backdrop-filter: blur(6px)){
  header.nav, .subnav { backdrop-filter: none; }
}

/* A6 — one progress bar, one implementation.
   Verified drift: DE solid/width, UK-course solid/scaleX, UK-subject rainbow
   gradient, US rainbow gradient. Unify on the brand blue + scaleX. */
.progress{
  position: fixed; top: 0; left: 0; height: 3px; width: 100%;
  transform-origin: 0 50%;
  background: var(--sm-blue, #1300FF) !important;
  box-shadow: none !important;
}

/* ---------------------------------------------------------------------------
   B3 — content clipped behind the sticky CTA.
   Verified: no page sets body{padding-bottom}; UK's largest is 16px against a
   ~96px bar. Reserve the bar's height at the end of the document.
   --------------------------------------------------------------------------- */
body:has(.actionbar), body:has(.sticky-cta), body:has(.floatcta){
  padding-bottom: calc(var(--db-cta-h) + env(safe-area-inset-bottom, 0px)) !important;
}

/* A2c — there are FOUR sticky-CTA patterns across the three geos:
     DE  .actionbar  — 2-line context + full-width button (115px tall)
     UK  .actionbar  — 1-line full-width button (83px)
     US course       — none at all
     US majors .floatcta — a 151x42 floating pill at z-index:900
   Unify the z-scale so they can never fight the nav (50) / subnav (40), and
   reserve the pill's height too so it stops covering the last table row. */
.actionbar, .sticky-cta{ z-index: 60 !important; }
.floatcta{
  z-index: 60 !important;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px)) !important;
}
/* never show both a bar and a floating pill */
body:has(.actionbar) .floatcta, body:has(.sticky-cta) .floatcta{ display: none !important; }
/* fallback for engines without :has() */
.actionbar ~ *:last-child, .db-cta-spacer{ display: block; }
.db-cta-spacer{ height: calc(var(--db-cta-h) + env(safe-area-inset-bottom, 0px)); }

/* A2 — one sticky CTA treatment across the page and across geos.
   Verified: DE grey bar + hard black top border in one section, white in
   others; UK full-width bar; US none / floating pill. */
.actionbar, .sticky-cta{
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  background: #fff !important;
  border-top: 1px solid var(--g200, #e5e7eb) !important;
  box-shadow: 0 -6px 24px rgba(0,0,0,.07) !important;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px)) !important;
}
.actionbar-inner{ max-width: 720px; margin: 0 auto; }

/* B4 — the lead FAB collided with the CTA.
   Verified: .lead-fab bottom:88px on UK (right on top of the bar), 18px on US,
   display:none on DE. Anchor it above the bar, or hide it when a CTA exists. */
.lead-fab{ bottom: calc(var(--db-cta-h) + var(--db-fab-gap)) !important; z-index: 56 !important; }
body:has(.actionbar) .lead-fab{ display: none !important; }   /* CTA already covers the action */

/* C7 — no emoji as an icon. Replace the 📬 glyph with a real mark. */
.lead-fab .emoji, .lead-fab-emoji{ display: none; }

/* ---------------------------------------------------------------------------
   C1 — FAQ accordion. CORRECTION: my first reading of the screenshot was wrong
   and this is very nearly a non-issue.

   What the product actually does (degree_us.css / degree_uk.css):
       .faq summary .pl            { border:1.5px solid var(--g200); place-items:center }
       .faq details[open] .pl      { background:var(--ink); color:#fff;
                                     border-color:var(--ink); transform:rotate(45deg) }
   So it is ONE control — a single `+` glyph rotated 45° into a `×`. Correct.
   And it does NOT overlap the question text: measured at 375pt the text ends at
   288px and the toggle starts at 306px (summary is flex/space-between/gap:8px).
   The red circle in the screenshot crosses the text; the layout doesn't.

   The one fair critique left is weight: the open state jumps from a hairline
   outline to a solid near-black fill (`background:var(--ink);color:#fff`), which
   is what read as two different controls in the screenshot.

   Deliberately NOT patched here. It is cosmetic, the product's own rule is
   already correct in structure, and I could not reproduce the open state in the
   local harness (the geo stylesheet is loaded cross-origin there), so any
   override I wrote would be unverified. Softening `--ink` to a tint is a
   one-line change in degree_us.css / degree_uk.css and belongs with the
   designer, not in a mobile fix layer.
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   C2 — career-ladder stepper: the step numbers rendered dark, offset to the
   top-left, effectively outside the solid blue circle. (Christian circled it.)
   --------------------------------------------------------------------------- */
.ladder li, .lad-step, .step-row{ position: relative; }
.lad-dot, .step-dot, .ladder .dot{
  position: relative; flex: none;
  width: 34px; height: 34px; border-radius: 999px;
  background: var(--sm-blue, #1300FF);
  display: grid; place-items: center;
  color: #fff !important; font-weight: 700; font-size: 14px; line-height: 1;
  box-shadow: 0 0 0 4px #fff, 0 0 0 5px var(--g200, #e5e7eb);
}
.lad-dot > *, .step-dot > *{ position: static !important; inset: auto !important; color: inherit !important; }

/* ---------------------------------------------------------------------------
   C3 — unstyled native <details> ("▼ Starting on or after 1 January 2027?")
   sat flush against the card edge with a raw browser marker. (Circled.)

   CAUTION: FAQ rows are ALSO unclassed <details>, so a bare
   `details:not([class])` selector restyles the whole FAQ list as grey cards —
   I shipped exactly that bug in the first pass and caught it in the browser.
   Scope by what the FAQ has and this doesn't: a `.pl` toggle in its summary,
   and a `.faq` ancestor.
   --------------------------------------------------------------------------- */
details:not([class]):not(:has(summary .pl)):not(.faq *){
  margin: 12px 0 0; padding: 12px 14px;
  border: 1px solid var(--g200, #e5e7eb); border-radius: 12px;
  background: var(--g50, #f9fafb);
}
.faq details, details:has(summary .pl){ border: 0; background: none; padding: 0; margin: 0; }
details:not([class]):not(:has(summary .pl)):not(.faq *) > summary{
  list-style: none; cursor: pointer; font-weight: 600;
  display: flex; align-items: center; gap: 8px;
}
details:not([class]):not(:has(summary .pl)):not(.faq *) > summary::-webkit-details-marker{ display: none; }
details:not([class]):not(:has(summary .pl)):not(.faq *) > summary::before{
  content: ""; flex: none; width: 8px; height: 8px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg); transition: transform .18s ease;
}
details:not([class]):not(:has(summary .pl)):not(.faq *)[open] > summary::before{ transform: rotate(45deg); }

/* ---------------------------------------------------------------------------
   C4 — numbers: some stat values rendered in a letter-spaced monospace face
   (DE "0 €" via .deg; US majors "12,228" / "$64,323–$74,155"), others in the
   brand sans. Unify on the sans with tabular figures.
   --------------------------------------------------------------------------- */
.deg, .us-stat b, .stat-v, .kpi-v, .statnum, .bignum{
  font-family: inherit !important;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: 0 !important;
}

/* C9 — table sort affordances rendered as grey filled squares that read as
   broken images. Make them quiet glyphs. */
.sortable th .sicon, th[aria-sort] .sicon, .majortbl th img{
  background: none !important; border: 0 !important; width: auto; height: auto;
  opacity: .45; font-size: 11px;
}

/* B10 — payback chart: SVG scaled down so its labels rendered ~8px, "break
   even" clipped at the left edge, and it had no card while everything around
   it did. */
.roi-chart, .payback, .roi-svg{
  display: block; width: 100%; box-sizing: border-box;
  padding: 12px 14px 8px;
  border: 1px solid var(--g200, #e5e7eb); border-radius: 14px; background: #fff;
  overflow: clip;                       /* contain, never let the chart escape */
}
.roi-chart svg, .payback svg, .roi-svg svg{
  width: 100%; height: auto; max-width: 100%;
}
.roi-chart text, .payback text, .roi-svg text{ font-size: 11px !important; }

/* `overflow: clip` here is a precaution, not a fix for an observed bug.
   I first wrote `overflow: visible` (to un-clip the left-most "break even"
   label) and thought it caused a 149px horizontal pan — but that reading came
   from a degenerate measurement where documentElement.clientWidth was 0. Re-run
   at a real 375pt viewport, toggling overflow:visible on these SVGs changes
   scrollWidth by 0px. So there was no regression; the earlier number was
   measurement error on my side.
   `clip` is kept anyway because nothing needs to escape these boxes.
   The "break even" label is clipped because it sits at x≈0 INSIDE the viewBox —
   fixing that means widening the viewBox / shifting the plot area in the
   generator (us_roi.py, uk_phase3.py), which CSS cannot reach. Generator work. */

/* B9 — value-map: rotated y-axis title overlapped the tick values. */
.vmap .ylab, .value-map .ylab{ writing-mode: vertical-rl; transform: rotate(180deg); margin-right: 6px; }
@media (max-width: 700px){
  .vmap .ylab, .value-map .ylab{ position: static; writing-mode: horizontal-tb; transform: none;
    margin: 0 0 4px; font-size: 12px; }
}

/* B11 — UK grade selector: four native <select>s in a ragged 2+2 grid with
   mismatched widths. */
@media (max-width: 700px){
  .gradepick, .fitpick{ display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px 12px; }
  .gradepick > label, .fitpick > label{ display: block; min-width: 0; }
  .gradepick select, .fitpick select{ width: 100%; min-width: 0; }
  .gradepick .q, .fitpick .q{ grid-column: 1 / -1; }
}

/* B7 — UK "every university offering X": a 2-column text list whose counts
   wrapped onto their own line. One column on mobile. */
@media (max-width: 700px){
  .unilist, .colcount, .uni-cols{ columns: 1 !important; column-count: 1 !important; }
  .unilist li, .uni-cols li{ break-inside: avoid; }
}

/* A3 — the active sub-nav tab was never scrolled into view (UK "Chancen"/US
   "ROI" sat half-cut at the right edge). JS does the scroll; this makes the
   cut-off edge legible as "there is more". */
.subnav-inner{
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 40px), transparent 100%);
          mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 40px), transparent 100%);
  scroll-behavior: smooth;
}

/* C12 — DE "Ähnliche Studiengänge" cards were ~3× taller than they needed to
   be and carried no meta, so five cards filled a screen. */
@media (max-width: 700px){
  .rec-card, .sim-card{ padding: 12px 14px !important; }
  .rec-card h3, .sim-card h3{ margin: 0 0 2px; font-size: 16px; }
}

/* C11 — role/employer lists used default UA disc bullets instead of the
   design-system check/dot mark. */
.rolelist, .whereList, .roles ul, .where ul{ list-style: none; padding-left: 0; }
.rolelist li, .whereList li, .roles ul li, .where ul li{
  position: relative; padding-left: 18px;
}
.rolelist li::before, .whereList li::before, .roles ul li::before, .where ul li::before{
  content: ""; position: absolute; left: 3px; top: .62em;
  width: 6px; height: 6px; border-radius: 999px; background: var(--sm-blue, #1300FF);
}

/* A2b — one primary button. Verified drift: .btn-ink is brand blue on DE/UK
   and #0A0A0A on US. */
.btn-ink{ background: var(--sm-blue, #1300FF) !important; color: #fff !important; }

/* ---------------------------------------------------------------------------
   B12 — the lime "Clearing 2026 is open" banner. Measured at 375pt: the text
   span inherits `text-align:center` inside a 3-item flex row, so the copy
   centre-wraps across 5 ragged lines and the bar grows to 217px — a quarter of
   the viewport, above the fold, before any content.

   The banner is `<div id="uk-clearing" style="…display:flex;justify-content:
   center;text-align:center">` — the styling is INLINE, so class selectors lose.
   Target the id and use !important; that is the only thing that beats an inline
   style short of changing the generator.
   --------------------------------------------------------------------------- */
@media (max-width: 700px){
  #uk-clearing{
    flex-wrap: wrap !important;
    justify-content: flex-start !important;
    text-align: left !important;
    gap: 4px 12px !important;
    padding: 9px 14px !important;
  }
  #uk-clearing > span{
    flex: 1 1 100%; min-width: 0;
    text-align: left !important; line-height: 1.35;
  }
  #uk-clearing > a{ flex: 0 0 auto; }        /* CTA sits under the copy, inline */
}

/* ---------------------------------------------------------------------------
   C8 — five chip styles on a single DE result card:
     .tag2 (neutral) · .tag2.deg (blue) · .tag2.field (ghost)
     .tag2.free (green) · .tag2.nc (amber)
   Five weights on one card is noise; collapse to three ROLES —
   primary (what it is), neutral (facts), quiet (taxonomy).

   NOTE, deliberately not restyled away: `.tag2.free` ("gebührenfrei") is an
   unverified FINANCIAL claim — see the factcheck finding, 20,315 pages assert
   0 € tuition while `pricing_model` is null for all 425 institutions. The right
   action is to suppress that chip until fees are sourced, not to recolour it.
   Uncomment the rule below once that decision is made.
   --------------------------------------------------------------------------- */
.tag2.free, .tag2.nc{
  background: var(--g100, #f3f4f6) !important;
  color: var(--g700, #374151) !important;
  border-color: var(--g200, #e5e7eb) !important;
}
/* .tag2.free{ display: none !important; }   <- enable when gating the fee claim */

/* ---------------------------------------------------------------------------
   A7 — "How pay grows" (#karriere .sb-row) is the chosen earnings widget.
   Its one real formatting bug: the lime value chip is centred on the marker
   (`left:50%; translateX(-50%)`) with no edge clamping, so at a high marker
   position it breaks out of its track.

   Measured on the live UK course page at 375pt:
       row 1  £40,000  dot 47.1%  chip 147→209   inside track      ok
       row 2  £44,000  dot 56.9%  chip 179→241   inside track      ok
       row 3  £59,000  dot 93.9%  chip 300→362   track ends 351  OVERFLOWS
   Exactly the class of bug MR !45 fixed for `.u3-rank b`; `.sb-dot i` was
   missed.

   The dot carries a bare inline `left:93.9%` with no custom property, so CSS
   alone cannot compute a proportional shift. Two-part fix:

   1. PREFERRED — have the generator also emit the number as `--p`
      (`style="left:93.9%;--p:93.9"`). The rule below then right-anchors the
      chip as it approaches 100%, the same technique as !45. It is written to
      be inert when `--p` is absent, so it is safe to ship before the
      generator change.
   2. FALLBACK, active today — inset the track so a centred chip still fits at
      either extreme. The band and dot are both percentages OF THE TRACK, so
      insetting it does not distort the scale.

   NOTE on the cascade: this widget's CSS lives in a <style> block INSIDE the
   section, i.e. later in document order than this file, and at equal
   specificity (`#karriere .sb-dot i`). Order therefore decides it and the page
   wins — so these need !important. Same trap as `#uk-clearing`'s inline style.
   --------------------------------------------------------------------------- */
#karriere .sb-track{
  margin-inline: 34px !important;      /* keeps a 0%/100% chip clear of the screen edge */
}
#karriere .sb-legend{ margin-inline: 34px !important; }
#karriere .sb-dot i{
  /* --p is the same number as the dot's `left:N%`. translateX(-N%) is a % of the
     CHIP's own width, so the chip slides from left-aligned at 0% to
     right-aligned at 100% — it can never overhang the marker's end of the track.
     Exactly !45's `.u3-rank b` technique.
     If --p is absent the calc() is invalid at computed-value time and the whole
     declaration is dropped, leaving the page's own translateX(-50%) — so this is
     safe to ship BEFORE the generator emits --p. */
  transform: translateX(calc(var(--p) * -1%)) !important;
  max-width: max-content;
}

/* ===========================================================================
   C6 / C10 / C1 — colour decisions (approved 2026-07-26)
   =========================================================================== */

/* C6 — semantic colour tokens, deliberately NOT the brand accents.
   Before: "positive" was an undocumented green, sitting next to the brand lime
   #C6FA02, so two different hues both read as "good" in different widgets.
   Brand blue #1300FF and lime #C6FA02 stay brand; these three carry meaning.
   Values chosen for >=4.5:1 on white and >=4.5:1 on the dark ground. */
:root{
  --sem-positive:#0B6E52;  --sem-positive-bg:#E6F5EF;
  --sem-warning:#9A5B06;   --sem-warning-bg:#FCF3E3;
  --sem-critical:#C2263C;  --sem-critical-bg:#FDEAEE;
}
@media (prefers-color-scheme: dark){
  :root{
    --sem-positive:#5FD9AE; --sem-positive-bg:#0D2620;
    --sem-warning:#E9B054;  --sem-warning-bg:#2A2010;
    --sem-critical:#FF8098; --sem-critical-bg:#2A1220;
  }
}
/* route the existing ad-hoc greens at the token */
.pos, .up, .growth, .roi-pos, .rec-feats li svg,
[class*="positive"], [class*="-up"]{ color: var(--sem-positive); }
.badge-pos, .chip-pos{ background: var(--sem-positive-bg); color: var(--sem-positive); }

/* C10 — one series colour per chart.
   Before: the three earnings bars each used a different gradient (blue/violet,
   violet/purple, pale blue/steel) for ONE series, so hue implied a distinction
   that does not exist. One ramp, differentiated by length only. */
.sb-band, .earn-fill, .u3-band-fill, .band-fill, .pay-fill{
  background: linear-gradient(90deg,
              color-mix(in srgb, var(--sm-blue, #1300FF) 22%, #fff),
              var(--sm-blue, #1300FF)) !important;
  border-color: color-mix(in srgb, var(--sm-blue, #1300FF) 25%, transparent) !important;
}
/* the marker keeps the lime accent — it is the ONE thing that should pop */
.sb-dot{ background: var(--accent, #C6FA02) !important; }

/* C1 — FAQ toggle: keep the closed (white / outlined) treatment when open.
   Owner: "should be the same design as before the white etc." So the open state
   no longer inverts to solid near-black; only the glyph rotates + -> x.
   These pages define .pl in a <style> INSIDE the section, i.e. later in document
   order at equal specificity, so !important is required to win. */
.faq details[open] summary .pl,
.faq summary .pl{
  background: #fff !important;
  border: 1.5px solid var(--g200, #E4E4E7) !important;
  color: var(--ink, #0A0A0A) !important;
}
@media (prefers-color-scheme: dark){
  .faq details[open] summary .pl, .faq summary .pl{
    background: transparent !important;
    border-color: var(--g200, #2B2F4D) !important;
    color: var(--ink, #EEF0FA) !important;
  }
}

/* accessibility: every sticky/fixed layer must respect reduced motion */
@media (prefers-reduced-motion: reduce){
  .progress, .faq-t::after, .acc-h::after, .subnav-inner{ transition: none !important; }
}
