*.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: one compact row, lined up with the page container below
   (Mantine Container size="xl" = 1320px with 16px gutters). On phones the
   titles wrap under menu button and logo instead of pushing the map a whole
   screen down. */
.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 24px;
  row-gap: 4px;
  max-width: 1320px;
  margin: 0 auto;
  padding: 10px 16px;
}
.site-logo {
  display: block;
  width: 144px;
  height: 42px;
}
.site-titles {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.site-institute {
  font-size: var(--mantine-font-size-sm);
}
.site-title {
  font-size: 26px;
  line-height: 1.2;
}

@media (max-width: 575px) {
  .site-header {
    column-gap: 12px;
    padding: 8px 16px;
  }
  .site-logo {
    width: 110px;
    height: 32px;
  }
  .site-titles {
    flex-basis: 100%;
  }
  .site-institute {
    font-size: var(--mantine-font-size-xs);
  }
  .site-title {
    font-size: 21px;
  }
}

.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 {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}
.heatmap-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 {
    order: -1;
  }
}

/* Expert mode: controls marked .expert-only are hidden for standard users.
   The expert-mode switch lives in the navigation drawer. When it is off,
   the top-level app frame div (#app-frame) carries the class "standard-view",
   which activates this rule app-wide. */
.standard-view .expert-only {
  display: none;
}

@media (min-width: 992px) {
  .heatmap-grid {
    grid-template-columns: 300px minmax(0, 1fr);
  }
  .heatmap-sidebar {
    position: sticky;
    top: 12px;
  }
}

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

.heatmap-card { overflow: hidden; }
.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 - 290px);
  --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 {}
.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;
}

/* 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%; }

.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;
}

#footer{
    text-align: center;
}

#footer>*{
    margin: 0 20px;
}

.fzblue{
    color: #023d6b;
}

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

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

.layout-section{
    padding: 10px;
    /*background-color: #f0f0f0;*/
    /*border: 1px solid #ccc;*/
    /*display: flex;*/
    /*justify-content: center;*/
    /*align-items: center;*/
}

.options-flex {
    display: flex;
    flex-wrap: wrap; /* Elemente umbrechen, wenn nötig */
    gap: 10px;
    width: 100%;
    height: 100%;
    justify-content: space-around;
}

.dropdown{
    zIndex: 1;
}

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

.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{
    margin: 10px 10px 0 10px;
    padding: 10px 15px;
    border-radius: 15px;
    border-left: 4px solid #023d6b;
    background-color: #eef3f8;
    color: #023d6b;
}

/* Validation page: filters, station map and statistics side by side in a
   band of fixed height (35vh), the time series below. The three columns take
   height 100% + min-height 0 so they shrink to the band instead of forcing it
   taller; the filter column scrolls inside. */
.validation-top {
    flex: 0 0 auto;
    height: 35vh;
    display: flex;
}
.validation-inputs {
    display: flex;
    flex-flow: column;
    /* Not space-evenly: the row grows with the map/plots next to it, which
       would tear the controls apart vertically. */
    justify-content: flex-start;
    gap: 18px;
    flex: 1 0 15%;
    height: 100%;
    min-height: 0;
    overflow-y: auto;
}
.validation-map {
    flex: 1 0 25%;
    height: 100%;
    min-height: 0;
}
.validation-stats {
    flex: 1 0 45%;
    height: 100%;
    min-height: 0;
}

/* Narrower screens: the three blocks stack, each at a height that suits it,
   and the page scrolls -- squeezed into one 35vh band the map was 100px wide
   and the statistics unreadable on a phone. */
@media (max-width: 991px) {
    .validation-top {
        height: auto;
    }
    .validation-top .options-flex {
        flex-direction: column;
        flex-wrap: nowrap;
    }
    .validation-inputs,
    .validation-map,
    .validation-stats {
        flex: 0 0 auto;
        width: 100%;
    }
    .validation-inputs {
        height: auto;
        overflow-y: visible;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
        gap: 12px 18px;
        box-sizing: border-box;
    }
    /* The date range is wider than one grid column. */
    .validation-inputs > :first-child {
        grid-column: 1 / -1;
    }
    /* The dropdowns carry fixed pixel widths for the desktop column. */
    .validation-inputs .dash-dropdown {
        width: 100% !important;
    }
    .validation-map {
        height: min(70vw, 420px);
        box-sizing: border-box;
    }
    .validation-stats {
        height: 340px;
    }
}

#statistic-parent{
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-template-rows: repeat(2, 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, 1fr);
        grid-template-rows: 1.2fr 1fr 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;
  }
}
