/* ==========================================================================
   FALCON-Bladder cohort and eligibility application.

   Typography and colour are taken from the OncoDiagnostics explorer
   (shiny-explorer-v1.1.1/app.R, the CSS block) so the two applications read as
   one system. The information architecture is NOT shared: none of
   OncoDiagnostics' page classes, masthead layout or nav position is imported
   beyond the table and note treatments named in VISUAL_BUILD_BLUEPRINT
   section 2.

   Every colour is a token on :root. No screen declares a raw hex.
   ========================================================================== */

:root {
  /* OncoDiagnostics core */
  --teal:        #185C62;   /* active state, accent, link */
  --teal-dark:   #12474C;   /* hover, chip text */
  --teal-light:  #DCEBEC;   /* selected row tint, chip background */
  --ink:         #111111;   /* primary text and hard rules */
  --white:       #FFFFFF;
  --supp:        #A62C2B;   /* suppression only. Never decorative. */

  /* Nemesis palette */
  --charcoal:    #121617;   /* sidebar ground */
  --turquoise:   #18B7B3;   /* sidebar active item, sidebar wordmark */
  --mint:        #72F2D9;   /* sidebar hover only */

  /* ---- neutrals --------------------------------------------------------
     TEXT COLOUR RULE (AA, 20 Aug 2026). Ordinary user-facing text is ink.
     That covers body text, table values, row and column labels, table
     headers, tab labels, control and selector labels, legends, panel
     subtitles, footnotes and every word on the Definitions and Data
     Partners pages. None of it is muted, greyed or set at reduced opacity.

     Grey survives in four places only, and each is a non-content use:
       - borders, separators and rules            --rule, --rule-soft
       - neutral panel grounds                    --panel-tint, --panel-edge
       - input placeholder text
       - the values that are NOT ordinary data:
           suppressed          .supp    --supp (red, not grey)
           not available / NA  .na      --ink-5 + italic
           the whole-screen not-available state   .na-box
         These must stay visually distinct from reported data, which is why
         they keep a treatment ordinary values are not allowed to have.

     --ink-2 .. --ink-5 are therefore no longer applied to running text.
     --ink-4 and --ink-5 remain for the not-available marks and for input
     chrome; --ink-2 and --ink-3 are kept as tokens so a future non-content
     use has a declared value rather than a raw hex. */
  --ink-2:       #444444;
  --ink-3:       #555555;
  --ink-4:       #666666;
  --ink-5:       #777777;
  --rule-hard:   #111111;
  --rule:        #DDDDDD;
  --rule-soft:   #F0F0F0;
  --panel-tint:  #FAFAFA;
  --panel-edge:  #EAEAEA;

  /* derived, sidebar only */
  --side-item:   #D5DBDC;
  --side-mute:   #7C8788;
  --side-active: #1B4F52;
  --side-rule:   #2A3132;
  --side-card:   #1A1F20;

  /* bar track. One fill colour only, --teal. */
  --bar-track:   #E8E8E8;

  /* The two banner tokens are deleted with the banner itself (AA, 20 Aug 2026),
     so no rule can reintroduce that treatment by reaching for a colour that is
     still declared. */
  --grp-bg:      #F5F7F8;

  /* ---- type scale ------------------------------------------------------
     Every working size in the application is declared here and nowhere else.
     No screen module and no rule below sets a raw px font-size, so a
     readability decision is made once and reaches every page.

     Raised for comfortable reading at 100% browser zoom (AA, 20 Aug 2026).
     The hierarchy and the relative steps between levels are unchanged: each
     token moved up together, so nothing that used to be smaller than
     something else has become larger than it. 13px is a hard floor for any
     text a reader has to read; only --fs-glyph is allowed below it, and it
     drives non-reading marks (the info dot, the carets, the link arrow)
     whose size is geometry rather than legibility. */
  --fs-glyph:      12px;    /* non-reading glyphs only */
  --fs-micro:      13px;    /* uppercase field, section and column labels */
  --fs-colpartner: 13.5px;  /* partner column heads, natural capitalisation */
  --fs-note:       13.5px;  /* notes, legends, captions, panel subtitles */
  --fs-body:       14px;    /* table body, numeric values, row labels, prose */
  --fs-control:    14.5px;  /* tabs, selectors, buttons, navigation */
  --fs-lead:       15px;    /* in-table group headings, card titles, ledes */
  --fs-sub:        16px;    /* page subtitle, rail panel heading */
  --fs-panel:      17px;    /* panel title */
  --fs-sec:        22px;    /* reference section heading */
  --fs-title:      29px;    /* page title */
  --fs-wordmark:   20px;    /* sidebar wordmark */

  /* ---- branding -------------------------------------------------------
     ONE brand group, upper right of the shell: Nemesis left, FALCON right,
     vertically centred on each other, separated by a small gap so they read
     as one intentional group. There is no OHDSI mark and no lower-right row
     (AA, 20 Aug 2026).

     Neither asset is recoloured, inverted, redrawn, cropped or substituted.
     Only HEIGHT is set here and width stays `auto`, so each keeps its own
     aspect ratio: nemesis-logo.png is 1122x935 (1.20) and falcon-logo.png is
     556x255 (2.18).

     The heights differ deliberately, to balance visual weight rather than to
     match a shared box. The Nemesis mark is a nearly solid dark silhouette
     that fills its frame edge to edge, so it carries far more ink per unit of
     height; the FALCON mark is an open two-leaf form with light interior and
     a much wider footprint. Set to a common height the Nemesis square would
     dominate the pair, so it is given the smaller height (36px -> ~43px wide)
     against FALCON's 40px (-> ~87px wide). Together with the 20px gap the
     group is about 150px wide and 40px tall, which sits inside the existing
     header row without lengthening it, so the analytical workspace loses no
     height and no width. */
  --brand-nem-h:    20px;   /* wide Nemesis wordmark, AA-approved 20Sep26 */
  --brand-falcon-h: 40px;
  --brand-gap:      20px;

  /* ---- application shell metrics --------------------------------------
     One horizontal inset for the whole workspace column. The header, the utility
     row, the tab strip and the results region all use it, which is what puts the
     page title, the tabs, the export control and the Nemesis | FALCON brand group
     on the same left and right lines on every page. Tune the workspace gutter
     here, once. */
  --shell-pad-x:   28px;
  --side-w:        230px;
  /* Prose measure. Reading text is held to a comfortable line length even where
     the region around it is very wide, so a 1920px window does not stretch a
     definition or a note across the screen. */
  --measure:       92ch;
  --measure-prose: 900px;
  /* Column measure for the two reference pages. Wider than the prose measure,
     because a definition table needs the room, and still a cap, because neither
     page should stretch across a very wide window. It reserves nothing for the
     brand marks: those sit in the header row (see .fb-header-brand). */
  --measure-ref:   1180px;
}

/* ---- application shell ------------------------------------------------- */

/* The application is a fixed viewport frame, not a page that grows.
 *
 * .fb-app is exactly one viewport tall and does not scroll. It holds two
 * columns: the charcoal sidebar at a fixed width and the analytical workspace
 * taking the rest. The workspace is itself a column: non-scrolling shell
 * furniture at the top (title, controls, the Nemesis | FALCON brand group, the
 * utility row, the tab strip) and then ONE results region, which takes all the
 * remaining height and scrolls internally. There is no last row: the brand marks
 * are part of the furniture at the top, so nothing is reserved at the foot.
 *
 * Everything the old layout got wrong followed from the page being allowed to
 * grow: the marks drifted with content height, the sidebar's charcoal stopped
 * before the content did, each tab scrolled differently, and the reserved right
 * strip plus a 1560px cap left unexplained white space on a wide window. None of
 * those are page-specific problems and none is fixed page by page. There is no
 * hard-coded height anywhere below, and no absolute positioning: the sidebar's
 * full height, the results region's height and the marks' position are all
 * consequences of the one 100vh flex frame. AA, 20 Aug 2026. */

html, body { height: 100%; margin: 0; padding: 0; overflow: hidden; }

body {
  background: var(--white);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  font-size: var(--fs-body);
}

h1, h2, h3, h4 { color: var(--ink); font-weight: 600; letter-spacing: -0.01em; }

.fb-app {
  display: flex;
  align-items: stretch;
  /* dvh where the browser supports it, so a mobile browser's collapsing toolbar
     does not leave a gap under the sidebar. vh is the fallback, declared first. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* ---- sidebar (mockup_1, mockup_8) ------------------------------------- */

/* The sidebar is server-rendered, so the flex child of .fb-app is Shiny's own
   uiOutput container and not .fb-side itself. Both are given the width and the
   charcoal ground, and both are full height, so the charcoal runs continuously
   from the top of the viewport to the bottom on every page.
 *
 * The sidebar's height is now a property of the shell and nothing else. It does
 * not depend on the height of the result on the right, because the shell no
 * longer grows with that result: there is no page height for the charcoal to
 * fall short of, so no white strip can appear beneath it and no page-specific
 * pixel height is needed anywhere.
 *
 * .fb-side scrolls internally if the navigation plus the Partners card is taller
 * than a short window. The card is pushed to the foot by margin-top: auto and
 * therefore sits at the bottom of the viewport, which is where it is looked for. */
.fb-app > #fb_sidebar {
  flex: 0 0 var(--side-w);
  width: var(--side-w);
  height: 100%;
  min-height: 0;
  background: var(--charcoal);
  display: flex;
  overflow: hidden;
}

.fb-side {
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  min-height: 0;
  background: var(--charcoal);
  border: none;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
}

/* Text only. No logo in the sidebar, no icons beside any nav item. */
.fb-wordmark {
  padding: 28px 20px 0;
  font-size: var(--fs-wordmark);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--turquoise);
  line-height: 1.2;
}

.fb-nav { margin-top: 34px; }

.fb-nav-item {
  display: block;
  padding: 12px 20px;
  font-size: var(--fs-control);
  font-weight: 400;
  color: var(--side-item);
  text-decoration: none;
  border-left: 3px solid transparent;
  line-height: 1.35;
}
.fb-nav-item:hover, .fb-nav-item:focus {
  color: var(--mint);
  text-decoration: none;
  background: transparent;
}
.fb-nav-item.active {
  background: var(--side-active);
  border-left-color: var(--turquoise);
  color: var(--white);
  font-weight: 600;
}
.fb-nav-item.active:hover { color: var(--white); }

/* REFERENCE section (mockup_8). The two icons drawn beside these rows in the
   mock-up are deliberately not built: nav icons are prohibited without
   exception. */
.fb-side-sec {
  margin: 26px 0 0;
  padding-top: 18px;
  border-top: 1px solid var(--side-rule);
}
.fb-side-sec-label {
  padding: 0 20px 6px;
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--side-mute);
}
.fb-nav-item .subl {
  display: block;
  font-size: var(--fs-micro);
  color: var(--side-mute);
  font-weight: 400;
  margin-top: 2px;
}
.fb-nav-item.ref { font-size: var(--fs-body); padding: 9px 20px; }
.fb-nav-item.ref.active .subl { color: var(--teal-light); }

/* Partners card, sidebar footer. */
.fb-side-foot {
  margin-top: auto;
  padding: 18px 16px 22px;
  border-top: 1px solid var(--side-rule);
}
.fb-partners-card {
  background: var(--side-card);
  border-radius: 4px;
  padding: 16px;
}
.fb-partners-card .pc-title {
  font-size: var(--fs-lead);
  font-weight: 600;
  color: var(--white);
  display: flex; align-items: center; justify-content: space-between;
}
.fb-partners-card .pc-sum {
  font-size: var(--fs-note);
  color: #9BA6A7;
  margin: 6px 0 12px;
}
.fb-partners-card .pc-btn {
  display: inline-block;
  width: 100%;
  text-align: center;
  font-size: var(--fs-body);
  padding: 7px 10px;
  background: transparent;
  border: 1px solid var(--turquoise);
  border-radius: 2px;
  color: var(--mint);
  text-decoration: none;
  cursor: pointer;
}
.fb-partners-card .pc-btn:hover { color: var(--white); border-color: var(--mint); }

/* ---- workspace -------------------------------------------------------- */

/* The analytical workspace: the right column of the shell, one viewport tall,
   itself a column of shell furniture (brand group included) then results region.
   There is no brand row beneath the results region any more, so the region runs
   to the foot of the viewport (AA, 20 Aug 2026).
 *
 * RESPONSIVE WIDTH. The 1560px cap is gone. The workspace takes whatever width
 * is left after the sidebar, so on a 1920px window the matrices get roughly
 * 1690px instead of stopping at 1560px with a white band beside them. Nothing is
 * capped here; the only widths held back are reading measures on prose (see
 * --measure) and they are stated on the prose itself, not on the column.
 *
 * The workspace does not scroll and carries no padding of its own: each row below
 * states its own --shell-pad-x inset, so the results region owns its scrollbars
 * and the scrollbar sits at the true right edge of the workspace rather than
 * inside a padded box. */
.fb-work {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--white);
  overflow: hidden;
  padding: 0;
}

/* The page is server-rendered, so the flex child of .fb-work is Shiny's own
   uiOutput container. It has to be the flexing column itself, otherwise the
   results region inside it has no height to take a share of. */
.fb-work > #fb_page {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* STABLE SHELL FURNITURE. These three rows are fixed-height children of the page
   column. They are laid out before the results region and never inside it, so
   switching a tab or a page cannot move the title, the controls, the tab strip or
   the FALCON mark: only the region below them is replaced. */
#fb_page > .fb-header,
#fb_page > .fb-bannerrow,
#fb_page > #fb_tabstrip {
  flex: 0 0 auto;
  min-height: 0;
}

/* Header row. Title and subtitle at the left, controls to their right, the one
   Nemesis | FALCON brand group far right. Nothing else anywhere: no OHDSI mark,
   no second Nemesis mark, no logo in the sidebar and no lower-right row. */
.fb-header {
  display: flex;
  align-items: flex-start;
  gap: 24px;
  padding: 24px var(--shell-pad-x) 14px;
}
.fb-header-titles { flex: 0 1 auto; min-width: 240px; }
.fb-title { font-size: var(--fs-title); font-weight: 600; color: var(--ink);
            letter-spacing: -0.01em; margin: 0 0 6px; }
.fb-subtitle { font-size: var(--fs-sub); color: var(--ink); }
.fb-header-controls {
  flex: 1 1 auto;
  display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap;
  padding-top: 4px;
}
/* THE brand group. One row, Nemesis first and FALCON second in source order, so
   Nemesis is on the left and FALCON on the right. `align-items: center` centres
   the two marks on each other despite their different heights, and --brand-gap
   holds them close enough to read as one group.
 *
 * `flex: 0 0 auto` means the group takes only its own width; .fb-header-controls
 * beside it is `flex: 1 1 auto` and absorbs all the slack, so the group is pushed
 * to the header's right inset and there is no white band to its right. Nothing
 * here is positioned absolutely and nothing is computed from content, so the group
 * is at the same place on every page and every tab, whatever is on screen.
 *
 * `padding-top` aligns the group with the title's cap height rather than the
 * flex-start edge of the row. The group is shorter than the title-plus-subtitle
 * block beside it, so it does not set the header's height and the results region
 * below keeps the height it had when the FALCON mark was here alone. */
.fb-header-brand {
  flex: 0 0 auto;
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--brand-gap);
  padding-top: 8px;
}
.fb-header-brand .nem, .fb-header-brand .falcon {
  margin: 0; flex: 0 0 auto; display: flex; align-items: center;
}
/* Height only, width auto: each asset keeps its own aspect ratio and neither is
   stretched, cropped or distorted. */
.fb-header-brand .nem img    { height: var(--brand-nem-h);    width: auto; display: block; }
.fb-header-brand .falcon img { height: var(--brand-falcon-h); width: auto; display: block; }

/* Control blocks, .ctrlbar typography from OncoDiagnostics. Widened from 210px
   to hold the larger label and value type on one line. */
.fb-ctrl { min-width: 240px; }
/* The header's population slot. It holds a control only on the tabs that
   control governs, so it must take no space on the tabs that supply their own:
   `display: contents` makes the slot generate no box, so the .fb-ctrl inside it
   is the flex item directly and an empty slot contributes neither width nor one
   of .fb-header-controls' 18px gaps. */
.fb-ctrl-slot { display: contents; }
.fb-ctrl > label, .fb-ctrl .control-label {
  display: block;
  font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink); margin-bottom: 5px;
}
.fb-ctrl .form-control, .fb-ctrl .selectize-input {
  font-size: var(--fs-control); color: var(--ink);
  border-color: #D8D8D8; box-shadow: none; border-radius: 2px;
}
.fb-ctrl .shiny-input-container { width: 100%; margin-bottom: 0; }
.fb-ctrl .selectize-input > .item {
  background: #EDF3F3 !important; border: 1px solid #C6D9DA !important;
  color: var(--teal-dark) !important; border-radius: 2px !important;
}

/* The Partners control. A labelled button that opens the shared multi-select,
   so the header control and the sidebar card write to one selection. */
.fb-selectish {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; min-width: 240px;
  background: var(--white); border: 1px solid #D8D8D8; border-radius: 2px;
  padding: 7px 11px; font-size: var(--fs-control); color: var(--ink);
  cursor: pointer; text-align: left;
}
.fb-selectish:hover { border-color: var(--teal); }
.fb-selectish .caret-ish { color: var(--ink); margin-left: 10px; }

/* ---- utility row: results-package date and export --------------------- */

/* The site-level banner is removed (AA, 20 Aug 2026) and its treatment with it:
   there is no .fb-banner rule left to revive it by accident. The row now holds
   the date field, when there is a date, and the export control, which keeps its
   position at the right of the row. */
.fb-bannerrow {
  display: flex; align-items: center; justify-content: flex-end; gap: 18px;
  padding: 0 var(--shell-pad-x);
  margin-bottom: 18px;
}
/* The date renders in ordinary ink, never in the reserved suppression colour, and
   it does not render at all while RESULTS_AS_OF is unset. */
.fb-asof { flex: 0 0 auto; font-size: var(--fs-note); color: var(--ink); }
.fb-export .btn, .fb-panel-foot .btn {
  font-size: var(--fs-body); padding: 7px 13px;
  border: 1px solid var(--rule); background: var(--white);
  color: var(--ink); border-radius: 2px; box-shadow: none;
}
.fb-export .btn:hover, .fb-panel-foot .btn:hover { border-color: var(--teal); color: var(--teal-dark); }

/* ---- tab strip -------------------------------------------------------- */
/* Only the active tab's content exists in the page. The strip is a set of
   links; the body beneath it is one output that is replaced, never appended
   to. There is no hidden tab pane anywhere in the DOM. */

/* The strip spans the workspace, so its bottom rule is the line the results
   region hangs from and it does not move when a tab changes. The inset matches
   the header's, less the tab's own first-item padding, so the first tab's label
   sits on the same left line as the page title. */
.fb-tabs {
  border-bottom: 1px solid var(--rule);
  margin: 0;
  padding: 0 var(--shell-pad-x);
  display: flex; flex-wrap: wrap;
}
.fb-tabs .fb-tab:first-child { padding-left: 0; }
.fb-tab {
  display: block;
  padding: 11px 16px;
  font-size: var(--fs-control); font-weight: 400; color: var(--ink);
  text-decoration: none;
  border: none; border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.fb-tab:hover { color: var(--teal-dark); text-decoration: none; }
.fb-tab.active { color: var(--ink); font-weight: 600; border-bottom-color: var(--teal); }

/* ---- the results region ----------------------------------------------- */

/* THE ONE SCROLLING REGION. .fb-body takes whatever viewport height is left
   below the shell furniture and scrolls internally, on both axes. It is the only
   scroll container in the application apart from the sidebar and the DT widgets'
   own bodies, so:
 *
 *   - a result taller than the region scrolls inside the region. The user cannot
 *     scroll the whole page past the bottom of the sidebar or the marks into more
 *     content, because there is no whole page to scroll.
 *   - a result wider than the region scrolls inside the region too. There is no
 *     application-level horizontal scrollbar: html and body are overflow: hidden
 *     and .fb-work is overflow: hidden.
 *   - the model is identical on every screen and every tab. Cohort Counts, all
 *     three Eligibility tabs, Attrition's Compare partners and Detailed table,
 *     the Demographics matrices, Definitions and Data Partners all put their
 *     content in this same region, so nothing scrolls differently from tab to tab.
 *
 * There are deliberately NO nested scroll boxes around individual components. The
 * matrices used to sit in their own 74vh box, which gave a second vertical
 * scrollbar competing with the page's, and the sticky heading row was anchored to
 * that box rather than to the workspace. Both axes are owned here now, and the
 * matrices' sticky heading row and sticky row-label column are anchored to this
 * region (see table.ovmat), which is what keeps a scrolled matrix readable. */
.fb-body {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  overflow: auto;
  overscroll-behavior: contain;
  padding: 22px var(--shell-pad-x) 26px;
}

/* The content column of the region. Full width: there is no right rail and no
   reserved strip, so the matrices and plots use the whole workspace. */
.fb-main { min-width: 0; }

/* No .fb-rail rule. The rail is gone from the DOM as well: it carried nothing
   once the About panels were removed, and the 300px it reserved for the brand
   marks is exactly the unexplained white space on the right of every analytical
   page. The marks no longer need a strip held clear for them; they sit in the
   header row above this region, not beside or over it (see .fb-header-brand). */

/* ---- panels ----------------------------------------------------------- */

.fb-panel {
  background: var(--white);
  border: 1px solid var(--panel-edge);
  border-radius: 3px;
  padding: 18px;
  margin-bottom: 22px;
}
/* A panel that frames a dense matrix grows to the matrix's own width instead of
   being crossed by it. Without this the panel would stop at the region's width
   and a wide table would run out through its right border, which is the visible
   symptom of putting the horizontal scroll in the region rather than in a box
   inside the panel. Reading text inside the same panel is held to a measure (see
   .metric-sub and .note below) so prose never drives the panel's width.
   Browsers without :has() simply keep the previous behaviour: the frame is
   crossed, nothing breaks and no value moves.
 *
 * The three declarations are one mechanism and all three are load bearing.
 * `fit-content` (not `max-content`) is what clamps the panel to the region when
 * the matrix fits: with `max-content` the panel came out exactly its own
 * padding-plus-border wider than the region, which put a 38px horizontal
 * scrollbar on pages that had nothing to scroll. `box-sizing: border-box` is
 * what makes `min-width: 100%` mean the region's width including that padding,
 * for the same reason. `min-width: 100%` is the floor that stops a narrow matrix
 * leaving white space beside it. Measured at 1440x900: a seven-column matrix now
 * gives panel 1154px inside a 1154px region with no horizontal scroll, and a
 * sixteen-column matrix gives panel 2037px with the scroll in the region. */
.fb-panel:has(.ovwrap) {
  box-sizing: border-box;
  width: -moz-fit-content;
  width: fit-content;
  min-width: 100%;
  max-width: none;
}
.fb-panel-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; margin-bottom: 14px;
}
.fb-panel-titles { flex: 1 1 auto; min-width: 0; }
.fb-panel-controls { flex: 0 0 auto; }
.metric-head { font-size: var(--fs-panel); font-weight: 600; color: var(--ink); margin: 0 0 4px; }
/* The measure keeps a panel subtitle from setting the panel's width and keeps it
   readable on a wide window. */
.metric-sub  { font-size: var(--fs-note); line-height: 1.5; color: var(--ink);
               margin: 0; max-width: var(--measure); }
.fb-panel-foot {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 12px; margin-top: 12px;
}
.fb-grid2 { display: flex; gap: 22px; align-items: flex-start; }
.fb-grid2 > .col-a { flex: 4 1 320px; min-width: 0; }
.fb-grid2 > .col-b { flex: 6 1 420px; min-width: 0; }
@media (max-width: 1180px) { .fb-grid2 { flex-wrap: wrap; } .fb-grid2 > * { flex: 1 1 100%; } }

.note {
  font-size: var(--fs-note); line-height: 1.55; color: var(--ink);
  border-left: 2px solid var(--rule);
  padding-left: 11px; margin: 14px 0 0;
  max-width: var(--measure);
}
/* A note's collapsed remainder. The visible sentence keeps the note's own type;
   the disclosure sits under it as a quiet control, not a card: no border, no
   ground, no shadow, so a panel still has one border and nothing nests. */
.note .notekeep { margin: 0 0 2px; }
.note details.notemore > summary {
  cursor: pointer; list-style: none; color: var(--ink-3);
  font-size: var(--fs-note); padding: 2px 0; display: inline-flex;
  align-items: baseline; gap: 6px; user-select: none;
}
.note details.notemore > summary::-webkit-details-marker { display: none; }
.note details.notemore > summary::before {
  content: "\25B8"; font-size: var(--fs-glyph); line-height: 1; color: var(--ink-4);
}
.note details.notemore[open] > summary::before { content: "\25BE"; }
.note details.notemore > summary:hover { color: var(--ink); }
.note details.notemore > summary:focus-visible {
  outline: 2px solid var(--teal); outline-offset: 2px; border-radius: 2px;
}
.note details.notemore .notemore-body { margin: 4px 0 0; }
.note details.notemore .notemore-body p { margin: 0 0 7px; }
.note details.notemore .notemore-body p:last-child { margin-bottom: 0; }

/* The whole-screen not-available state. This is one of the four places grey is
   still allowed: it is a missing value, not reported data, and it has to stay
   visually distinguishable from a screen that is showing real results. */
.na-box {
  border: 1px solid #E3E3E3; background: #FBFBFB;
  padding: 15px 16px; font-size: var(--fs-note); line-height: 1.55;
  color: var(--ink-3); border-radius: 2px;
  max-width: var(--measure);
}
.na-box .nt { color: var(--ink); font-weight: 600; display: block; margin-bottom: 3px; }
.fb-empty { padding: 34px 18px; text-align: center; color: var(--ink);
            font-size: var(--fs-body); }

/* ---- dense matrix (table.ovmat) --------------------------------------- */

/* The matrices carry up to 11 partner columns. At the raised body size the wide
   ones do not always fit the workspace, so a wide matrix scrolls horizontally.
   Type is never reduced to make a table fit. The sticky header row and sticky
   first column below are what keep a scrolled matrix readable, so they are load
   bearing, not decoration. Row labels wrap; numeric cells do not.
 *
 * .ovwrap IS NOT A SCROLL BOX. It is a frame only: a border, a radius and the
 * gap below the table. It used to be `max-height: 74vh; overflow: auto`, which
 * was the second scroll container the shell rework had to remove. Two problems
 * followed from it. A tall matrix scrolled inside a 74vh box that itself sat in a
 * scrolling page, so there were two vertical scrollbars and the amount of visible
 * table depended on the window rather than on the region. And the sticky heading
 * row anchored to that box, so it was the box, not the workspace, that the
 * heading stuck to.
 *
 * Both axes now belong to the results region, .fb-body, which is the nearest
 * scroll container above the table. That is precisely what makes the sticky
 * heading row and the sticky row-label column work: `top` and `left` below are
 * measured from the results region's own scrollport, so the heading row stops
 * under the tab strip and the row labels stop on the workspace's left gutter
 * line, on every screen and every tab, with one pair of scrollbars. */
.ovwrap {
  overflow: visible;
  border: 1px solid #E6E6E6; border-radius: 2px; margin-bottom: 10px;
}
/* RESPONSIVE WIDTH, table side. `width: auto` alone left a matrix at its natural
   width, so a seven-partner table drew about 830px of columns in a 1150px region
   and the rest of the region was white. `width: 100%` makes the table take the
   width the region actually offers and lets automatic table layout hand the
   surplus to the data columns, so more partner columns are legible before
   anything has to scroll.
 *
 * On a table `width: 100%` is a floor, not a cap, which is the property this
 * needs: automatic table layout uses the larger of the specified width and the
 * table's own minimum content width, so a matrix whose columns need more than the
 * region keeps every column at its natural width and the region scrolls instead.
 * Nothing here shrinks type, narrows a column below its content or drops a
 * label; the row-label column keeps the fixed measure fb_mat() gives it and only
 * the surplus is distributed. */
table.ovmat {
  border-collapse: separate; border-spacing: 0;
  font-size: var(--fs-body); line-height: 1.35;
  color: var(--ink); background: var(--white);
  width: 100%;
}
table.ovmat th, table.ovmat td {
  padding: 6px 10px; white-space: nowrap;
  border-bottom: 1px solid #F2F2F2; font-weight: 400;
}
/* Sticky heading row, anchored to the results region's scrollport (see .ovwrap).
   `top: 0` is the top of that scrollport, which is the line immediately under the
   tab strip, so a scrolled matrix keeps its partner columns named without the
   heading row leaving the workspace. */
table.ovmat thead th {
  position: sticky; top: 0; z-index: 3; background: var(--white);
  border-bottom: 1px solid var(--rule-hard);
  font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink);
  text-align: right; vertical-align: bottom;
}
/* Partner column headings are NOT uppercased. The central partner mapping in
   R/reference.R (SOURCE_FOLDER_TO_DISPLAY) decides how each abbreviation is
   capitalised - Hopkins, Pirha, Tartu, Varha, HUS, UZA, PGH - and the stylesheet
   must not override it. th.colpartner is set by the matrix renderer for every
   heading that is a partner abbreviation, everywhere in the application. */
table.ovmat thead th.colpartner {
  text-transform: none;
  letter-spacing: 0.01em;
  font-size: var(--fs-colpartner);
}
table.ovmat thead th.cpt { z-index: 5; text-align: left; white-space: normal; }
/* Two-row heading, used where several columns belong to one partner (Lab
   coverage by comorbidity: a With and a Without column per partner). The group
   row pins at the top of the scrollport and the sub row pins directly beneath
   it, so a scrolled matrix keeps both halves of the heading. The offset is the
   group row's own height: one line of --fs-colpartner type plus its 6px
   vertical padding. */
table.ovmat thead tr.grouphead th { top: 0; }
table.ovmat thead tr.subhead th { top: 29px; }
table.ovmat thead th.colgroupmain {
  text-align: center;
  border-bottom: 1px solid #E6E6E6;
  border-left: 1px solid #E6E6E6;
}
/* The sub row carries a written label ("With (N=180)"), not a code, so it keeps
   its own capitalisation like a partner heading does. */
table.ovmat thead tr.subhead th {
  border-bottom: 1px solid var(--rule-hard);
  text-transform: none; letter-spacing: 0.01em;
}
table.ovmat thead th .colsub {
  font-size: var(--fs-micro); font-weight: 400; text-transform: none;
  letter-spacing: 0; color: var(--ink); margin-top: 2px;
}
/* Sticky row-label column, anchored to the same scrollport. The offset is the
   shell's own horizontal inset, so a wide matrix scrolled right pins its row
   labels on the workspace gutter rather than against the sidebar. Measured at
   1440x900 the pinned position coincides with the labels' resting position, so
   the label column reads as fixed while the data columns slide under it. */
table.ovmat .cpt {
  position: sticky; left: var(--shell-pad-x); z-index: 2; background: var(--white);
  text-align: left; border-right: 1px solid #E6E6E6;
  white-space: normal; padding-right: 14px;
}
/* Raised from 74px with the body type, so a wider numeric string is not the
   thing that sets the column width. */
table.ovmat td { text-align: right; min-width: 82px; vertical-align: top; }
/* PARITY, not styling. The matrix row-label cell is a `th scope="row"` so each
   data cell resolves to a row header as well as a column header. That takes it
   out of the `table.ovmat td` rule above, which was giving it vertical-align:
   top. Restored here so a label that wraps to two lines or carries a .rowsub
   (Attrition step names, ARTEMIS drug rows) sits exactly where it did in v1.0.
   min-width is deliberately NOT restored: it is inert on this cell, whose width
   is set by the corner cell's inline min-width/max-width, 120-360px at every
   call site and so always above 82px. */
table.ovmat tbody th.cpt { vertical-align: top; }
table.ovmat tbody tr:hover .cpt { background: var(--panel-tint); }
table.ovmat .tid { color: var(--teal-dark); font-weight: 600; margin-right: 7px; }
table.ovmat .cpt a { color: var(--teal-dark); text-decoration: none;
                     border-bottom: 1px dotted #A8C4C6; }
table.ovmat .cpt a:hover { color: var(--teal); border-bottom-color: var(--teal); }
table.ovmat .rowsub { color: var(--ink); font-size: var(--fs-micro); margin-top: 2px; }
table.ovmat .num { text-align: right; }

/* row group / collapsible family header, the journal_gt row-group treatment */
table.ovmat tr.ovgrp td.grpcell {
  background: var(--grp-bg);
  border-top: 1px solid var(--rule-soft);
  border-bottom: 1px solid var(--rule-soft);
  padding: 8px 10px;
}
table.ovmat tr.ovgrp .grplab { font-size: var(--fs-lead); font-weight: 600; color: var(--ink); }
table.ovmat tr.ovgrp .grpcnt { font-size: var(--fs-note); color: var(--ink); margin-left: 7px; }
table.ovmat tr.ovgrp .chev { color: var(--ink); margin-right: 8px; font-size: var(--fs-glyph); }
table.ovmat tr.ovgrp a.grptog { border-bottom: none; color: var(--ink); text-decoration: none; }
table.ovmat tr.ovgrp a.grptog:hover .grplab { color: var(--teal-dark); }

/* the column the attrition display toggle is currently emphasising */
table.ovmat td.emph { font-weight: 600; }

/* the highlighted final attrition row */
table.ovmat tr.finalrow td { background: var(--teal-light); font-weight: 600; }
table.ovmat tr.finalrow td.cpt { background: var(--teal-light); }
/* PARITY, not styling. The final row's label cell is now a th (see the
   row-header note in R/helpers.R fb_mat), so it no longer matches the two rules
   above. Both declarations are restored, so the final attrition row keeps the
   teal ground and the weight it had in v1.0 across its whole width. Nothing
   else in this stylesheet changes for v1.1. */
table.ovmat tr.finalrow th.cpt { background: var(--teal-light); font-weight: 600; }

/* The three display states, kept visually distinct everywhere. These two are the
   other place grey and the reserved red survive the ink rule: a suppressed count
   and a value the partner did not supply are NOT ordinary reported data and must
   not look like it. Ordinary values above are ink, so the contrast runs the right
   way: nothing reported looks muted, and nothing muted looks reported. */
.supp { color: var(--supp); }
.na   { color: var(--ink-5); font-style: italic; }

/* in-cell comparison bar. These are the defaults, used as-is on Attrition, where
   the length encodes the percentage and the colour encodes nothing.
 *
 * On Demographics -> Age and sex the cell is white and the BAR carries the
 * magnitude: fb_bar() is passed an inline fill of pct_scale_color(pct), so the
 * bar's length and its colour are the same percentage. The track below stays the
 * default, which reads against a white cell at both ends of the ramp. No bar at
 * all for a suppressed or missing value, which is enforced in fb_bar(), not
 * here. */
.fbbar {
  height: 6px; border-radius: 2px; background: var(--bar-track);
  margin-top: 3px; width: calc(100% - 8px); min-width: 46px;
}
.fbbar-fill { height: 6px; border-radius: 2px; background: var(--teal); }

/* step badge on the attrition table */
.stepbadge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 23px; height: 23px; border-radius: 50%;
  background: var(--teal-light); color: var(--teal-dark);
  font-size: var(--fs-micro); font-weight: 600;
}

.ovlegend { font-size: var(--fs-note); line-height: 1.5; color: var(--ink);
            margin: 0 0 8px; }
.ovlegend .k { color: var(--ink); font-weight: 600; }
.ovlegend div { margin-bottom: 4px; }
.ovlegend .swatch {
  display: inline-block; width: 11px; height: 11px; border-radius: 2px;
  margin-right: 7px; vertical-align: -1px; border: 1px solid #E0E0E0;
}

/* Continuous cell-ground scale legend (Lab coverage around index date). Direction
   only: no
   band labels, no cut points, no good/poor wording. The gradient itself is
   generated in R from the same interpolation the cells use, so the two cannot
   drift apart. The off-scale swatch is the neutral ground given to suppressed
   and not-reported cells, which sit off the scale entirely. */
.fb-scalelegend {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  font-size: var(--fs-note); color: var(--ink); margin: 0 0 10px;
}
.fb-scalelegend .bar {
  display: inline-block; height: 11px; width: 210px;
  border-radius: 2px; border: 1px solid #E0E0E0;
}
.fb-scalelegend .lo, .fb-scalelegend .hi { color: var(--ink); }
.fb-scalelegend .off { margin-left: 14px; }
.fb-scalelegend .off .sw {
  display: inline-block; width: 11px; height: 11px; border-radius: 2px;
  margin-right: 6px; vertical-align: -1px; border: 1px solid #E0E0E0;
}

/* ---- DT tables -------------------------------------------------------- */

table.dataTable { font-size: var(--fs-body); width: 100% !important; }
table.dataTable thead th {
  border-bottom: 1px solid var(--rule-hard) !important;
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink); font-weight: 600;
}
table.dataTable td { border-top: 1px solid var(--rule-soft) !important; color: var(--ink); }
/* The DT tables (attrition detail, coverage detail, the partner directory) keep
   their own horizontal scroll at the raised body size. */
.dataTables_wrapper { margin-bottom: 10px; overflow-x: auto; }
/* DT chrome, sized explicitly rather than by a blanket rule on the wrapper, so
   nothing inside the table inherits a size by accident. */
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_length { font-size: var(--fs-note); }
.dataTables_wrapper .dataTables_filter input,
.dataTables_wrapper .dataTables_length select {
  border: 1px solid #D8D8D8; border-radius: 2px; font-size: var(--fs-control);
}

/* ---- segmented toggles (radioGroupButtons) --------------------------- */

.fb-seg .btn-group > .btn {
  font-size: var(--fs-body); padding: 6px 13px;
  border: 1px solid var(--rule); background: var(--white);
  color: var(--ink); border-radius: 2px; box-shadow: none; font-weight: 400;
}
.fb-seg .btn-group > .btn.active,
.fb-seg .btn-group > .btn:active {
  background: var(--teal); border-color: var(--teal);
  color: var(--white); font-weight: 600; box-shadow: none;
}
.fb-seg .control-label {
  font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink); margin: 0 8px 0 0;
}
.fb-seg { display: inline-flex; align-items: center; gap: 8px; }

.fb-radios .control-label {
  font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink); margin-bottom: 5px;
}
.fb-radios .radio label, .fb-radios .radio-inline {
  font-size: var(--fs-body); color: var(--ink);
}

.fb-controlrow {
  display: flex; align-items: flex-end; gap: 22px; flex-wrap: wrap;
  padding: 14px 16px 14px;
  background: var(--panel-tint); border: 1px solid var(--panel-edge);
  border-radius: 3px; margin-bottom: 18px;
}
.fb-controlrow .shiny-input-container { margin-bottom: 0; }

/* ---- the retired About panel, and the retired shell brand row --------- */

/* No screen emits .fb-about any more (AA, 20 Aug 2026: the About panel is
   removed from every analytical screen and not replaced). The rules are kept
   because fb_about_block() and fb_rail(about_blocks) are still in the codebase,
   so a future screen-specific block would render correctly rather than unstyled.
   Nothing below is currently reachable. */
.fb-about {
  background: var(--white);
  border: 1px solid var(--panel-edge);
  border-radius: 3px;
  padding: 18px;
}
.fb-about h2 { font-size: var(--fs-sub); font-weight: 600; color: var(--ink); margin: 0 0 8px; }
.fb-about p  { font-size: var(--fs-body); line-height: 1.55; color: var(--ink); margin: 0 0 10px; }
.fb-about ul { padding-left: 18px; margin: 0 0 10px; }
.fb-about li { font-size: var(--fs-body); line-height: 1.55; color: var(--ink); margin-bottom: 6px; }
.fb-about .blk + .blk {
  border-top: 1px solid var(--rule-soft);
  margin-top: 14px; padding-top: 14px;
}
.fb-about a { color: var(--teal); font-weight: 600; text-decoration: none;
              font-size: var(--fs-body); }
.fb-about a:hover { text-decoration: underline; }
.fb-about a .ext { font-size: var(--fs-glyph); margin-left: 4px; }

/* There is no .fb-shell-brand rule any more, and no lower-right brand row for it
   to style (AA, 20 Aug 2026).
 *
 * The row it styled held the Nemesis and the OHDSI marks at the bottom right of
 * the workspace column. Both the row and the OHDSI mark are REMOVED. Branding is
 * the single upper-right Nemesis | FALCON group in .fb-header-brand above, which
 * is the only branding treatment in this stylesheet: there is exactly one Nemesis
 * mark and one FALCON mark, no OHDSI mark (ohdsi-logo.png stays in www/ as an
 * unused file and no rule or markup references it), and no logo in the sidebar.
 *
 * The rule is deleted rather than left in place unused, so no future edit can
 * revive a second placement by re-emitting the class.
 *
 * NO GAP IS LEFT BEHIND. The row was the last child of the workspace flex column
 * and reserved its own height there. With it gone, #fb_page is the column's only
 * child and is `flex: 1 1 auto`, so it takes the full viewport height and the
 * results region inside it grows by exactly the height the row used to hold. No
 * compensating padding, height or margin is needed anywhere, and the marks still
 * cannot overlap a result: they are above the results region now, not beside or
 * beneath it. */

/* ---- reference pages -------------------------------------------------- */

/* Reading measure for Definitions and Data Partners. The second term of the old
   `min(1180px, calc(100% - 340px))` reserved a 340px right strip for the brand
   marks, and it was the binding term at ordinary window sizes: at a 1440px window
   it held these pages to about 814px inside a 1150px region, which is the white
   band on the right of the reference pages. The marks are a row of the shell now
   and need no strip, so the reserve is gone and only the reading measure is left.
   The cap itself stays: these pages are prose and a definition table, and neither
   should be stretched across a 1920px window. Prose inside is held tighter still
   (see .lede and .prose below). */
.fb-ref { max-width: var(--measure-ref); padding-bottom: 8px; }

/* A listed cohort that belongs to no T family renders as its own small table
   directly beneath the Target population section, with no heading, because it has
   no family and none is invented for it. */
.fb-ref .m-sec-plain { margin-top: 28px; padding-top: 20px; }
.fb-ref .m-sec {
  margin: 40px 0 0; padding-top: 24px; border-top: 1px solid #E0E0E0;
}
.fb-ref .m-sec > h2 {
  font-size: var(--fs-sec); font-weight: 700; margin: 0 0 18px;
  display: flex; gap: 12px; align-items: baseline; letter-spacing: -0.01em;
}
.fb-ref .m-sec > h2 .num { color: var(--teal); font-weight: 700; }

/* Reference-page prose. These two rules replace the four inline font-size
   declarations that used to sit in R/screen_reference.R, so the reference pages
   are driven by the same central scale as every other screen and no screen
   module sets a size of its own. Colour and measure are unchanged. */
.fb-ref .lede {
  font-size: var(--fs-lead); line-height: 1.6; color: var(--ink);
  max-width: var(--measure-prose);
}
.fb-ref .prose { font-size: var(--fs-body); line-height: 1.6; color: var(--ink);
                 max-width: var(--measure-prose); }
.fb-ref ul.prose li { font-size: var(--fs-body); line-height: 1.6;
                      margin-bottom: 6px; }

.fb-deftable { width: 100%; border-collapse: separate; border-spacing: 0; }
.fb-deftable th {
  font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink);
  border-bottom: 1px solid var(--rule-hard); padding: 7px 11px; text-align: left;
}
.fb-deftable td {
  font-size: var(--fs-body); line-height: 1.5; color: var(--ink);
  padding: 9px 11px; vertical-align: top;
  border-top: 1px solid var(--rule-soft);
}
/* The T identifier keeps the teal accent: it is the column's key, not muted
   secondary text. Widened with the body type so "T1A" and the longest T id sit
   on one line. */
.fb-deftable td.tidcol { width: 104px; color: var(--teal-dark); font-weight: 600; }
/* Widened for the longer short names. */
.fb-deftable td.namecol { width: 290px; }
/* The pre-wrap that used to sit here existed only to preserve doubled spaces in
   the reference text. Those are now normalised in
   apply_cohort_text_corrections(), so the cell wraps normally. */

/* .fb-defnote has no caller: the Definitions page carries no notes beneath a
   definition any more. Kept as a declared treatment, not reachable. */
.fb-defnote { font-size: var(--fs-note); line-height: 1.5; color: var(--ink);
              margin-top: 6px; }

/* ---- partner picker modal -------------------------------------------- */

.fb-modal-actions { margin-bottom: 12px; display: flex; gap: 8px; }
.fb-modal-actions .btn {
  font-size: var(--fs-body); padding: 5px 13px; border: 1px solid var(--rule);
  background: var(--white); color: var(--ink-2); border-radius: 2px;
}
.modal-body .checkbox label { font-size: var(--fs-body); color: var(--ink); }
.modal-title { font-size: var(--fs-sub); font-weight: 600; color: var(--ink); }
.modal-footer .btn { font-size: var(--fs-body); }

/* ---- plots ------------------------------------------------------------ */

.shiny-plot-output { max-width: 100%; }
