* {
    touch-action: pan-x pan-y;
}

body {
    background-color: white;
}

body:fullscreen, body::backdrop {
    background-color: black !important;
}


:root {
    --primary: #203650;
    --siteview-primary-on-white: #265081;
    --siteview-secondary: #798696;
    --siteview-light-blue-on-white: #1B8EDB;
    --siteview-light-blue-on-black: #7FC8F8;
    --success: #165931;
    --warning: #fdb10c;
    --danger: #9e021d;
}

.bg-siteview-primary {
    background-color: var(--siteview-primary-on-white);
}
.bg-siteview-primary:hover {
    background-color:  var(--siteview-light-blue-on-white);
}

.login-sidebar {
    background: var(--primary); /* fallback for old browsers */
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='600' height='300' preserveAspectRatio='none' viewBox='0 0 1024 1024'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1018%26quot%3b)' fill='none'%3e%3cpath d='M-43.44 824.24C78.34 818.54 158.01 602.43 394.24 609.2 630.47 615.97 663.41 1117.7 831.92 1181.11' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-45.84 103.41C111.6 129.22 165.09 414.1 443.53 513.01 721.97 611.92 574.04 943.78 688.21 1029.79' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-54.04 485.57C83 470.2 130.07 143.88 397.05 168.13 664.03 192.38 642.18 921.66 848.14 1065.14' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-6.39 194.23C110.67 259.3 34.74 529.84 266.87 655.03 498.99 780.22 425.07 1036.44 540.12 1096.57' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-166.15 354.86C14.25 391.6 97.74 795.68 372.89 856.62 648.04 917.56 547.68 1115.88 642.41 1140.31' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1018'%3e%3crect width='1024' height='1024' fill='white'%3e%3c/rect%3e%3c/mask%3e%3c/defs%3e%3c/svg%3e");
    background-position: bottom;
    background-repeat: no-repeat;
    -webkit-background-size: cover;
    -moz-background-size: cover;
    background-size: cover;
    -o-background-size: cover;
}

.welcome-page-background {
    background: var(--primary); /* fallback for old browsers */
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='600' height='300' preserveAspectRatio='none' viewBox='0 0 1024 1024'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1018%26quot%3b)' fill='none'%3e%3cpath d='M-43.44 824.24C78.34 818.54 158.01 602.43 394.24 609.2 630.47 615.97 663.41 1117.7 831.92 1181.11' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-45.84 103.41C111.6 129.22 165.09 414.1 443.53 513.01 721.97 611.92 574.04 943.78 688.21 1029.79' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-54.04 485.57C83 470.2 130.07 143.88 397.05 168.13 664.03 192.38 642.18 921.66 848.14 1065.14' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-6.39 194.23C110.67 259.3 34.74 529.84 266.87 655.03 498.99 780.22 425.07 1036.44 540.12 1096.57' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3cpath d='M-166.15 354.86C14.25 391.6 97.74 795.68 372.89 856.62 648.04 917.56 547.68 1115.88 642.41 1140.31' stroke='rgba(66%2c 138%2c 187%2c 0.45)' stroke-width='2'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1018'%3e%3crect width='1024' height='1024' fill='white'%3e%3c/rect%3e%3c/mask%3e%3c/defs%3e%3c/svg%3e");
    background-position: bottom;
    background-repeat: no-repeat;
    -webkit-background-size: cover;
    -moz-background-size: cover;
    background-size: cover;
    -o-background-size: cover;
}

.welcome-card {
    max-width: 42rem;
    background-color: #ffffff;
    border-radius: 2rem;
    border: 1px solid rgb(213, 213, 213);
}

.nav-link {
    transition: all 0.2s ease;
    cursor: pointer;
    border-radius: 200px;
}

.navbar-light .navbar-nav .nav-item.active .nav-link {
    background-color: var(--primary);
    color: white;
}

.navbar-light .navbar-nav .nav-item:hover .nav-link {
    background-color: var(--siteview-light-blue-on-white);
    color: white;
}

.navbar-brand {
    transition: all 0.2s ease;
    cursor: pointer
}

.navbar-brand:hover {
    transform: scale(1.09)
}

/* Translucent + blur is future-proofing: today nothing scrolls under the
   navbar (it sits above the scroll container), so it renders as clean white.
   The .nav-scrolled shadow is toggled by the scroll listener in base.html. */
.navbar {
    position: relative;
    /* above the app content: the backdrop-filter makes the navbar a stacking
       context, and without an explicit z-index the expanded burger menu
       paints BEHIND the camera cards' stacking contexts */
    z-index: 1030;
    background: rgba(255, 255, 255, .82);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    transition: box-shadow .2s ease;
}

/* hairline only on the expanded (desktop) navbar, as before the restyle */
@media (min-width: 992px) {
    .navbar {
        border-bottom: 1px solid #e4e9ef;
    }
}

.navbar.nav-scrolled {
    box-shadow: 0 4px 16px -8px rgba(14, 39, 67, .12);
}

/* Applies at lg+ (992px) to match the navbar-expand-lg breakpoint: the
   webclient-container height reservation is only wanted once the navbar
   expands into its full horizontal row. Below lg the navbar is a compact
   burger, so the app container uses plain flex-grow and gets full height. */
@media (min-width: 992px) {
    .webclient-container {
        max-height: calc(100% - 40px);
    }
}

.is-invalid .invalid-feedback {
    display: block;
}

.hover-image {
    transition: .5s ease;
    backface-visibility: hidden;
}

.hover-image-container {
    position: relative;
}

.hover-image-center-btn {
    transition: .5s ease;
    opacity: 0;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    -ms-transform: translate(-50%, -50%);
    text-align: center;
    font-size: 2.5rem;
    color: var(--siteview-light-blue-on-white);
}

.hover-image-container:hover .hover-image {
    opacity: 0.3;
    cursor: pointer;
}

.hover-image-container:hover .hover-image-center-btn {
    opacity: 1;
    cursor: pointer;
}

.mail-activity-card {
    max-width: 40rem;
    padding: 1rem;
    padding-left: 1.25rem;
    border-radius: 2rem;
    word-break: break-word;
    background-color: var(--primary);
    transition: all 0.2s ease;
    display: flex;
    flex-direction: column;
    color: white;
}

.mail-activity-card:hover {
    box-shadow: 5px 6px 10px 0 #84b4fa;
    transform: scale(1.024)
}

.mail-template-card {
    max-width: 50rem;
    padding: 1rem;
    padding-left: 1.25rem;
    border-radius: 2rem;
    word-break: break-word;
    background-color: var(--primary);
    transition: all 0.2s ease;
    display: flex;
    flex-direction: column;
    color: white;
}

.mail-template-card:hover {
    box-shadow: 5px 6px 10px 0 #84b4fa;
    transform: scale(1.024)
}

.dashboard-card {
    max-width: 32rem;
    padding: 1rem;
    padding-left: 1.25rem;
    border-radius: 2rem;
    overflow: hidden;
    background-color: var(--primary);
    transition: all 0.2s ease;
    height: 100%;
    color: white;
}

.dashboard-card:hover {
    box-shadow: 5px 6px 10px 0px #a7c2ea;
    transform: scale(1.024)
}

/* --- Dashboard (/projects): Projekt- und Kamera-Ansicht --------------------
   Design handoff v2 "flat" (7a desktop / 7b mobile): one flat project row per
   line, white surface + hairline, no shadows/transforms - color carries only
   status information. Camera view: dense auto-refreshing grid, density via
   the --cv-track slider. Markup: projects/show_projects.html; behavior:
   js/project_cards.js + js/dashboard_view.js. */

/* small-label style (badges, counts, dates, meta): default Bootstrap font */
.pc-mono {
    letter-spacing: .04em;
    font-weight: 500;
}

/* --- toolbar ---------------------------------------------------------- */

/* Three zones: title left, view toggle CENTERED, search + round controls right.
   A grid, not a flex row - a flex child with margin-left:auto centres itself
   between its neighbours, not in the toolbar, so the toggle would drift as the
   left zone's text width changed.
   Each responsive STAGE is a set of custom properties (--dash-*) set on
   #dashboard-root, so a stage can be applied at two different breakpoints
   without duplicating the whole block. That matters because the one-row layout
   needs ~1446px in camera mode (the mirror below doubles the size control's
   footprint) but only ~999px in project mode - measured, not guessed. */
.dash-toolbar {
    display: grid;
    /* minmax(AUTO, 1fr), not minmax(0, 1fr): a 0-floor track may be narrower
       than its content, and the oversized item then OVERFLOWS it - the controls
       are justify-self:end, so they overflowed leftward into the centre column
       and silently overlapped the size control (invisible to scrollWidth, which
       is why the sweep missed it). With an auto floor the two equal 1fr side
       tracks can never be smaller than the wider side's content, so a wrong
       breakpoint now shows as visible overflow instead of a quiet overlap. */
    grid-template-columns: var(--dash-cols, minmax(auto, 1fr) auto minmax(auto, 1fr));
    grid-template-areas: var(--dash-areas, "lead centre controls");
    align-items: center;
    gap: var(--dash-gap, 14px);
    /* horizontal padding is negative-margined at the compact stage to claw back
       width from the container's own padding before the layout has to break */
    padding: 20px var(--dash-pad-x, 8px) 24px;
    margin: 0 var(--dash-pull-x, 0);
}

.dash-toolbar-lead {
    grid-area: lead;
    justify-self: start;
    /* d-flex / align-items-center / flex-wrap come from Bootstrap utilities in
       the template; only the grid placement and this gap (Bootstrap's scale has
       no 14px step) live here */
    gap: 14px;
    /* deliberately NO min-width: 0 - that made the zone's automatic minimum 0,
       so its track could collapse below the (now nowrap) headline, which then
       overflowed rightwards into the centre column. With the default auto
       minimum the minmax(auto, 1fr) track can never be narrower than the
       headline. */
}

/* Inner 1fr/auto/1fr with an EMPTY first cell (the ::before mirror): the toggle
   auto-places into column 2 and the size control into column 3, and both fr
   tracks resolve to the same width under intrinsic sizing, so the empty one
   mirrors the control and the toggle lands dead centre - no hardcoded width to
   drift out of sync with the control's real one. In project mode the control is
   display:none, so both tracks collapse to 0 and the zone is just the toggle.
   The control stays IN FLOW deliberately: absolutely positioned it reserved no
   space and painted over the headline and the search box. */
.dash-toolbar-centre {
    grid-area: centre;
    justify-self: center;
    display: grid;
    grid-template-columns: var(--dash-centre-cols, 1fr auto 1fr);
    align-items: center;
    gap: 12px;
}

.dash-toolbar-centre::before {
    content: '';
    display: var(--dash-mirror, block);
}

/* headline word per view mode: "Meine Projekte" / "Meine Kameras" */
.dash-word {
    display: none;
}

#dashboard-root:not(.dash-mode-cameras) .dash-word-projects,
#dashboard-root.dash-mode-cameras .dash-word-cameras {
    display: inline;
}

.pc-h {
    margin: 0;
    /* "Meine Kameras" must never break across two lines */
    white-space: nowrap;
    font-size: 32px;
    font-weight: 700;
    letter-spacing: -.015em;
    color: #14243a;
}

@media (max-width: 767.98px) {
    .pc-h {
        font-size: 24px;
    }
}

.pc-count {
    font-size: 12px;
    color: #7a8a9e;
}

.pc-count-archived {
    color: #92a1b3;
}

/* admin filter chips (?company= / ?user= presets) */
.pc-chip {
    align-self: center;
    font-size: 11px;
    text-transform: uppercase;
    padding: 5px 12px;
    border-radius: 999px;
    border: 1px solid #d5dee8;
    background: #ffffff;
    color: #43536a;
}

.dash-toolbar-controls {
    grid-area: controls;
    justify-self: end;
    /* d-flex / align-items-center / flex-nowrap come from Bootstrap utilities in
       the template - flex-nowrap keeps search + filter + sort as ONE unit, so
       when they stop fitting the stage moves the whole group to its own row
       rather than dropping the search under the buttons. Gap stays here:
       Bootstrap's scale has no 12px step. */
    gap: 12px;
}

/* MEDIUM: the search/filter/sort group drops to its own full-width row,
   right-aligned; row 1 keeps the toggle. The mirror is switched off here so the
   toggle and size control centre as a PAIR, with the control still to the
   toggle's right.
   Same stage, two BOOTSTRAP breakpoints (this project runs Bootstrap 5.1.3 via
   Flask-BS4): camera mode carries the size control and runs out of one-row width
   first, so it leaves the wide layout below `xl`; project mode not until `lg`.
   Those standard breakpoints were CHECKED against a fine 5px scan of the real
   geometry rather than assumed - one clean row needs >=1090px in camera mode and
   >=860px in project mode, so xl (1200) and lg (992) both clear it with margin.
   Re-run that scan if the toolbar's contents change; if a floor ever exceeds its
   breakpoint the row overflows horizontally (loud, by design - see the
   minmax(auto, 1fr) note above). */
:root {
    --dash-stage-medium-areas: "lead centre ." "controls controls controls";
}

/* below xl */
@media (max-width: 1199.98px) {
    #dashboard-root.dash-mode-cameras {
        --dash-gap: 8px;
        --dash-pad-x: 0px;
        --dash-pull-x: -8px;
        --dash-areas: var(--dash-stage-medium-areas);
        --dash-centre-cols: auto auto;
        --dash-mirror: none;
    }
}

/* below lg */
@media (max-width: 991.98px) {
    #dashboard-root {
        --dash-gap: 8px;
        --dash-pad-x: 0px;
        --dash-pull-x: -8px;
        --dash-areas: var(--dash-stage-medium-areas);
        --dash-centre-cols: auto auto;
        --dash-mirror: none;
    }
}

/* segmented view toggle as a pill slide-track, mirroring the webclient's
   Live/Archiv/Video switch (.sv-mode-toggle in frontend App.scss); the
   active segment is CSS-driven via the dash-mode-cameras class */
.dash-view-toggle {
    display: inline-flex;
    gap: 4px;
    padding: 4px;
    background: #eef2f7;
    border-radius: 999px;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .05);
}

.dash-view-btn {
    border: none;
    background: transparent;
    color: #55637a;
    /* the label never breaks away from its icon */
    white-space: nowrap;
    font-size: 14px;
    font-weight: 500;
    padding: 7px 16px;
    border-radius: 999px;
    cursor: pointer;
    transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}

.dash-view-btn:hover {
    background: rgba(255, 255, 255, .7);
    color: var(--primary);
}

.dash-view-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(32, 54, 80, .25);
}

#dashboard-root:not(.dash-mode-cameras) .dash-view-btn[data-view="projects"],
#dashboard-root.dash-mode-cameras .dash-view-btn[data-view="cameras"] {
    background: var(--primary);
    color: #ffffff;
    font-weight: 600;
    box-shadow: 0 2px 8px rgba(32, 54, 80, .35);
}

/* per-view visibility, all keyed on the root class */
[data-view-count="cameras"] {
    display: none;
}

.dash-mode-cameras [data-view-count="cameras"] {
    display: inline;
}

.dash-mode-cameras [data-view-count="projects"] {
    display: none;
}

#camera-view {
    display: none;
}

.dash-mode-cameras #camera-view {
    display: block;
}

.dash-mode-cameras #project-view {
    display: none;
}

.cv-size-control {
    display: none;
}

.dash-mode-cameras .cv-size-control {
    display: flex;
}

/* camera card size: - | slider | + on the same pill track as the toggle
   (magnetic snaps in dashboard_view.js) */
.cv-size-control {
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    background: #eef2f7;
    border-radius: 999px;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .05);
}

.cv-size-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: #55637a;
    font-size: 13px;
    cursor: pointer;
    transition: background .15s ease, color .15s ease;
}

.cv-size-btn:hover {
    background: rgba(255, 255, 255, .7);
    color: var(--primary);
}

.cv-size-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(32, 54, 80, .25);
}

#camera-size-slider {
    -webkit-appearance: none;
    appearance: none;
    width: 140px;
    height: 4px;
    border-radius: 999px;
    background: #d7dfe9;
    outline: none;
}

#camera-size-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    border-radius: 999px;
    background: var(--primary);
    border: 2px solid #ffffff;
    box-shadow: 0 1px 4px rgba(32, 54, 80, .4);
    cursor: pointer;
}

#camera-size-slider::-moz-range-thumb {
    width: 16px;
    height: 16px;
    border-radius: 999px;
    background: var(--primary);
    border: 2px solid #ffffff;
    box-shadow: 0 1px 4px rgba(32, 54, 80, .4);
    cursor: pointer;
}

#camera-size-slider::-moz-range-track {
    height: 4px;
    border-radius: 999px;
    background: #d7dfe9;
}

#camera-size-slider:focus-visible {
    box-shadow: 0 0 0 3px rgba(32, 54, 80, .25);
}

/* mobile: search full-width on top, then the view toggle; no size control -
   phones always render the one full-width column (enforced in
   dashboard_view.js applyLayout) */
/* NARROW: three stacked rows, title -> view toggle -> controls. The named areas
   carry that order (the zones need no `order` of their own). The controls keep
   their search -> filter -> sort order at EVERY width - no `order` reshuffling
   here; the search's width:100% base just lets it grow into whatever space the
   two round buttons leave rather than staying at its fixed 260px. No size
   control - phones get one full-width camera column anyway (enforced in
   dashboard_view.js applyLayout). */
@media (max-width: 767.98px) {
    /* the .dash-mode-cameras selector is repeated deliberately: the camera-mode
       MEDIUM rule above is #dashboard-root.dash-mode-cameras (0,1,1,0) and would
       otherwise out-specify a plain #dashboard-root here, leaving camera mode on
       the two-row layout below 768px. Matching its specificity lets source order
       decide, and this block comes later. */
    #dashboard-root,
    #dashboard-root.dash-mode-cameras {
        --dash-cols: minmax(0, 1fr);
        --dash-areas: "lead" "centre" "controls";
        --dash-centre-cols: auto auto;
        --dash-mirror: none;
    }

    .dash-toolbar-lead,
    .dash-toolbar-controls {
        justify-self: stretch;
    }

    /* left-aligned, not centred: stacked under the title it should line up with
       it rather than float in the middle of the row */
    .dash-toolbar-centre {
        justify-self: start;
    }

    .dash-toolbar-controls {
        width: 100%;
        /* iOS Safari gives .pc-search a flex contribution ~93px LARGER than its
           used width (measured on-device: a 260px search contributes 353, a
           281px one 374). Every painted box fits, but the row's CONTENT then
           measures 449 in a 377px row and inflates the page's scrollWidth, so
           the dashboard scrolls sideways in camera mode. Chrome does not do
           this, so it cannot be reproduced on desktop.
           Clipping the row contains it whatever the cause, and unlike a width
           cap it holds at every phone width (a fixed 180px search fits a 393px
           phone but overflows again at 320px).
           The padding+negative margin is load-bearing: the round filter/sort
           buttons draw their focus ring and active-filter pulse OUTSIDE their
           box, and a bare overflow:hidden would slice both off. The padding
           gives that 6px halo room inside the clip; the margin cancels the
           visual offset. */
        overflow: hidden;
        padding: 6px;
        margin: -6px;
    }

    /* The control row is flex-nowrap, so its min-content decides whether the
       toolbar fits, and Chrome leaves only ~6px of headroom. min-width: 0 drops
       the search's automatic minimum so it can shrink instead of forcing the
       row wider. It deliberately does NOT get width: 100% / flex-grow - making
       the search wider fed straight into the iPhone overflow (it grew 1:1 with
       the search) and made it worse.
       The #dashboard-root prefix is load-bearing - the base `.pc-search` rule
       sits LATER in this file at identical specificity. */
    #dashboard-root .pc-search {
        min-width: 0;
    }

    /* the icon buttons are already at their minimum - shrinking them would
       squash the tap target instead of the search field */
    #dashboard-root .dash-toolbar-controls .dash-modeonly {
        flex: none;
    }

    /* iOS Safari zooms the page when a focused input is smaller than 16px, and
       that zoom is persisted per site - the page then scrolls horizontally on
       every later visit, without the user doing anything. Phones only; the 14px
       size is kept from md up.
       The #dashboard-root prefix is load-bearing here: the base
       `.pc-search input` rule sits LATER in this file at identical specificity,
       so a plain selector would lose the cascade. */
    #dashboard-root .pc-search input {
        font-size: 16px;
    }

    .dash-mode-cameras .cv-size-control {
        display: none;
    }
}

/* --- round icon-selects (filter + sort) --------------------------------- */
/* A real <select> laid transparently over an icon: clicking the button IS
   clicking the select, so the native dropdown opens right at the button with no
   custom popover, and it stays a real keyboard/screen-reader control. */
.dash-iconselect {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 1px solid #d5dee8;
    background: #ffffff;
    color: #55637a;
    font-size: 15px;
    cursor: pointer;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.dash-iconselect select {
    position: absolute;
    /* 16px, not the wrapper's inherited 15px: iOS Safari zooms the page when a
       focused form control is under 16px - selects included - and keeps that
       zoom per site, which leaves the dashboard horizontally scrollable. The
       select is opacity: 0, so the size is invisible either way.
       Bootstrap Reboot sets font-size: inherit on select, hence the 15px. */
    font-size: 16px;
    /* -1px, not 0: insets resolve against the wrapper's PADDING box, so inset:0
       would leave the 1px border uncovered and a click on the button's outer
       ring would miss the select entirely */
    inset: -1px;
    opacity: 0;
    border: 0;
    padding: 0;
    margin: 0;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.dash-iconselect:hover {
    border-color: #b6c4d6;
    color: var(--primary);
}

/* the select carries the focus, so the ring has to be drawn on the wrapper */
.dash-iconselect:focus-within {
    box-shadow: 0 0 0 3px rgba(32, 54, 80, .25);
}

/* off-default: dark fill + a slow halo, so a restored selection can never hide
   cards without a visible cue */
.dash-iconselect-active {
    background: var(--primary, #203650);
    border-color: var(--primary, #203650);
    color: #ffffff;
    animation: dash-filter-pulse 2.4s ease-out infinite;
}

/* :hover is (0,2,0) and sets color:var(--primary); the active rule is only
   (0,1,0), so without this hover would paint the icon dark blue ON the dark
   blue fill. */
.dash-iconselect-active:hover {
    background: #2b4563;
    border-color: #2b4563;
    color: #ffffff;
}

/* both the halo and the focus ring draw box-shadow - focus must win */
.dash-iconselect-active:focus-within {
    animation: none;
    box-shadow: 0 0 0 3px rgba(32, 54, 80, .35);
}

@keyframes dash-filter-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(32, 54, 80, .45);
    }
    70% {
        box-shadow: 0 0 0 7px rgba(32, 54, 80, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(32, 54, 80, 0);
    }
}

/* only the controls of the view you are looking at are shown */
.dash-modeonly {
    display: none;
}

#dashboard-root:not(.dash-mode-cameras) .dash-modeonly-projects,
#dashboard-root.dash-mode-cameras .dash-modeonly-cameras {
    display: inline-flex;
}

/* timestamp of the shown image, next to the camera name. Without this it
   inherits .cv-camera's 14px/600 and reads as a second camera name, and with no
   flex:none it steals width from the ellipsised name link on small cards. */
.cv-stamp {
    margin-left: auto;
    padding-left: 8px;
    font-size: 10px;
    font-weight: 400;
    color: #8595ab;
    white-space: nowrap;
    flex: none;
}

/* --- search ------------------------------------------------------------ */

.pc-search {
    position: relative;
    width: 260px;
    max-width: 100%;
}

.pc-search .bi-search {
    position: absolute;
    left: 14px;
    top: 50%;
    transform: translateY(-50%);
    color: #7a8a9e;
    pointer-events: none;
}

/* the custom clear button replaces the native WebKit one (Firefox has none) */
.pc-search input::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.pc-search-clear {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: #7a8a9e;
    font-size: 13px;
    cursor: pointer;
    transition: background .15s ease, color .15s ease;
}

.pc-search-clear:hover,
.pc-search-clear:focus-visible {
    background: #e8eef5;
    color: #14243a;
}

.pc-search input {
    width: 100%;
    padding: 8px 38px 8px 36px;
    border-radius: 999px;
    border: 1px solid #dde6ef;
    background: #f6f9fc;
    font-size: 14px;
    color: #14243a;
    outline: none;
    transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}

.pc-search input:focus {
    border-color: #1c5fa8;
    background: #ffffff;
    box-shadow: 0 0 0 3px rgba(28, 95, 168, .15);
}

/* centered empty / no-results state (search misses, no cameras at all) */
.pc-no-results {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    text-align: center;
    padding: 48px 16px;
    margin: 0;
    color: #55637a;
}

.pc-no-results-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 999px;
    background: #eef2f7;
    color: #8b9bb0;
    font-size: 22px;
}

.pc-no-results-hint {
    font-size: 13px;
    color: #8b9bb0;
}

/* welcoming placeholder card (no projects / no cameras yet) */
.dash-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    text-align: center;
    padding: 56px 24px;
    border: 1px dashed #c9d6e5;
    border-radius: 14px;
    background: #f9fafc;
}

.dash-placeholder-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 999px;
    background: #eef4fb;
    color: #1c5fa8;
    font-size: 26px;
}

.dash-placeholder-title {
    font-size: 17px;
    font-weight: 600;
    color: #14243a;
}

.dash-placeholder-hint {
    font-size: 13.5px;
    color: #7a8a9e;
    max-width: 420px;
}

/* keeps the camera-view placeholder from spanning the full-width page */
.cv-placeholder-wrap {
    max-width: 720px;
    margin: 0 auto;
}

/* deferred camera-view loading + error states */
.cv-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 64px 16px;
    color: #55637a;
}

.cv-loading .spinner-border {
    color: #1c5fa8;
}

.cv-retry-btn {
    margin-top: 4px;
    padding: 8px 18px;
    border: 1px solid #1c5fa8;
    border-radius: 999px;
    background: #ffffff;
    color: #1c5fa8;
    font-size: 14px;
    cursor: pointer;
    transition: background .15s ease;
}

.cv-retry-btn:hover,
.cv-retry-btn:focus-visible {
    background: #eef4fb;
    outline: none;
}

/* --- project view (flat rows, 7a/7b) ----------------------------------- */

.pv-container {
    max-width: 1000px;
    margin: 0 auto;
    padding-bottom: 72px;
}

.pv-section {
    margin-bottom: 44px;
}

.pv-section:last-child {
    margin-bottom: 0;
}

/* marketing panel sits at the very end with extra breathing room */
.pv-section-about {
    margin-top: 88px;
}

.pv-section-head {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 14px;
}

.pv-section-title {
    margin: 0;
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: #14243a;
}

/* the archived block is the only labelled section left, so it carries the whole
   burden of separating itself from the active list above: more air and a larger
   title than a plain section head would need */
.pv-section-archived {
    margin-top: 72px;
}

.pv-section-title-archived {
    font-size: 26px;
    color: #55637a;
}

.project-list {
    display: flex;
    flex-direction: column;
    gap: 22px;
}

/* entrance: scroll-reveal - rows fade up when they enter the viewport
   (classes driven by project_cards.js, which also removes them again after
   the reveal so the inline stagger delay never affects hover transitions;
   without JS or with reduced motion the rows are simply visible) */
.pc-reveal {
    opacity: 0;
    transform: translateY(10px);
}

.pc-reveal.pc-revealed {
    opacity: 1;
    transform: none;
    transition: opacity .4s ease-out, transform .4s ease-out;
}

/* flat row card: state reads through border + surface tint only */
.project-card {
    --pr-border: #e3e9f0;
    --pr-border-hover: #b6c9de;
    --pr-border-active: #1c5fa8;
    --pr-bg-hover: #fbfdff;
    --pr-bg-pressed: #f0f6fc;
    --pr-focus-ring: rgba(28, 95, 168, .22);
    --pr-bar-track: #e7edf4;
    --pr-bar-fill: #1c5fa8;
    --pr-date-color: #7a8a9e;
    position: relative;
    display: flex;
    height: 180px;
    border-radius: 14px;
    overflow: hidden;
    /* very subtle uniform tint instead of pure white */
    background: #f9fafc;
    border: 1px solid var(--pr-border);
    transition: border-color .15s ease, background .15s ease, transform .15s ease;
    -webkit-tap-highlight-color: transparent;
}

.project-card.pr-status-warn {
    --pr-border: #f0dcb4;
    --pr-border-hover: #dcb35c;
    --pr-border-active: #b98418;
    --pr-bg-hover: #fffdf7;
    --pr-bg-pressed: #fdf6e6;
    --pr-focus-ring: rgba(185, 132, 24, .22);
    --pr-bar-track: #f5ead2;
    --pr-bar-fill: #d79b21;
    --pr-date-color: #8f6410;
}

.project-card.pr-status-over {
    --pr-border: #f2cfcf;
    --pr-border-hover: #dc9a9a;
    --pr-border-active: #b23b3b;
    --pr-bg-hover: #fffcfb;
    --pr-bg-pressed: #fdf1f1;
    --pr-focus-ring: rgba(178, 59, 59, .22);
    --pr-bar-track: #f5e2e2;
    --pr-bar-fill: #c04a4a;
    --pr-date-color: #9a3232;
}

.project-card.pr-archived {
    --pr-bar-fill: #93a3b4;
}

.project-card.pc-clickable {
    cursor: pointer;
}

/* iOS long-press on the card link (incl. its card-covering overlay), the
   gear icon or the cover image must not open the link-preview / save-image
   callout - a press is either a tap (navigate) or nothing. */
.pc-link,
.cv-link,
.pr-gear,
.pc-new-card,
.pc-cover-img,
.cv-img {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-user-drag: none;
}

/* hover only for real pointers - iOS keeps :hover stuck after a tap.
   The small lift is a deliberate deviation from the strictly-flat handoff
   (user request). */
@media (hover: hover) and (pointer: fine) {
    .project-card.pc-clickable:hover {
        border-color: var(--pr-border-hover);
        background: var(--pr-bg-hover);
        /*transform: translateX(-5px);*/
        transform: scale(102%);
    }
}

/* pressed / loading: border + surface tint, deliberately no transform */
.project-card.pc-clickable:active,
.project-card.pc-clickable.is-loading {
    border-color: var(--pr-border-active);
    background: var(--pr-bg-pressed);
}

/* keyboard focus ring, drawn on the stretched link's card-covering overlay */
.pc-link:focus-visible {
    outline: none;
}

.pc-link:focus-visible::after {
    box-shadow: 0 0 0 3px var(--pr-focus-ring);
    border-radius: 14px;
}

/* cover: 320x180 (16:9) flush left on desktop; white slot so transparent
   logo covers sit on a clean white background (doubles as the loading
   backdrop - photo covers paint over it anyway) */
.pr-cover {
    position: relative;
    flex: none;
    width: 320px;
    background: #ffffff;
}

.pc-cover-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
}

/* fade-in as a one-shot animation: the resting state is a plain opacity: 1,
   so a throttled/skipped animation can never leave a cover invisible */
@keyframes pc-fade-in {
    from {
        opacity: 0;
    }
}

.pc-cover-img.loaded {
    opacity: 1;
    animation: pc-fade-in .3s ease;
}

.pr-archived .pc-cover-img {
    filter: grayscale(.9);
}

/* status badge */
.pr-badge {
    display: inline-flex;
    align-items: center;
    line-height: 1;
    flex: none;
    font-size: 10px;
    letter-spacing: .1em;
    white-space: nowrap;
    padding: 6px 9px;
    border-radius: 4px;
}

.pr-badge-active { background: #eef4fb; color: #1c5fa8; }
.pr-badge-new { background: #e7f5ec; color: #1f6b45; }
.pr-badge-warn { background: #fdf0d8; color: #8f6410; }
.pr-badge-over { background: #fbe9e9; color: #9a3232; }
.pr-badge-archived { background: #eef1f5; color: #55637a; }

/* mobile: badge sits inside the cover; the AKTIV variant gets a white
   surface so it stays readable on any cover */
.pr-badge-on-cover {
    position: absolute;
    right: 12px;
    top: 12px;
    z-index: 2;
}

.pr-badge-on-cover.pr-badge-active {
    background: #ffffff;
}

/* content column */
.pr-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 20px 20px 20px 24px;
    min-width: 0;
}

.pr-head {
    display: flex;
    align-items: flex-start;
    gap: 14px;
}

.pr-headtext {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.pr-company {
    font-size: 13px;
    color: #1c5fa8;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pr-archived .pr-company {
    color: #55637a;
}

.pr-title {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -.015em;
    line-height: 1.2;
    color: #14243a;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

.pr-title .pc-link {
    color: inherit;
    text-decoration: none;
}

.pr-archived .pr-title {
    color: #43536a;
}

/* selectable text that still clicks through: raised above the stretched
   link so drag-selection works; the selection-aware click handler in
   project_cards.js performs the navigation for plain clicks */
.pc-address {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: flex-start;
    gap: 7px;
    width: fit-content;
    max-width: 100%;
    min-width: 0;
    font-size: 13.5px;
    line-height: 1.45;
    color: #55637a;
    -webkit-user-select: text;
    user-select: text;
}

.pc-address span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pc-clickable .pc-address {
    cursor: pointer;
}

.pc-address .bi {
    font-size: 14px;
    color: #8b9bb0;
    flex: none;
}

/* admin-only: pin icon doubles as address-copy control */
.pr-copy-addr {
    cursor: pointer;
    transition: color .15s ease;
}

.pr-copy-addr:hover,
.pr-copy-addr:focus-visible {
    color: #1c5fa8;
    outline: none;
}

.pr-copy-addr.copied {
    color: #1f6b45;
}

/* footer: meta left, 200px runtime right */
.pr-foot {
    margin-top: auto;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
}

.pr-meta {
    display: flex;
    align-items: center;
    gap: 16px;
    font-size: 11.5px;
    color: #7a8a9e;
}

.pr-meta-item {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.pr-meta-item .bi {
    font-size: 13px;
    line-height: 1;
}

/* settings entry (superuser/admin); raised above the stretched link */
.pr-gear {
    position: relative;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    color: #7a8a9e;
    font-size: 14px;
    text-decoration: none;
    transition: color .15s ease;
}

.pr-gear:hover,
.pr-gear:focus-visible {
    color: #1c5fa8;
}

.pr-runtime {
    flex: none;
    width: 200px;
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.pr-dates {
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    color: var(--pr-date-color);
}

.pr-bar {
    height: 3px;
    border-radius: 999px;
    background: var(--pr-bar-track);
    overflow: hidden;
}

.pr-bar-fill {
    height: 100%;
    border-radius: 999px;
    background: var(--pr-bar-fill);
}

/* loading feedback after a card click: spinner chip over the cover */
.pc-loading-spinner {
    position: absolute;
    inset: 0;
    z-index: 4;
    display: none;
    align-items: center;
    justify-content: center;
}

.project-card.is-loading .pc-loading-spinner {
    display: flex;
}

.pc-loading-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border-radius: 999px;
    background: rgba(20, 36, 58, .72);
    border: 1px solid rgba(255, 255, 255, .35);
}

.pc-loading-spinner .spinner-border {
    width: 1.4rem;
    height: 1.4rem;
    color: #ffffff;
}

/* mobile (<768px, 7b): stacked card */
@media (max-width: 767.98px) {
    .project-card {
        flex-direction: column;
        height: auto;
    }

    .pr-cover {
        width: 100%;
        aspect-ratio: 16 / 9;
    }

    .pr-content {
        padding: 16px;
    }

    .pr-company {
        font-size: 12.5px;
    }

    .pr-title {
        font-size: 19px;
        letter-spacing: -.01em;
        line-height: 1.25;
        display: block;
        -webkit-line-clamp: unset;
        overflow: visible;
    }

    .pc-address {
        font-size: 13px;
        line-height: 1.4;
    }

    .pc-address .bi {
        margin-top: 2px;
    }

    .pc-address span {
        white-space: normal;
        overflow: visible;
    }

    .pr-foot {
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
    }

    .pr-runtime {
        width: 100%;
        margin-top: 6px;
    }
}

/* "Neues Projekt hinzufügen": flat dashed row */
.pc-new-card {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    height: 92px;
    border: 1px dashed #c9d6e5;
    border-radius: 14px;
    background: #ffffff;
    color: #1c5fa8;
    font-size: 15px;
    text-decoration: none;
    transition: border-color .15s ease, background .15s ease;
    -webkit-tap-highlight-color: transparent;
}

.pc-new-plus {
    font-size: 20px;
    font-weight: 300;
    line-height: 1;
}

@media (hover: hover) and (pointer: fine) {
    .pc-new-card:hover {
        border-color: #1c5fa8;
        background: #f7fbff;
        color: #1c5fa8;
    }
}

.pc-new-card:active {
    border-style: solid;
    border-color: #1c5fa8;
    background: #eff6fd;
    color: #1c5fa8;
}

.pc-new-card:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(28, 95, 168, .22);
}

@media (max-width: 767.98px) {
    .pc-new-card {
        height: 76px;
    }
}

/* "Über SiteView" marketing panel (kept from the previous round, aligned
   to the 14px radius of the new cards) */
.pc-eyebrow {
    font-size: 13px;
    letter-spacing: normal;
    color: #1c5fa8;
}

.pc-about {
    display: flex;
    align-items: center;
    gap: 44px;
    padding: 40px 44px;
    border-radius: 14px;
    background: #ffffff;
    border: 1px solid #e3e9f0;
}

.pc-about-text {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
}

.pc-about-title {
    margin: 0;
    font-size: 28px;
    font-weight: 700;
    letter-spacing: -.015em;
    color: #14243a;
}

.pc-about-lead {
    margin: 0;
    font-size: 17px;
    line-height: 1.45;
    color: #3c5069;
}

.pc-about-copy {
    margin: 0;
    font-size: 14px;
    line-height: 1.6;
    color: #5b6b7e;
}

.pc-about-cta {
    display: inline-flex;
    align-items: center;
    margin-top: 10px;
    padding: 10px 22px;
    border-radius: 999px;
    border: 1px solid #1c5fa8;
    background: #1c5fa8;
    color: #ffffff;
    font-size: 15px;
    font-weight: 500;
    text-decoration: none;
    transition: background .18s ease, color .18s ease, border-color .18s ease;
}

.pc-about-cta:hover {
    background: #174e8b;
    border-color: #174e8b;
    color: #ffffff;
}

.pc-about-cta:focus-visible {
    outline: none;
    color: #ffffff;
    box-shadow: 0 0 0 3px rgba(28, 95, 168, .35);
}

.pc-about-media {
    flex: 0 0 42%;
    max-width: 42%;
}

@media (max-width: 991.98px) {
    .pc-about {
        padding: 32px 28px;
    }
}

/* --- camera view -------------------------------------------------------- */

#camera-view {
    padding: 0 8px 72px;
}

/* fluid columns: dashboard_view.js derives the column count from the
   slider's target width and the available grid width, then sets
   grid-template-columns: repeat(N, minmax(0, 1fr)) inline - cards always
   share the full row width. This rule is only the no-JS fallback. */
.cv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, min(400px, 100%));
    justify-content: center;
    gap: 16px;
}

.cv-card {
    position: relative;
    display: flex;
    flex-direction: column;
    border-radius: 12px;
    border: 1px solid #e3e9f0;
    background: #ffffff;
    overflow: hidden;
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease, transform .15s ease;
    -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) and (pointer: fine) {
    .cv-card:hover {
        border-color: #b6c9de;
        background: #fbfdff;
        /*transform: translateX(4px);*/
        transform: scale(101%);
    }
}

/* very large cards (> 1000px, class set by dashboard_view.js) keep the
   hover colors but skip the zoom - a scaling near-fullscreen image reads
   as jarring */
.cv-grid.cv-static-hover .cv-card:hover {
    transform: none;
}

.cv-card:active {
    border-color: #1c5fa8;
    background: #f0f6fc;
}

.cv-link:focus-visible {
    outline: none;
}

.cv-link:focus-visible::after {
    box-shadow: 0 0 0 3px rgba(28, 95, 168, .22);
    border-radius: 12px;
}

/* 16:9 stage with letterboxing: portrait or odd-ratio images are contained,
   never cropped or stretched */
.cv-media {
    position: relative;
    aspect-ratio: 16 / 9;
    background: #101828;
}

.cv-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.cv-media-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #55637a;
    font-size: 28px;
}

/* data-driven status badge (top-left): dot + label on a dark translucent
   chip - readable on any image content. Freshness states (see
   app/cameras/state.py): Live green (pulse), Auffälligkeit amber (calm
   pulse), Ausfall red (pulse), Kamera betriebsbereit blue, Anonymisierung verzögert muted amber, Zeitplan beendet /
   Inaktiv grey. */
.cv-status {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 2;
    /* grid, NOT flex: both label spans share one cell (column 2), so the
       chip width is the wider of the two and can only grow monotonically on
       hover - side-by-side spans animated the width past/below its target,
       which flickered when the edge moved under the cursor */
    display: inline-grid;
    grid-template-columns: auto auto;
    align-items: center;
    padding: 4px 9px 4px 7px;
    border-radius: 999px;
    background: rgba(20, 36, 58, .72);
    color: #ffffff;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: .02em;
    line-height: 1;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .35);
    cursor: default;
}

.cv-status-dot {
    grid-column: 1;
    grid-row: 1;
    width: 8px;
    height: 8px;
    border-radius: 999px;
    margin-right: 6px;
}

/* short word by default; hover (desktop) or tap (.cv-expanded, touch)
   crossfades to the full label from state.py's FRESHNESS_LABELS - incl. the
   blue idle state's next-capture time. Both spans overlap in the same grid
   cell; only the full label's max-width animates. */
.cv-status-text,
.cv-status-full {
    grid-column: 2;
    grid-row: 1;
    overflow: hidden;
    white-space: nowrap;
    transition: opacity .15s ease;
}

.cv-status-full {
    max-width: 0;
    opacity: 0;
    transition: max-width .2s ease, opacity .15s ease;
}

.cv-status:hover .cv-status-full,
.cv-status.cv-expanded .cv-status-full {
    max-width: 320px;
    opacity: 1;
}

.cv-status:hover .cv-status-text,
.cv-status.cv-expanded .cv-status-text {
    opacity: 0;
}

.cv-status[data-state="live"] .cv-status-dot {
    background: #35c07c;
    animation: cv-pulse-live 2.2s ease-out infinite;
}

/* gentle green "is live" pulse, calmer than the red Ausfall pulse */
@keyframes cv-pulse-live {
    0% {
        box-shadow: 0 0 0 0 rgba(53, 192, 124, .55);
    }
    70% {
        box-shadow: 0 0 0 5px rgba(53, 192, 124, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(53, 192, 124, 0);
    }
}

.cv-status[data-state="inactive"] .cv-status-dot,
.cv-status[data-state="ended"] .cv-status-dot {
    background: #aab7c4;
}

/* healthy but nothing expected right now (weekend/night pause) */
.cv-status[data-state="idle"] .cv-status-dot {
    background: #4dabf7;
}

/* camera delivers, but the shown (anonymized) variant lags behind */
.cv-status[data-state="lagging"] .cv-status-dot {
    background: #d9b370;
}

.cv-status[data-state="warning"] .cv-status-dot {
    background: #f0ad4e;
    animation: cv-pulse-warning 2.2s ease-out infinite;
}

@keyframes cv-pulse-warning {
    0% {
        box-shadow: 0 0 0 0 rgba(240, 173, 78, .55);
    }
    70% {
        box-shadow: 0 0 0 5px rgba(240, 173, 78, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(240, 173, 78, 0);
    }
}

.cv-status[data-state="stale"] .cv-status-dot {
    background: #ff5a4e;
    animation: cv-pulse 1.6s ease-out infinite;
}

@keyframes cv-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(255, 90, 78, .6);
    }
    70% {
        box-shadow: 0 0 0 6px rgba(255, 90, 78, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(255, 90, 78, 0);
    }
}

.cv-caption {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 12px;
    min-width: 0;
}

.cv-project {
    font-size: 12px;
    color: #1c5fa8;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cv-company {
    color: #8b9bb0;
}

.cv-camera {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
    color: #14243a;
}

.cv-camera .cv-link {
    color: inherit;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cv-inactive-tag {
    flex: none;
    font-size: 9px;
    letter-spacing: .08em;
    color: #9a3232;
    background: #fbe9e9;
    border-radius: 4px;
    padding: 3px 6px;
}

/* --- reduced motion ------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
    .project-card,
    .pc-new-card,
    .cv-card,
    .cv-size-btn,
    .dash-view-btn,
    .pc-about-cta,
    .pr-gear {
        transition: none;
    }

    .pc-cover-img.loaded,
    .cv-status[data-state="stale"] .cv-status-dot,
    .cv-status[data-state="warning"] .cv-status-dot,
    .cv-status[data-state="live"] .cv-status-dot,
    .dash-iconselect-active {
        animation: none;
    }

    .cv-status-text,
    .cv-status-full {
        transition: none;
    }

    /* scroll-reveal is skipped in JS under reduced motion; safety net */
    .pc-reveal {
        opacity: 1;
        transform: none;
    }

    .project-card.pc-clickable:hover,
    .cv-card:hover {
        transform: none;
    }
}

/* --- Shared component styles (webclient sidebar buttons, siteview
   buttons, toasts, grid.js, stats pills, tables, print) ------------- */

.btn-siteview-primary {
    background-color: var(--primary);
    color: var(--siteview-light-blue-on-black);
    padding: 7px 20px;
    text-decoration: none;
    border-radius: 50px;
    align-items: center;
    justify-content: left;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

.btn-siteview-primary:hover, .btn-siteview-primary:focus, .btn-siteview-primary:active, .btn-siteview-primary.active {
    color: white;
    background-color: var(--siteview-light-blue-on-white);
}

.btn-siteview-secondary {
    background-color: #4a4f53;
    color: white;
    padding: 7px 20px;
    text-decoration: none;
    border-radius: 50px;
    align-items: center;
    justify-content: left;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

.btn-siteview-secondary:hover, .btn-siteview-secondary:focus, .btn-siteview-secondary:active, .btn-siteview-secondary.active {
    color: black;
    background-color: #d5d8da;
}

.btn-siteview-success {
    background-color: var(--success);
    color: #f6f2d5;
    padding: 7px 20px;
    text-decoration: none;
    border-radius: 50px;
    align-items: center;
    justify-content: left;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

.btn-siteview-success:hover, .btn-siteview-success:focus, .btn-siteview-success:active, .btn-siteview-success.active {
    color: black;
    background-color: #96d6b0;
}

.btn-siteview-danger {
    background-color: var(--danger);
    color: #f6f2d5;
    padding: 7px 20px;
    text-decoration: none;
    border-radius: 50px;
    align-items: center;
    justify-content: left;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

.btn-siteview-danger:hover, .btn-siteview-danger:focus, .btn-siteview-danger:active, .btn-siteview-danger.active {
    color: black;
    background-color: rgb(225, 87, 112);
}

.button-camera-select {
    background-color: #7FC8F8;
    color: white;
    text-decoration: none;
    border-radius: 58px;
    height: 58px;
    display: flex;
    margin-bottom: 10px;
    margin-top: 10px;
    margin-left: 6px;
    margin-right: 20px;
    padding-left: 10px;
    padding-right: 10px;
    align-items: center;
    justify-content: left;
    overflow: hidden;
    width: auto;
    min-width: 58px;
    max-width: 58px; /** I'm animating max-width because width needs to be auto, and auto can't be animated **/
    -webkit-transition: max-width 0.3s;
    transition: max-width 0.3s;
}

.button-camera-select.active {
    background-color: var(--primary);
    border-radius: 73px;
    height: 73px;
    min-width: 73px;
    max-width: 73px;
    margin-left: 0;
}

.button-camera-select.active .icon {
    padding: 0 7px;
}


.button-camera-select.disabled {
    background-color: #909ba8;
    cursor: default;
}

.button-camera-select .icon {
    padding: 0 3px;
    display: flex;
    align-items: center;
}

.button-camera-select .text {
    display: flex;
    white-space: nowrap;
    padding: 15px;
}


.button-camera-select:hover {
    color: white;
    max-width: 500px;
    overflow: visible;
}


.toastify {
    --background-rgb: 255, 255, 255;
    --background-alpha: 0.96;
    background: rgba(var(--background-rgb), var(--background-alpha));
    max-width: 450px;
    margin: 10px 10px 10px 20px;
    padding: 8px 8px 8px 8px;
    font-size: 0.95rem;
    color: #000000;
    cursor: auto;
    pointer-events: auto;
    background-clip: padding-box;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-radius: 0.5rem;
    border: 3px solid transparent;
}

.toastify:hover {
    --background-alpha: 1;
    .toast-close {
        opacity: 1;
    }
}

.toast-close {
    background: white;
    border: 1px solid #ccc;
    border-radius: 50%;
    cursor: pointer;
    font-size: 0.95rem;
    font-weight: lighter;
    color: #5a5a5a;
    position: absolute;
    width: 1.5rem;
    height: 1.5rem;
    margin: 0;
    padding: 0;
    top: -0.9rem;
    right: -0.9rem;
    transition: opacity 0.2s ease;
    opacity: 0;
    @media (max-width: 767px) {
        opacity: 1 !important;
    }
}

.toastify-avatar {
    width: 1.5em;
    height: 1.5em;
    padding-right: 0.4rem;
    padding-left: 0;
}

.toastify-success {
    background: linear-gradient(rgba(var(--background-rgb), var(--background-alpha)), rgba(var(--background-rgb), var(--background-alpha))) padding-box,
    linear-gradient(45deg, #00f260, #0575e6) border-box;

}

.toastify-info {
    background: linear-gradient(rgba(var(--background-rgb), var(--background-alpha)), rgba(var(--background-rgb), var(--background-alpha))) padding-box,
    linear-gradient(45deg, #00dbde, #2618f3) border-box;
}

.toastify-danger {
    background: linear-gradient(rgba(var(--background-rgb), var(--background-alpha)), rgba(var(--background-rgb), var(--background-alpha))) padding-box,
    linear-gradient(45deg, #c31432, #fdb10c) border-box;

}

.gridjs .gridjs-table {
    width: auto;
    min-width: 100%;
}

.gridjs .gridjs-th {
    padding: 14px 10px;
}

.gridjs .gridjs-td {
    padding: 3px 12px;
}

.stats-pill {
    position: relative;
    border-radius: 50rem;
    padding: 0.45rem 1rem;
    font-size: 0.85rem;
    font-weight: 500;
    border: 1px solid #dee2e6;
    background: #fff;
    color: #495057;
    cursor: pointer;
    transition: all 0.15s ease-in-out;
}
.stats-pill:hover {
    background: #f0f4f8;
    border-color: #adb5bd;
}
.stats-pill.active {
    background: var(--siteview-primary-on-white);
    border-color: var(--siteview-primary-on-white);
    color: #fff;
}
.stats-pill.active:hover {
    background: var(--siteview-light-blue-on-white);
    border-color: var(--siteview-light-blue-on-white);
}
.stats-pill .stats-pill-count {
    position: absolute;
    top: -12px;
    right: -6px;
    font-size: 0.75rem;
    font-weight: 600;
    min-width: 1.35rem;
    height: 1.5rem;
    line-height: 1.35rem;
    padding: 0 0.35rem;
    border-radius: 50rem;
    text-align: center;
    background: #6c757d;
    color: #fff;
    border: 2px solid #fff;
}
.stats-pill.active .stats-pill-count {
    background: var(--siteview-light-blue-on-white);
}
.stats-pill-icon {
    font-size: 1rem;
    margin-right: 0.2rem;
}

.stats-lock-btn {
    width: 2rem;
    height: 2rem;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 1px solid #dee2e6;
    background: #fff;
    color: #6c757d;
    font-size: 0.85rem;
    cursor: pointer;
    transition: all 0.15s ease-in-out;
}
.stats-lock-btn:hover {
    background: #f0f4f8;
}
.stats-lock-btn.locked,
.stats-lock-btn.paused {
    background: var(--siteview-primary-on-white);
    border-color: var(--siteview-primary-on-white);
    color: #fff;
}

.row-click-table {
    table {
        border-collapse: collapse;
        width: 100%;
    }

    th {
        border: 1px solid #ddd;
        border-bottom: 2px solid #ddd;
        background-color: #f2f2f2;
        padding: 8px;
    }

    tbody td {
        border: 1px solid #ddd;
        padding: 8px;
    }

    tbody tr.clickable-row {
        transition: background 0.15s ease;
        cursor: pointer;
    }

    tbody tr.clickable-row:hover {
        background: var(--siteview-light-blue-on-black);
    }

    ::selection {
    background: var(--siteview-secondary);
    color: white; /* Set the color of the selected text */
    }

    tbody tr[visible='false'],
    .no-result {
        display: none;
    }

    tbody tr[visible='true'],
    .no-result {
        display: table-row;
    }

    .counter {
        color: #ccc;
    }
}

/* --- Wochentag-Auswahl / btn-check Gruppen -------------------------------------------------
   Bootstrap 5.1 rendert .btn-outline-primary:hover und .btn-check:checked + .btn-outline-primary
   mit identischen Deklarationen (#fff auf #0d6efd) - ein überfahrener Tag sieht damit exakt aus
   wie ein ausgewählter. Auf Touch-Geräten bleibt :hover nach dem Antippen zusätzlich hängen.
   Deshalb: Hover bekommt nur eine leichte Tönung (nie eine Vollfläche) und gilt ausschließlich
   für echte Zeigegeräte. Ausgewählt bleibt die Vollfläche und wird zusätzlich fetter gesetzt.
   Die Farben stehen hier als Literale, weil Bootstrap 5.1 Komponentenfarben als Hex kompiliert -
   bei einem Retheme des Backends müssen sie hier mitgeändert werden. */
.btn-check + .btn-outline-primary {
    --sv-day-accent: #0d6efd;
    --sv-day-hover-bg: rgba(13, 110, 253, 0.12);
    --sv-day-halo: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}

/* ausgewählt: Vollfläche (Bootstrap) + fetter -> eindeutig "an" */
.btn-check:checked + .btn-outline-primary {
    font-weight: 600;
}

/* Hover nur mit echtem Zeiger: kein hängenbleibender Hover-Zustand nach einem Tap */
@media (hover: hover) and (pointer: fine) {
    .btn-check:not(:checked) + .btn-outline-primary:hover {
        background-color: var(--sv-day-hover-bg);
        border-color: var(--sv-day-accent);
        color: var(--sv-day-accent);
    }

    /* Halo um den einzelnen Button beim Überfahren - macht zusätzlich sichtbar, welcher Tag
       gerade unter dem Zeiger liegt (auch bei bereits ausgewählten Tagen).
       z-index, weil sich die Buttons in einer .btn-group überlappen und der Schatten sonst
       vom Nachbarn beschnitten wird (Bootstrap vergibt dort selbst z-index: 1). */
    .btn-check + .btn-outline-primary:hover {
        box-shadow: var(--sv-day-halo);
        position: relative;
        z-index: 2;
    }
}

/* ein Tap hinterlässt Fokus auf dem versteckten Input - Ring nur für Tastaturbedienung */
/* ...aber nicht, solange der Zeiger darauf steht - sonst wuerde der Klick den Hover-Halo
   loeschen (die Regel ist spezifischer als die Halo-Regel oben). */
.btn-check:focus:not(:focus-visible) + .btn-outline-primary:not(:hover) {
    box-shadow: none;
}

/* Touch-Geräte: Bootstrap färbt .btn-outline-primary:active (und .btn-check:active + ...)
   exakt wie :checked - also Vollfläche. iOS/Safari behält :active nach dem Antippen bei, ein
   gerade abgewählter Tag sieht deshalb weiterhin ausgewählt aus. Auf groben Zeigern wird der
   Nicht-ausgewählt-Zustand daher erzwungen; die Auswahl selbst kommt allein aus :checked. */
@media (hover: none), (pointer: coarse) {
    .btn-check + .btn-outline-primary {
        -webkit-tap-highlight-color: transparent;
    }

    .btn-check:not(:checked):active + .btn-outline-primary,
    .btn-check:not(:checked):focus + .btn-outline-primary,
    .btn-check:not(:checked) + .btn-outline-primary:active,
    .btn-check:not(:checked) + .btn-outline-primary:focus,
    /* :hover ist der eigentliche Übeltäter - Bootstraps .btn-outline-primary:hover steht in
       KEINER Media Query, und iOS lässt :hover nach dem Antippen stehen, bis woanders getippt
       wird. Ein gerade abgewählter Tag bleibt dadurch vollflächig blau. */
    .btn-check:not(:checked) + .btn-outline-primary:hover {
        background-color: transparent;
        color: var(--sv-day-accent);
        border-color: var(--sv-day-accent);
        box-shadow: none;
    }
}

/* Selected row in the entity picker grids (admin create forms). The gridjs tds
   carry their own background, so a plain Bootstrap table-* class has no effect. */
.picker-selected-row td.gridjs-td {
    background-color: #cfe2ff;
}

/* flask-bs4's wtf macros render submit buttons without a button_map entry
   (e.g. all the "Abbrechen" cancel buttons) as plain text — dress them up as
   outline-secondary buttons. Keyed on our own stable field name, not on the
   macro's accidental "btn-None" class artifact. */
form .btn[name="cancel"] {
    color: #6c757d;
    border-color: #6c757d;
}

form .btn[name="cancel"]:hover,
form .btn[name="cancel"]:active {
    color: #fff;
    background-color: #6c757d;
    border-color: #6c757d;
}
