*.external::after {
  content: "";
  display: inline-block;
  width: 0.7em;
  height: 0.7em;
  margin-left: 0.2em;
  background-color: currentColor;

  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='black' d='M432 320h-32a16 16 0 00-16 16v112H64V128h144a16 16 0 0016-16V80a16 16 0 00-16-16H48a48 48 0 00-48 48v352a48 48 0 0048 48h352a48 48 0 0048-48V336a16 16 0 00-16-16zM488 0H360c-21.37 0-32.05 25.91-17 41l35.73 35.73L135 320.37a24 24 0 000 34L157.67 377a24 24 0 0034 0l243.61-243.68L471 169c15 15 41 4.5 41-17V24a24 24 0 00-24-24z'/%3E%3C/svg%3E");
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;

  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='black' d='M432 320h-32a16 16 0 00-16 16v112H64V128h144a16 16 0 0016-16V80a16 16 0 00-16-16H48a48 48 0 00-48 48v352a48 48 0 0048 48h352a48 48 0 0048-48V336a16 16 0 00-16-16zM488 0H360c-21.37 0-32.05 25.91-17 41l35.73 35.73L135 320.37a24 24 0 000 34L157.67 377a24 24 0 0034 0l243.61-243.68L471 169c15 15 41 4.5 41-17V24a24 24 0 00-24-24z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}

/* Site header (components/header.py), rebuilt after the one on
   https://www.fz-juelich.de/en/ice/ice-3 (measured at 1280 px): logo and a
   light divider (22 px either side), the institute in 18 px over the name in
   30 px, both on 42 px lines and level with the logo's bottom, then the
   navigation in 24 px under the titles, its text in line with theirs, the
   current and the hovered item marked by a bar in Jülich blue. Below 992 px
   logo and titles stack and the menu button replaces the navigation, as
   there. --logo-h is the height of the logo itself; its image adds 0.3 of
   that on every side. */
:root {
  --logo-h: 56px;
  --fz-blue: #023d6b;
}
.site-header {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  max-width: 1320px;
  margin: 0 auto;
  padding: 36px 16px 0;
}
.site-logo-link {
  grid-column: 1;
  grid-row: 1;
  padding-right: 22px;
  border-right: 1px solid #d8d8d8;
}
.site-logo {
  display: block;
  height: calc(var(--logo-h) * 1.6);
  width: auto;
}
.site-titles {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding-left: 22px;
}
.site-institute {
  font-size: 18px;
  line-height: 42px;
  color: var(--fz-blue);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.site-title {
  font-size: 30px;
  font-weight: 400;
  line-height: 42px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The menu button: three 2 px bars, 40 px wide, 10 px apart, in a 70 x 62
   button -- the "hamburgers" collapse button the fz-juelich.de header uses
   --, turning into a cross while the menu is open. */
.site-burger {
  grid-column: 3;
  grid-row: 1;
  align-self: center;
  display: none;
  position: relative;
  z-index: 530;               /* above the open menu, to close it again */
  width: 70px;
  height: 62px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.site-burger-box {
  position: absolute;
  left: 15px;
  top: 19px;
  width: 40px;
  height: 24px;
}
.site-burger-inner,
.site-burger-inner::before,
.site-burger-inner::after {
  position: absolute;
  left: 0;
  width: 40px;
  height: 2px;
  border-radius: 4px;
  background: #000;
  transition: transform 0.15s ease, top 0.15s ease, opacity 0.15s ease;
}
.site-burger-inner {
  top: 11px;
}
.site-burger-inner::before,
.site-burger-inner::after {
  content: "";
}
.site-burger-inner::before {
  top: -10px;
}
.site-burger-inner::after {
  top: 10px;
}
.site-burger.open .site-burger-inner {
  transform: rotate(45deg);
}
.site-burger.open .site-burger-inner::before {
  top: 0;
  opacity: 0;
}
.site-burger.open .site-burger-inner::after {
  top: 0;
  transform: rotate(-90deg);
}

/* The menu itself (components/navigation.py): a light panel over the page
   from under the logo down, one row per page in 20 px with a chevron, the
   rows divided by thin black lines. */
.menu-drawer {
  display: none;
}
@media (max-width: 991px) {
  .menu-drawer.open {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: var(--menu-top);
    z-index: 520;
    overflow-y: auto;
    padding-bottom: 80px;
    background: rgb(244, 247, 248);
  }
}
.menu-sections {
  list-style: none;
  /* The panel starts under the logo, where the button (now a cross, 62 px)
     still sits: the rows begin below it. */
  margin: 72px 0 20px;
  padding: 0 30px;
}
.menu-sections li {
  border-bottom: 1px solid #000;
}
.menu-sections a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 0;
  font-size: 20px;
  font-weight: 400;
  line-height: 36px;
  color: #000;
  text-decoration: none;
}
.menu-chevron {
  height: 36px;
  width: 36px;
}
.site-tabs {
  grid-column: 2 / 4;
  grid-row: 2;
  display: flex;
  margin-left: -5px;          /* item text (28 px in) under the titles (23 px in) */
}
/* Measured there: the item is 58 px (20 + 24 + 14), with a transparent
   10 px border below; the current page gets a 3 px bar right under the item,
   the hovered one an 11 px bar (which on fz-juelich.de runs into the opening
   submenu). Both as wide as the text. */
.site-tab {
  position: relative;
  width: auto;
  padding: 20px 28px 14px;
  border-bottom: 10px solid transparent;
  border-radius: 0;
  background: transparent !important;
}
.site-tab .mantine-NavLink-label {
  display: block;             /* an inline 24 px span is 27 px tall */
  font-size: 24px;
  font-weight: 500;
  line-height: 24px;
  color: var(--fz-blue);
  white-space: nowrap;
}
.site-tab .mantine-NavLink-body {
  overflow: visible;
  line-height: 24px;          /* the item is 58 px high, as there */
}
.site-tab::after {
  content: "";
  position: absolute;
  left: 28px;
  right: 28px;
  top: 100%;                  /* right under the item, on its 10 px border */
  height: 0;
  background: var(--fz-blue);
}
.site-tab[data-active]::after {
  height: 3px;
}
.site-tab:hover::after {
  height: 11px;
}

@media (max-width: 991px) {
  :root {
    --logo-h: 40px;
    --menu-top: calc(16px + var(--logo-h) * 1.6);   /* right under the logo */
  }
  .site-header {
    padding-top: 16px;
    padding-bottom: 8px;
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .site-logo-link {
    grid-column: 1 / -1;
    justify-self: start;
    padding-right: 0;
    border-right: 0;
  }
  .site-titles {
    grid-column: 1;
    grid-row: 2;
    padding-left: calc(var(--logo-h) * 0.3);   /* in line with the logo */
  }
  .site-burger {
    grid-column: 2;
    grid-row: 2;
    display: block;
  }
  .site-tabs {
    display: none;
  }
  .site-institute {
    font-size: 15px;
    line-height: 24px;
  }
  .site-title {
    font-size: 24px;
    line-height: 32px;
  }
}
@media (max-width: 575px) {
  :root {
    --logo-h: 30px;
    --menu-top: calc(8px + var(--logo-h) * 1.6);
  }
  .site-header {
    padding-top: 8px;
  }
  .site-institute-parent {
    display: none;
  }
  .site-institute {
    font-size: 12px;
    line-height: 18px;
  }
  .site-title {
    font-size: 20px;
    line-height: 26px;
  }
}

.page-wrap {
  padding-top: 0.5rem;
  padding-bottom: 2rem;
}

/* minmax(0, ...) rather than 1fr: a plain 1fr track is at least as wide as
   its widest unbreakable content (the time-aggregation tabs), which pushed
   the whole column -- sidebar included -- past the edge of a phone. */
.heatmap-grid,
.page-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}
.heatmap-grid > *,
.page-grid > * {
  min-width: 0;
}

.heatmap-sidebar {
  display: block;
}
.heatmap-main {
  display: block;
}

/* Single column: the map comes first, the settings below it -- their
   summaries already say what is shown, and nobody should scroll past five
   accordion rows to see the map at all. */
@media (max-width: 991px) {
  .heatmap-main,
  .page-content {
    order: -1;
  }
}

/* Expert mode: controls marked .expert-only are hidden for standard users.
   The expert-mode switch (in the footer; see components/navigation.py)
   sets the class "standard-view" on #app-frame,
   which activates this rule app-wide. */
.standard-view .expert-only {
  display: none;
}

@media (min-width: 992px) {
  .heatmap-grid,
  .page-grid {
    grid-template-columns: 300px minmax(0, 1fr);
  }
  /* The settings start level with the top of the map itself, below the
     figure's white header band: 10 px margin plus the band's 15 % of the
     rest (figures/heatmap.py, _HEADER_DOMAIN_TOP), for the height the
     graph actually gets (--heatmap-height within its min and max). */
  .heatmap-sidebar {
    position: sticky;
    top: 12px;
    padding-top: calc(10px + 0.15 * (clamp(var(--heatmap-min-height),
                                           var(--heatmap-height),
                                           var(--heatmap-max-height)) - 20px));
  }
}

@media (min-width: 1200px) {
  .heatmap-grid,
  .page-grid {
    grid-template-columns: 360px minmax(0, 1fr);
  }
}

.heatmap-card { overflow: hidden; }

/* The settings column of the Zeppelin and validation pages while it spans
   the page width (columns stacked, but wider than a phone): the
   settings on the left, the page's small map beside them instead of under
   them, a line between the two. */
.settings-split {
  display: flex;
  flex-direction: column;
  gap: var(--mantine-spacing-sm);
}
/* From 768 px: each column is then at least as wide as the settings column
   on a desktop (300 px), so no section title has to be cut. */
@media (min-width: 768px) and (max-width: 991px) {
  .settings-split {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--mantine-spacing-lg);
    align-items: start;
  }
  .settings-split > :nth-child(2) {
    border-left: 1px solid var(--mantine-color-gray-3);
    padding-left: var(--mantine-spacing-lg);
  }
}

/* Settings section titles (components/accordion.py): each line on one line,
   an over-long value cut with an ellipsis rather than wrapped. */
.mantine-Accordion-label {
  min-width: 0;
}
.settings-title > * {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.footer-links > * {
  white-space: nowrap;
}
.heatmap-inner {
  display: flex;
  flex-direction: column;
}
/* Map height, read by the inline style of the graph (components/heatmap.py).
   Tall enough to use the screen, short enough that the time slider below
   stays in view: the subtracted part is header, gaps and the time-control box
   plus the old-site banner Apache adds in production. The px cap keeps the
   figure -- and with it the proportional header band -- from growing
   unboundedly when the page is zoomed out. */
:root {
  --heatmap-height: calc(100dvh - 360px);
  --heatmap-min-height: 420px;
  --heatmap-max-height: 800px;
}

/* On a phone the map is far narrower than tall, so a viewport-tall figure is
   mostly empty margin above and below Europe: size it from the width. */
@media (max-width: 767px) {
  :root {
    --heatmap-height: calc(75vw + 70px);
    --heatmap-min-height: 0px;
  }
}

.heatmap-graph {
  flex: 1 1 auto;
  width: 100%;
}

/* On a phone the toolbar sits on the title line, and its zoom buttons are
   what the two-finger gestures replace. */
@media (max-width: 767px) {
  #air-quality-map .modebar-container {
    display: none;
  }
}

.controls-box {}
/* The slider's first and last date labels sit centred on its ends, so half
   of each hangs outside: room for them on a phone, where nothing else does. */
.time-control {
  padding: 0 20px;
}
@media (min-width: 768px) {
  .time-control {
    padding-left: var(--map-inset-left);
    padding-right: var(--map-inset-right);
  }
}
.controls-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.controls-btn {
  white-space: nowrap;
  flex: 0 0 auto;
  min-width: 5rem;
}
.slider-wrap {
  flex: 1 1 auto;
  min-width: 0;
  /* The first date label sits centred on the track's start; half of it
     would otherwise reach under the play button. */
  padding-left: 1.25rem;
}

/* Five aggregation tabs do not fit a phone on one line each: let the longer
   labels ("Daily mean") break instead of overflowing the box. */
@media (max-width: 575px) {
  .controls-box .mantine-SegmentedControl-label {
    white-space: normal;
    line-height: 1.2;
    padding-left: 4px;
    padding-right: 4px;
    font-size: var(--mantine-font-size-xs);
  }
}
.hour-slider { width: 100%; }
/* A phone's slider is about 200 px for five days: smaller dates fit. */
@media (max-width: 575px) {
  .hour-slider .mantine-Slider-markLabel {
    font-size: 11px;
  }
}

.mantine-Select-option > span,
.mantine-Combobox-option > span {
  white-space: pre-wrap !important;
}
.mantine-Select-option > span::first-line {
  font-weight: 600;
}

/* Check mark of the selected option. The species select renders its options
 * itself (assets/dmc_functions.js) to get a per-option tooltip, which means
 * re-rendering this icon too -- matching Mantine's own size and opacity. */
.mantine-Select-option > svg.select-check-icon {
  width: 0.7rem;
  height: 0.7rem;
  flex: 0 1 auto;
  opacity: 0.4;
}

.mantine-Input-input > span,
.mantine-Select-input > span {
  white-space: pre-wrap !important;
}
.mantine-Select-input > span::first-line {
  font-weight: 600;
}


/* copy from validation website */

.headline{
    color: #023d6b;
    font-size: 30px;
}

.fzblue{
    color: #023d6b;
}

.link{
    color: #023d6b;
    text-decoration: none;
}

.link:hover{
    text-decoration: underline;
}

.dropdown{
    zIndex: 1;
}

/* The look of a Mantine Paper (withBorder, radius lg, shadow sm) for the
   boxes that are plain divs: the statistic tiles on the validation page. */
.statistic {
    min-height: 0;
    overflow: hidden;
}
.paper-big{
    border: 1px solid var(--mantine-color-gray-3);
    border-radius: var(--mantine-radius-lg);
    padding: 10px;
    background-color: white;
    box-shadow: var(--mantine-shadow-sm);
}

.paper-small{
    /*border: 1px solid #ccc;*/
    border-radius: 10px;
    padding: 5px;
    background-color: white;
    box-shadow: rgba(100, 100, 111, 0.2) 0px 2px 7px 0px;
}

/* Empty-state notice for the validation page: shown whenever the filters
   match no stations or the measurement database cannot be reached. Collapses
   to nothing when the callback clears it, so it costs no space in the normal
   case. */
.status-banner:empty{
    display: none;
}

.status-banner{
    padding: 10px 15px;
    border-radius: var(--mantine-radius-lg);
    border-left: 4px solid #023d6b;
    background-color: #eef3f8;
    color: #023d6b;
}

/* Validation page (components/validation.py): the map under the controls,
   the statistics and the time series at the right. Fixed heights throughout:
   the statistic plots are responsive, and a block that took its height from
   them grew with every resize. */
.validation-map {
    height: 300px;
    overflow: hidden;
}
/* A phone: the map alone across the width. */
@media (max-width: 575px) {
    .validation-map {
        height: min(70vw, 420px);
    }
}
.validation-stats {
    height: 300px;
    min-height: 0;
}
.validation-timeseries {
    --validation-timeseries-height: 460px;
}
@media (max-width: 991px) {
    .validation-stats {
        height: 340px;
    }
}

#statistic-parent{
    display: grid;
    /* minmax(0, ...): a plain 1fr row is at least as tall as its content,
       and the content is a plot sizing itself to the row. */
    grid-template-columns: repeat(6, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
    grid-column-gap: 10px;
    grid-row-gap: 10px;
}

#mean-div{ grid-area: 1 / 1 / 3 / 3; }
#rmse-div{ grid-area: 1 / 3 / 2 / 5; }
#corr-div{ grid-area: 2 / 3 / 3 / 5; }
#bias-div{ grid-area: 1 / 5 / 2 / 7; }
#mmb-div { grid-area: 2 / 5 / 3 / 7; }

/* Phones: mean on its own row, the four others two by two below it. */
@media (max-width: 575px) {
    #statistic-parent {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        grid-template-rows: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr);
    }
    #mean-div { grid-area: 1 / 1 / 2 / 3; }
    #rmse-div { grid-area: 2 / 1 / 3 / 2; }
    #bias-div { grid-area: 2 / 2 / 3 / 3; }
    #corr-div { grid-area: 3 / 1 / 4 / 2; }
    #mmb-div  { grid-area: 3 / 2 / 4 / 3; }
    .validation-stats {
        height: 420px;
    }
}

/* The small maps (MapLibre via Plotly) render without the maplibre-gl
   stylesheet, so their control containers lose their absolute positioning and
   the initially expanded attribution overflows BELOW the map -- into the time
   series on the meteochemogram, and over the live-position row on the Zeppelin
   page. Anchor the controls inside the map and collapse the attribution to a
   small (i) toggle; the full OSM credit appears on hover/focus, which the OSM
   attribution guidelines permit for small maps.

   Both maps get the identical treatment, so the rules list both ids rather
   than growing a second copy that can drift. */
#meteochemogram-plot .maplibregl-ctrl-bottom-right,
#zeppelin-map .maplibregl-ctrl-bottom-right {
  position: absolute;
  bottom: 0;
  right: 0;
  z-index: 2;
}

#meteochemogram-plot .maplibregl-ctrl-attrib,
#zeppelin-map .maplibregl-ctrl-attrib {
  margin: 0;
  text-align: right;
}

#meteochemogram-plot .maplibregl-ctrl-attrib-button,
#zeppelin-map .maplibregl-ctrl-attrib-button {
  list-style: none;
  cursor: pointer;
  display: block;
  width: 16px;
  height: 16px;
  margin-left: auto;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  text-align: center;
}

#meteochemogram-plot .maplibregl-ctrl-attrib-button::marker,
#zeppelin-map .maplibregl-ctrl-attrib-button::marker {
  content: "";
}

#meteochemogram-plot .maplibregl-ctrl-attrib-button::after,
#zeppelin-map .maplibregl-ctrl-attrib-button::after {
  content: "i";
  font: italic bold 11px/16px serif;
  color: #333;
}

#meteochemogram-plot .maplibregl-ctrl-attrib-inner,
#zeppelin-map .maplibregl-ctrl-attrib-inner {
  display: none;
}

#meteochemogram-plot .maplibregl-ctrl-attrib:hover .maplibregl-ctrl-attrib-inner,
#zeppelin-map .maplibregl-ctrl-attrib:hover .maplibregl-ctrl-attrib-inner,
#meteochemogram-plot .maplibregl-ctrl-attrib:focus-within .maplibregl-ctrl-attrib-inner,
#zeppelin-map .maplibregl-ctrl-attrib:focus-within .maplibregl-ctrl-attrib-inner {
  display: block;
  white-space: nowrap;
  font-size: 9px;
  line-height: 14px;
  background: rgba(255, 255, 255, 0.9);
  padding: 0 4px;
  border-radius: 3px 0 0 0;
}

/* Touch on the forecast map: one finger scrolls the page, two fingers pan and
   zoom the map (handled in assets/clientside_callbacks.js). Excluding
   pinch-zoom here keeps the browser from zooming the whole page instead. */
#air-quality-map {
  touch-action: pan-x pan-y;
  position: relative;
}

/* "Use two fingers to move the map", shown over the map while it is scrolled
   past with one finger (clientside_callbacks.js). */
.map-touch-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 24px;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  font-size: 18px;
  font-weight: 500;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
  /* Above Leaflet's panes and controls (up to 1000) as well. */
  z-index: 1001;
}
.map-touch-hint.visible {
  opacity: 1;
}

/* MapLibre's own notice for the same gesture; ours (above) replaces it, see
   the cooperative-gesture block in assets/clientside_callbacks.js. Without the
   maplibre-gl stylesheet it would sit there as bare text. */
.maplibregl-cooperative-gesture-screen {
  display: none;
}

/* Meteochemogram modal: species panels on the left, the chart beside them;
   stacked, full screen and scrolling on a phone. */
.meteo-layout {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}
.meteo-controls {
  min-width: 0;
}
.meteo-chart {
  position: relative;
  min-width: 0;
}
.meteo-scale-wrap {
  position: relative;
  width: 100%;
  height: 82vh;
  overflow: hidden;
  transition: opacity 0.2s ease;
}
/* No figure yet: keep the space, show nothing -- an empty Plotly figure is a
   pair of stray axes. */
.meteo-scale-wrap.meteo-empty {
  visibility: hidden;
}
/* A new figure is on its way: the old one stays as context, dimmed. */
.meteo-scale-wrap.meteo-busy {
  opacity: 0.35;
}
.meteo-progress {
  position: absolute;
  inset: 0;
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding-top: 18vh;
  pointer-events: none;
}
.meteo-progress.visible {
  display: flex;
}
.meteo-progress-card {
  width: min(360px, 90%);
  padding: 20px 24px;
  background: var(--mantine-color-body);
  border: 1px solid var(--mantine-color-gray-3);
  border-radius: 12px;
  box-shadow: 0 6px 24px rgba(2, 61, 107, 0.12);
}

@media (max-width: 767px) {
  .meteo-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
  }
  /* The chart first, the panels to change it below. */
  .meteo-chart {
    order: -1;
  }
  .meteo-modal-inner {
    padding: 0 !important;
  }
  .meteo-modal-content {
    flex: 0 0 100% !important;
    max-width: 100% !important;
    height: 100dvh;
    max-height: 100dvh !important;
    border-radius: 0 !important;
  }
  .meteo-progress {
    padding-top: 40px;
  }
  /* Before the first chart the wrapper has no height to keep. */
  .meteo-scale-wrap.meteo-empty {
    min-height: 260px;
  }
}

/* Plot toolbars (components/graph_config.py): the zoom buttons -- box zoom
   and +/- -- and pan only where there is a mouse; on a touch screen the
   two-finger gestures replace them. */
@media (pointer: coarse) {
  .modebar-btn[data-attr="zoom"],
  .modebar-btn[data-attr="dragmode"][data-val="zoom"],
  .modebar-btn[data-attr="dragmode"][data-val="pan"] {
    display: none !important;
  }
}
.validation-timeseries.is-empty {
    --validation-timeseries-height: 160px;
}
