/* Horizontal breathing room for page content. MudMainContent ships with no
   inline padding, so without this every page's content sits flush against the
   screen edge — very noticeable on phones. */
.mud-main-content {
    padding-left: 12px;
    padding-right: 12px;
}

@media (min-width: 960px) {
    .mud-main-content {
        padding-left: 16px;
        padding-right: 16px;
    }
}

/* ---------------------------------------------------------------------------
   Page header (see PageHeader.razor)
   --------------------------------------------------------------------------- */
.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin: 8px 0 20px;
}

.page-header__title {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.page-header__icon {
    color: var(--mud-palette-text-secondary);
    flex: 0 0 auto;
}

.page-header__actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
   Empty state (see EmptyState.razor)
   --------------------------------------------------------------------------- */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 6px;
    /* Enough vertical room that it reads as a deliberate state and not as a
       page that stopped rendering, without pushing itself off small screens. */
    padding: 48px 16px;
    color: var(--mud-palette-text-secondary);
}

.empty-state__icon {
    font-size: 48px;
    width: 48px;
    height: 48px;
    opacity: .38;
    margin-bottom: 4px;
}

.empty-state__hint {
    max-width: 42ch;
}

.empty-state__action {
    margin-top: 12px;
}

/* ---------------------------------------------------------------------------
   Camera tiles (home screen)

   The name and the timestamp sit ON the camera picture, so their legibility
   depends on a scene nobody controls: the same white text that reads fine at
   night sits on a sunlit lawn an hour later. A flat translucent bar is not
   enough for that - it dims the bright scene and the dark one by the same
   amount. A gradient that is near-opaque behind the text and fades to nothing
   is, and it stays out of the way of the picture itself.
   --------------------------------------------------------------------------- */
.cam-tile {
    position: relative;
    height: 30vh;
    min-height: 180px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: 6px;
}

.cam-tile__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    z-index: 0;
}

.cam-tile__bar {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 8px;
    /* Belt and braces: the gradient carries most of it, the shadow covers the
       blown-out highlight the gradient cannot darken enough. */
    text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}

.cam-tile__bar--top {
    background: linear-gradient(180deg, rgba(0, 0, 0, .78) 0%, rgba(0, 0, 0, .45) 60%, rgba(0, 0, 0, 0) 100%);
    padding-bottom: 14px;
}

.cam-tile__bar--bottom {
    margin-top: auto;
    background: linear-gradient(0deg, rgba(0, 0, 0, .78) 0%, rgba(0, 0, 0, .45) 60%, rgba(0, 0, 0, 0) 100%);
    padding-top: 14px;
}

/* A camera that is live right now. Marks the tile without the 1px border
   nudging the picture out of alignment with its neighbours. */
.cam-tile--live {
    outline: 2px solid var(--mud-palette-error);
    outline-offset: -2px;
}

.cam-tile__time {
    font-variant-numeric: tabular-nums;
    opacity: .85;
}

/* ---------------------------------------------------------------------------
   Nav drawer section labels

   Hidden once the drawer collapses to the mini rail: a section caption squeezed
   into 56px wraps into an unreadable stack and pushes the icons apart.
   --------------------------------------------------------------------------- */
.nav-section {
    display: block;
    padding: 4px 16px 0;
    color: var(--mud-palette-text-disabled);
    white-space: nowrap;
    overflow: hidden;
}

.mud-drawer--closed .nav-section,
.mud-drawer-mini:not(.mud-drawer--open) .nav-section {
    display: none;
}

/* ---------------------------------------------------------------------------
   Record cards (see SingleRecordDisplay.razor)
   --------------------------------------------------------------------------- */
.record-card__media {
    position: relative;
    /* One fixed shape for every card: the source posters are a mix of portrait
       crops and full frames, and letting each set its own height turned a page
       of results into a ragged column. cover crops, it does not letterbox. */
    aspect-ratio: 16 / 9;
    background: #000;
    cursor: pointer;
    overflow: hidden;
}

.record-card__img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    display: block;
}

.record-card__img--missing {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mud-palette-text-disabled);
    background: var(--mud-palette-background-gray);
}

/* Play affordance: the picture has always been clickable, it just never said so. */
.record-card__play {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    opacity: 0;
    transition: opacity .12s ease-in-out;
    text-shadow: 0 2px 8px rgba(0, 0, 0, .8);
    pointer-events: none;
}

.record-card__media:hover .record-card__play {
    opacity: .9;
}

.record-card__overlay {
    position: absolute;
    left: 0;
    right: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 8px;
    color: #fff;
    font-size: .8125rem;
    line-height: 1.2;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
    pointer-events: none;
}

.record-card__overlay--top {
    top: 0;
    background: linear-gradient(180deg, rgba(0, 0, 0, .72) 0%, rgba(0, 0, 0, 0) 100%);
    padding-bottom: 16px;
}

.record-card__overlay--bottom {
    bottom: 0;
    background: linear-gradient(0deg, rgba(0, 0, 0, .72) 0%, rgba(0, 0, 0, 0) 100%);
    padding-top: 16px;
}

.record-card__camera {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.record-card__duration,
.record-card__date {
    opacity: .8;
    white-space: nowrap;
}

.record-card__time,
.record-card__duration,
.record-card__date {
    font-variant-numeric: tabular-nums;
}

/* The chips below the picture carry the whole meaning of the lower half, so it
   should not claim card-height when there are none. */
.record-card__body {
    padding: 8px 12px 12px !important;
}

/* ---------------------------------------------------------------------------
   Record grid

   Column count follows the window: about 270px is the width at which a record
   card still reads at a glance, so the row holds as many as fit - five across a
   desktop, four on a laptop, two on a tablet, one on a phone. MudGrid could not
   do five: twelve columns divide into 1, 2, 3, 4, 6 and nothing between.
   --------------------------------------------------------------------------- */
.record-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
    gap: 16px;
    align-items: start;
}

/* Capped at five. Left to auto-fill, a wide monitor kept adding columns - eight
   across a 2500px screen, at which point a record is a thumbnail and the camera
   name on it is unreadable. Five is the point where the picture still carries. */
@media (min-width: 1500px) {
    .record-grid {
        grid-template-columns: repeat(5, minmax(0, 1fr));
    }
}

/* ---------------------------------------------------------------------------
   Connection overlay (see App.razor / boot.js)

   Deliberately styled without MudBlazor: it has to be able to appear when the
   circuit - and with it every Blazor-rendered component - is gone.
   --------------------------------------------------------------------------- */
.conn-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 20000;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, .72);
    backdrop-filter: blur(3px);
    color: #fff;
    font-family: Roboto, system-ui, sans-serif;
}

.conn-overlay.is-visible {
    display: flex;
}

.conn-overlay__card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    max-width: 320px;
    width: 100%;
    padding: 28px 24px;
    border-radius: 12px;
    background: #1e1e2d;
    box-shadow: 0 12px 40px rgba(0, 0, 0, .5);
}

.conn-overlay__spinner {
    width: 44px;
    height: 44px;
    margin-bottom: 6px;
    border: 4px solid rgba(255, 255, 255, .18);
    border-top-color: #7e6fff;
    border-radius: 50%;
    animation: conn-spin 900ms linear infinite;
}

@keyframes conn-spin {
    to { transform: rotate(360deg); }
}

.conn-overlay__title {
    font-size: 1.05rem;
    font-weight: 500;
}

.conn-overlay__text {
    font-size: .875rem;
    opacity: .8;
}

.conn-overlay__attempt {
    font-size: .8125rem;
    opacity: .55;
    font-variant-numeric: tabular-nums;
}

.conn-overlay__button {
    margin-top: 10px;
    padding: 10px 18px;
    /* Comfortably tappable: this button exists for the case where someone is
       staring at a frozen page on a phone. */
    min-height: 44px;
    border: 0;
    border-radius: 8px;
    background: #7e6fff;
    color: #fff;
    font: inherit;
    font-size: .9375rem;
    cursor: pointer;
}

.conn-overlay__button:active {
    filter: brightness(.9);
}

/* The failed state stops pretending it is still trying. */
.conn-overlay.is-failed .conn-overlay__spinner {
    animation: none;
    border-top-color: var(--mud-palette-error, #f5564a);
    opacity: .5;
}

/* ---------------------------------------------------------------------------
   Camera status strip + live grid (Übersicht)
   --------------------------------------------------------------------------- */
.cam-status-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.cam-status {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 12px 5px 10px;
    border-radius: 999px;
    background: var(--mud-palette-background-gray);
    font-size: .8125rem;
    line-height: 1.2;
    white-space: nowrap;
}

.cam-status__dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    flex: 0 0 auto;
    background: currentColor;
}

/* Colour is the state, so it has to survive being the only signal on the chip. */
.cam-status--recording {
    color: var(--mud-palette-error);
}

.cam-status--recording .cam-status__dot {
    animation: cam-status-pulse 1.4s ease-in-out infinite;
}

.cam-status--motion {
    color: var(--mud-palette-warning);
}

.cam-status--idle {
    color: var(--mud-palette-success);
}

.cam-status--offline {
    color: var(--mud-palette-text-disabled);
}

.cam-status__name {
    color: var(--mud-palette-text-primary);
}

.cam-status--offline .cam-status__name {
    color: var(--mud-palette-text-disabled);
}

@keyframes cam-status-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .25; }
}

/* Live tiles: as many as fit, but never so narrow that the picture stops being
   worth showing. One per row on a phone. */
.live-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 16px;
    align-items: start;
}

/* On a phone the empty state is competing for the same screen as the content
   below it - say the same thing in less height. */
@media (max-width: 600px) {
    .empty-state {
        padding: 28px 12px;
    }
}

/* Camera name over a live tile (LiveCameraControl, ShowCameraName). */
.live-tile__name {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 4;
    padding: 6px 10px 16px;
    color: #fff;
    font-size: .8125rem;
    font-weight: 500;
    line-height: 1.2;
    pointer-events: none;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
    background: linear-gradient(180deg, rgba(0, 0, 0, .72) 0%, rgba(0, 0, 0, 0) 100%);
}

/* ---------------------------------------------------------------------------
   Live belt (Übersicht)

   Cameras come and go on their own schedule, and in a wrapping grid every
   arrival and departure re-flows the whole block - tiles jump sideways, the
   sections below jump vertically, and watching it is exhausting.

   A single row fixes that by making the movement part of the layout: a slot
   animating its own width to zero pushes its neighbours along as it goes, and a
   new slot growing from zero opens a gap for itself. No positions are measured,
   nothing is transformed against the layout - the layout IS the animation.
   --------------------------------------------------------------------------- */
.live-belt {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    align-content: flex-start;
    /* Wraps rather than scrolls. A single scrolling row kept the arrivals and
       departures perfectly smooth but hid cameras off the right edge, and a page
       whose job is "everything at a glance" must not have an off-screen. The
       cost is that crossing a row boundary is still a hop - but that happens
       when a row fills, not on every arrival, which is what made the grid
       version unwatchable. */
}

.live-belt__slot {
    flex: 0 0 auto;
    width: min(420px, 82vw);
    overflow: hidden;
    animation: belt-enter 420ms cubic-bezier(.22, .61, .36, 1) both;
}

.live-belt__card {
    height: 100%;
}

.live-belt__slot.is-leaving {
    animation: belt-leave 450ms cubic-bezier(.55, .06, .68, .19) forwards;
    pointer-events: none;
}

@keyframes belt-enter {
    from {
        width: 0;
        opacity: 0;
        margin-right: -16px;
    }
    to {
        width: min(420px, 82vw);
        opacity: 1;
        margin-right: 0;
    }
}

@keyframes belt-leave {
    from {
        width: min(420px, 82vw);
        opacity: 1;
        margin-right: 0;
    }
    to {
        width: 0;
        opacity: 0;
        /* Cancels the flex gap as the slot closes, so the last few pixels do not
           leave a hole behind. */
        margin-right: -16px;
    }
}

/* Someone who prefers less motion gets the same information without the ride. */
@media (prefers-reduced-motion: reduce) {
    .live-belt__slot,
    .live-belt__slot.is-leaving {
        animation-duration: 1ms;
    }
}

/* The hold countdown: a hairline that drains while a camera is going quiet, so a
   tile about to leave the belt says so before it goes. Kept to a hairline because
   this page is watched for hours - it should be readable, not attention-seeking. */
.live-belt__hold {
    position: absolute;
    left: 0;
    top: 0;
    height: 2px;
    width: 100%;
    z-index: 6;
    background: var(--mud-palette-warning);
    transform-origin: left center;
    animation: belt-hold 15s linear forwards;
}

@keyframes belt-hold {
    from { transform: scaleX(1); opacity: .85; }
    to { transform: scaleX(0); opacity: .35; }
}

/* A camera that is actually recording earns a soft breathing outline - the one
   thing on the page worth catching a tired eye. */
.live-belt__slot.is-recording .live-belt__card {
    outline: 2px solid var(--mud-palette-error);
    outline-offset: -2px;
    animation: belt-recording 2.4s ease-in-out infinite;
}

.live-belt__slot.is-recording .live-belt__hold {
    background: var(--mud-palette-error);
}

@keyframes belt-recording {
    0%, 100% { outline-color: var(--mud-palette-error); }
    50% { outline-color: rgba(245, 86, 74, .25); }
}

@media (prefers-reduced-motion: reduce) {
    .live-belt__slot.is-recording .live-belt__card,
    .live-belt__hold {
        animation: none;
    }
}

/* ---------------------------------------------------------------------------
   Unhandled-error overlay (#blazor-error-ui)

   Blazor toggles this element's inline display between none and block, so the
   layout has to work as a block - hence the absolutely centred card rather than
   flex centring, which an inline display:block would override.

   It reuses the connection overlay's card because it is the same message to the
   same person: the page has stopped working, here is the way out. As a yellow
   strip along the bottom edge it was routinely missed.
   --------------------------------------------------------------------------- */
#blazor-error-ui {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 20001;
    background: rgba(0, 0, 0, .72);
    backdrop-filter: blur(3px);
    color: #fff;
    font-family: Roboto, system-ui, sans-serif;
}

#blazor-error-ui .conn-overlay__card {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    max-width: min(320px, calc(100vw - 48px));
}

.conn-overlay__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-bottom: 6px;
    border-radius: 50%;
    background: var(--mud-palette-error, #f5564a);
    color: #fff;
    font-size: 1.5rem;
    font-weight: 700;
}

.conn-overlay__link {
    margin-top: 4px;
    padding: 8px 12px;
    border: 0;
    background: none;
    color: rgba(255, 255, 255, .6);
    font: inherit;
    font-size: .8125rem;
    text-decoration: underline;
    cursor: pointer;
}

/* The reload control is an <a> here and a <button> in the connection overlay. */
a.conn-overlay__button {
    display: inline-block;
    text-decoration: none;
    text-align: center;
}

/* MudBlazor ships its own rules for this element - it expects the framework's
   default strip along the bottom and pins the dismiss control into its corner.
   Undo that, or it lands beside the icon of a card it knows nothing about. */
#blazor-error-ui .dismiss {
    position: static;
    color: rgba(255, 255, 255, .6);
}

#blazor-error-ui .reload {
    color: #fff;
    text-decoration: none;
}
