/* =========================================
   THE PALETTE

   Slate #2c3e50 and Outfit come from heaphomestead.com, which is where this
   palette started - the marketing site and the app looking like one product
   rather than two that happen to link to each other.

   THE ACCENT NO LONGER MATCHES IT. The site's is burnt orange #e67e22; this is
   a plum, and the ground and the rules were cooled to sit under it - a warm
   cream under a cool purple reads as two schemes arguing. Whether the site
   follows is an open question, and until it does, the two halves agree about
   everything except the one colour a visitor sees on every button.

   What this block replaces was two blues nobody had chosen - Bootstrap's
   default and a brighter accent - sitting beside Bootstrap's green, amber and
   teal. The old comment said picking one was a real design decision rather
   than a refactor. It was, and this is it.

   HOW IT REACHES BOOTSTRAP. 5.3's utilities read --bs-<name>-rgb at use time,
   so retinting --bs-primary-rgb here moves every .text-primary, .bg-primary
   and .border-primary in the app at once. Its BUTTONS do not: .btn-primary
   carries literal hexes in its own --bs-btn-* variables, which is why each
   button variant we use is restated below. A variant not restated keeps
   Bootstrap's colour, so check here before reaching for a new one.

   WHAT IS DELIBERATELY NOT RETINTED. success, danger, warning and info keep
   Bootstrap's values for now. The greens and ambers in the wild are doing two
   different jobs - "this occasion saved" (feedback, which should stay
   conventional) and "this is an occasion" (identity, which should become part
   of the palette) - and telling those apart is per-page work that belongs with
   the landing pages, not here.

   PRIMARY IS INK, NOT ACTION. Slate is the colour of headings, icons and
   rules; the accent marks the one thing on a page you would click, and is
   spent through .btn-accent rather than by making .btn-primary orange. Both
   read as "primary" in English, and having the word mean two colours depending
   on whether it followed btn- was the trap worth avoiding.
========================================= */
:root {
    /* ---------------------------------------------------------------
       THE SCHEME. Every colour in the app resolves to one of these nine
       values, and nothing below this block contains a hex. Swapping the
       app's whole appearance is editing these lines and nothing else -
       which is the point of doing it this way rather than retinting each
       page, and the cheapest way to try a different scheme and put this
       one back if it looks worse in daylight.

       The -rgb pairs have to be raw triplets because Bootstrap composes
       them with an alpha at use time - rgba(var(--bs-primary-rgb), .5) -
       so a hex there silently produces no colour at all. Keep each one in
       step with the hex above it.
       --------------------------------------------------------------- */
    --app-slate: #2c3e50;
    --app-slate-rgb: 44, 62, 80;
    --app-slate-dark: #1f2d3a;
    --app-accent: #6d4c9a;
    --app-accent-rgb: 109, 76, 154;
    --app-accent-dark: #573c7d;
    --app-ground: #f8f7fa;
    --app-surface: #ffffff;
    --app-text: #4a4a4a;
    --app-muted: #7f8c8d;
    --app-rule: #e2e0e6;
    --app-sunken: #f1eff5;

    /* Tints of the slate, for subtle fills and emphasised text. Derived by
       hand rather than by colour-mix() so they hold in older browsers. */
    --app-slate-tint: #e8ecef;
    --app-slate-tint-strong: #c3ccd4;

    /* The two page widths. Reading pages hold a column somebody reads; tool
       pages hold a table somebody scans. Every page picks one through
       PageFrame, and nothing hardcodes a max-width of its own. */
    --app-width-reading: 900px;
    --app-width-tool: 1320px;

    --app-font: 'Outfit', 'Segoe UI', system-ui, -apple-system, sans-serif;

    /* ---------------------------------------------------------------
       BOOTSTRAP, POINTED AT THE SCHEME ABOVE. Nothing new is decided
       here; each line only tells Bootstrap which of our tokens to use.
       --------------------------------------------------------------- */
    --bs-primary: var(--app-slate);
    --bs-primary-rgb: var(--app-slate-rgb);
    --bs-primary-text-emphasis: var(--app-slate-dark);
    --bs-primary-bg-subtle: var(--app-slate-tint);
    --bs-primary-border-subtle: var(--app-slate-tint-strong);

    --bs-body-bg: var(--app-ground);
    --bs-body-color: var(--app-text);
    --bs-body-font-family: var(--app-font);
    --bs-border-color: var(--app-rule);
    --bs-secondary-color: var(--app-muted);
    --bs-secondary-bg: var(--app-sunken);
    --bs-tertiary-bg: var(--app-sunken);

    --bs-link-color: var(--app-slate);
    --bs-link-color-rgb: var(--app-slate-rgb);
    --bs-link-hover-color: var(--app-accent);
    --bs-link-hover-color-rgb: var(--app-accent-rgb);

    --bs-heading-color: var(--app-slate);
}

html, body {
    font-family: var(--app-font);
    background-color: var(--app-ground);
    color: var(--app-text);
}

/* .text-muted is Bootstrap's most-used class in this app and its grey is a
   cold one. Warmed to the site's own muted, once, rather than in each page. */
.text-muted {
    color: var(--app-muted) !important;
}

.btn-primary {
    --bs-btn-color: var(--app-surface);
    --bs-btn-bg: var(--app-slate);
    --bs-btn-border-color: var(--app-slate);
    --bs-btn-hover-color: var(--app-surface);
    --bs-btn-hover-bg: var(--app-slate-dark);
    --bs-btn-hover-border-color: var(--app-slate-dark);
    --bs-btn-active-color: var(--app-surface);
    --bs-btn-active-bg: var(--app-slate-dark);
    --bs-btn-active-border-color: var(--app-slate-dark);
    --bs-btn-disabled-color: var(--app-surface);
    --bs-btn-disabled-bg: var(--app-slate);
    --bs-btn-disabled-border-color: var(--app-slate);
    --bs-btn-focus-shadow-rgb: var(--app-slate-rgb);
}

.btn-outline-primary {
    --bs-btn-color: var(--app-slate);
    --bs-btn-border-color: var(--app-slate);
    --bs-btn-hover-color: var(--app-surface);
    --bs-btn-hover-bg: var(--app-slate);
    --bs-btn-hover-border-color: var(--app-slate);
    --bs-btn-active-color: var(--app-surface);
    --bs-btn-active-bg: var(--app-slate);
    --bs-btn-active-border-color: var(--app-slate);
    --bs-btn-disabled-color: var(--app-slate);
    --bs-btn-disabled-border-color: var(--app-slate);
    --bs-btn-focus-shadow-rgb: var(--app-slate-rgb);
}

/* The one action on a page. If two buttons on the same screen are wearing
   this, one of them is the wrong colour. */
.btn-accent {
    --bs-btn-color: var(--app-surface);
    --bs-btn-bg: var(--app-accent);
    --bs-btn-border-color: var(--app-accent);
    --bs-btn-hover-color: var(--app-surface);
    --bs-btn-hover-bg: var(--app-accent-dark);
    --bs-btn-hover-border-color: var(--app-accent-dark);
    --bs-btn-active-color: var(--app-surface);
    --bs-btn-active-bg: var(--app-accent-dark);
    --bs-btn-active-border-color: var(--app-accent-dark);
    --bs-btn-disabled-color: var(--app-surface);
    --bs-btn-disabled-bg: var(--app-accent);
    --bs-btn-disabled-border-color: var(--app-accent);
    --bs-btn-focus-shadow-rgb: var(--app-accent-rgb);
}

.btn-outline-accent {
    --bs-btn-color: var(--app-accent-dark);
    --bs-btn-border-color: var(--app-accent);
    --bs-btn-hover-color: var(--app-surface);
    --bs-btn-hover-bg: var(--app-accent);
    --bs-btn-hover-border-color: var(--app-accent);
    --bs-btn-active-color: var(--app-surface);
    --bs-btn-active-bg: var(--app-accent);
    --bs-btn-active-border-color: var(--app-accent);
    --bs-btn-disabled-color: var(--app-accent-dark);
    --bs-btn-disabled-border-color: var(--app-accent);
    --bs-btn-focus-shadow-rgb: var(--app-accent-rgb);
}

/* The gap left above a newly revealed message when revealNewest scrolls to it.
   Here rather than in the script, so the breathing room is a design decision. */
#newest-segment,
#active-message-target {
    scroll-margin-top: 1rem;
}

/* The account section's own nav. Bootstrap's nav-pills fill the active item with
   --bs-primary, which is now slate - a heavy block of it down the side of a form.
   The accent marks where you are instead, the same way the top bar does. */
.account-nav .nav-link {
    color: var(--app-text);
    border-radius: 6px;
    padding: .5rem .75rem;
}

    .account-nav .nav-link:hover {
        background-color: var(--app-sunken);
        color: var(--app-slate);
    }

    .account-nav .nav-link.active {
        background-color: var(--app-sunken);
        color: var(--app-slate);
        font-weight: 600;
        border-left: 3px solid var(--app-accent);
        border-radius: 0 6px 6px 0;
    }

/* The library's kind filter. A row of these is the page saying what it can show
   you, so they are legible at rest rather than only on hover - an outline button
   that looks pressable and does nothing until clicked is worse than a label. */
.kind-pill {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--app-rule);
    background-color: var(--app-surface);
    color: var(--app-text);
    border-radius: 999px;
    padding: .3rem .85rem;
    font-size: .86rem;
    text-decoration: none;
    transition: border-color .15s ease, color .15s ease;
}

    .kind-pill:hover {
        border-color: var(--app-accent);
        color: var(--app-slate);
    }

    .kind-pill.on {
        background-color: var(--app-slate);
        border-color: var(--app-slate);
        color: var(--app-surface);
    }

/* =========================================
   THE ACCOUNT CARD

   Signing in, registering, resetting a password: one short form, centred, on
   its own surface. Narrower than a reading column because the measure that
   matters here is the input field, not a line of prose - and a form that runs
   the width of a page reads as a chore before anybody has typed anything.
========================================= */
.account-card-wrap {
    max-width: 460px;
    width: 100%;
    margin: 3rem auto;
    padding-inline: 16px;
}

.account-card {
    background-color: var(--app-surface);
    border: 1px solid var(--app-rule);
    border-radius: 10px;
    padding: 2rem 1.75rem;
}

    .account-card h1 {
        color: var(--app-slate);
    }

    /* Bootstrap's floating labels sit on a white field by default; these sit on
       one too, so the only change needed is the resting border. */
    .account-card .form-control {
        border-color: var(--app-rule);
    }

/* =========================================
   THE TWO SURFACES A LIST IS MADE OF

   The survey counted eight card treatments across the app, from
   "card h-100 border-0 shadow-sm" to "card p-4 mb-4 shadow-sm border-primary
   border-top border-4". Border, fill, radius and shadow each say "separate
   object", and spending all four on every box flattens the hierarchy until
   nothing on the page is lifted because everything is.

   So there are two, and the difference between them is the shape of the
   content, not its importance:

   .thing-tile  a photograph with a name under it, in a grid. For things you
                recognise by their picture - a memorial, a volume.
   .thing-row   a line with a small image at the left and facts beside it. For
                things you recognise by their words - an occasion's date and
                venue, an experience's opening sentence.

   Both are a white surface on the warm ground with a hairline rule, and no
   shadow at rest: one flat plane, the way a printed page is. The hover state
   is where the accent earns its keep - it marks the one row under the cursor,
   which is the only moment a list needs to lift anything.
========================================= */
.thing-tile,
.thing-row {
    background-color: var(--app-surface);
    border: 1px solid var(--app-rule);
    border-radius: 6px;
    transition: border-color .15s ease, box-shadow .15s ease;
}

    .thing-tile:hover,
    .thing-row:hover {
        border-color: var(--app-accent);
        box-shadow: 0 1px 6px rgba(var(--app-slate-rgb), 0.10);
    }

    /* Keyboard users get the same signal, since the hover state is carrying
       real information about which row is about to be opened. */
    .thing-tile:focus-visible,
    .thing-row:focus-visible {
        border-color: var(--app-accent);
        outline: 2px solid var(--app-accent);
        outline-offset: 2px;
    }

/* The letterbox a tile's photograph sits in. contain rather than cover, for
   the reason the memorials grid documents: these are whatever people uploaded,
   nothing stores their dimensions, and cover is a promise to mangle anything
   that is not roughly tile-shaped. */
.thing-tile-photo {
    width: 100%;
    aspect-ratio: 1 / 1;
    background-color: var(--app-sunken);
    border-bottom: 1px solid var(--app-rule);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: .5rem;
}

    .thing-tile-photo img {
        max-width: 100%;
        max-height: 100%;
        object-fit: contain;
    }

/* The small square at the left of a row, and the placeholder that stands in
   for it. Sized by the page, since an occasion's is 72px and an experience's
   is 84px and both were chosen against their own content. */
.thing-row-photo {
    border-radius: 4px;
    object-fit: cover;
    flex-shrink: 0;
}

.thing-row-placeholder {
    border-radius: 4px;
    background-color: var(--app-sunken);
    border: 1px solid var(--app-rule);
    color: var(--app-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.text-accent {
    color: var(--app-accent-dark) !important;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem var(--app-surface), 0 0 0 0.25rem rgba(var(--app-accent-rgb), 0.55);
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}


.app-layout {
    display: flex;
    height: 100vh;
    width: 100vw;
    overflow: hidden;
}

.main-content {
    flex-grow: 1; /* Takes up the remaining horizontal space */
    overflow-y: auto; /* Adds a scrollbar to the content if it gets too long */
    background-color: var(--app-ground);
}

.content-wrapper {
    padding: 2rem;
}


/* The semi-transparent screen that covers the old page */
.global-loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(255, 255, 255, 0.7); /* A slight white frost effect */
    z-index: 9999; /* Keeps it on top of everything else */
    /* Flexbox to center the spinner */
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-size: 1.2rem;
    color: var(--app-slate);
    font-weight: bold;
}

/* The animated spinning circle */
.spinner {
    border: 4px solid rgba(var(--app-slate-rgb), 0.18);
    border-top: 4px solid var(--app-accent);
    border-radius: 50%;
    width: 50px;
    height: 50px;
    animation: spin 0.8s linear infinite;
    margin-bottom: 15px;
}

@keyframes spin {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

/* =========================================
   SHARED FULLSCREEN IMAGE MODAL
   Used by WeekRemember, WeekTestPlay, and Memorial/Event's image zoom.
   Was previously copy-pasted with minor variations in each page's own
   .razor.css - one definition here now.
========================================= */
.image-modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background-color: rgba(0, 0, 0, 0.85);
    backdrop-filter: blur(5px);
    z-index: 9999;
    display: flex;
    justify-content: center;
    align-items: center;
    animation: fadeInModal 0.2s ease-out forwards;
}

.image-modal-content {
    max-width: 90vw;
    max-height: 90vh;
    border-radius: 12px;
    box-shadow: 0 10px 40px rgba(0,0,0,0.5);
    object-fit: contain;
    animation: zoomInImage 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}

.image-modal-close {
    position: absolute;
    top: 20px;
    right: 30px;
    background: rgba(255, 255, 255, 0.2);
    border: none;
    color: white;
    font-size: 1.5rem;
    width: 45px;
    height: 45px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    transition: background 0.2s;
}

    .image-modal-close:hover {
        background: rgba(255, 255, 255, 0.4);
    }

@keyframes fadeInModal {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

@keyframes zoomInImage {
    0% { transform: scale(0.8); opacity: 0; }
    100% { transform: scale(1); opacity: 1; }
}

/* =========================================
   SIMPLE CONFIRMATION MODAL
   Used by WeekTest.razor's "Start Early?" prompt. This was missing
   entirely before - the .simple-modal-* classes were referenced in markup
   with no matching CSS anywhere, so the dialog rendered unstyled.
========================================= */
.simple-modal-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 9999;
}

.simple-modal-content {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: #ffffff;
    border-radius: 16px;
    padding: 2rem;
    max-width: 90vw;
    width: 420px;
    text-align: center;
    z-index: 10000;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

/* =========================================
   "TAP TO RESPOND" HINT
   Used by Memorial/Event's timeline continue button, and now WeekRemember.
========================================= */
.bounce-hint {
    animation: bounceDown 2s infinite;
    opacity: 0.8;
}

@keyframes bounceDown {
    0%, 20%, 50%, 80%, 100% {
        transform: translateY(0);
    }

    40% {
        transform: translateY(-5px);
    }

    60% {
        transform: translateY(-3px);
    }
}

/* ---------------------------------------------------------------------------
   QR codes (event links, memorial and challenge share panels)

   Generated server-side by QrCode.ForUrl as inline SVG carrying a viewBox, so
   the code scales to whatever box it's given. That matters because a longer URL
   needs more modules - the svg's own dimensions grow with the link, and only
   the container decides how big it actually renders.

   Sizing lives on the wrapper rather than the svg so the white quiet zone a
   scanner needs stays inside the border.
   --------------------------------------------------------------------------- */
.qr-frame {
    /* These sit inside d-flex centering wrappers, and a flex item shrinks below its width by
       default - which collapsed the code to the width of its own padding while the height held.
       Never shrink; on a narrow phone fall back to the available width instead. */
    flex: 0 0 auto;
    max-width: 100%;
    aspect-ratio: 1 / 1;
}

.qr-frame svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* Beside an event link - small enough that several sit in a row. */
.qr-frame-sm { width: 152px; height: 152px; }

/* The share panel's single code, meant to be scanned across a table. */
.qr-frame-lg { width: 200px; height: 200px; }

/* --- Occasion photo strip -------------------------------------------------
   A horizontal scroller rather than a grid: the day's photos arrive over time,
   from the host and from guests, so the count is unknown and a grid would keep
   reflowing the page underneath the header as people post.

   Snap points make it feel deliberate on a phone, which is where most of these
   are looked at - and where the guests posting them are standing. */
.event-gallery {
    display: flex;
    gap: .5rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    padding-bottom: .35rem;
    /* Bleed to the container edge so a half-visible photo says "there is more",
       instead of the strip looking like it simply ends. */
    scrollbar-width: thin;
}

.event-gallery-item {
    flex: 0 0 auto;
    scroll-snap-align: start;
    padding: 0;
    border: 0;
    background: none;
    border-radius: .5rem;
    overflow: hidden;
    cursor: zoom-in;
    line-height: 0;
}

    .event-gallery-item img {
        height: 190px;
        width: auto;
        max-width: 78vw;
        object-fit: cover;
        border-radius: .5rem;
        box-shadow: 0 .125rem .35rem rgba(0, 0, 0, .12);
    }

    .event-gallery-item:focus-visible {
        outline: 2px solid #0d6efd;
        outline-offset: 2px;
    }

@media (max-width: 575.98px) {
    .event-gallery-item img {
        height: 150px;
    }
}

/* ============================================================================
   Upload zone (MediaDropZone)

   Replaces the bare <InputFile class="form-control">, which on a phone was a
   small grey "Choose Files / No file chosen" - a tap target barely bigger than
   the text in it, with nothing to say what it wanted or whether anything was
   happening.
   ============================================================================ */

.upload-zone {
    border: 2px dashed #ced4da;
    border-radius: .75rem;
    padding: 1.5rem 1rem;
    text-align: center;
    background-color: #fbfbfc;
    transition: border-color .15s ease, background-color .15s ease;
}

    /* The zone is focusable so paste works, but a dashed box with a default focus
       ring on it looks broken. Tint instead. */
    .upload-zone:focus,
    .upload-zone:focus-visible {
        outline: none;
        border-color: #86b7fe;
        background-color: #f6f9ff;
    }

.upload-zone-compact {
    padding: 1rem .75rem;
}

.upload-zone-over {
    border-color: #0d6efd;
    background-color: #eef5ff;
}

.upload-zone-icon {
    display: block;
    font-size: 1.75rem;
    color: #6c757d;
    margin-bottom: .35rem;
}

/* ---- the strip of what is being uploaded ---- */

.upload-tiles {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    justify-content: center;
    margin-top: 1rem;
}

.upload-tile {
    position: relative;
    width: 72px;
    height: 72px;
    border-radius: .5rem;
    overflow: hidden;
    background-color: #e9ecef;
    flex: 0 0 auto;
}

    .upload-tile img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
    }

.upload-tile-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: #6c757d;
    font-size: 1.5rem;
}

/* Sits along the bottom edge rather than covering the picture: the thumbnail is
   there so someone can see they picked the right photo, and a full-tile scrim
   would hide the one thing it is for. */
.upload-tile-overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 5px;
    background-color: rgba(0, 0, 0, .35);
}

.upload-tile-bar {
    height: 100%;
    background-color: #0d6efd;
    transition: width .15s linear;
}

.upload-tile-failed {
    opacity: .55;
}

.upload-tile-error {
    height: auto;
    top: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(220, 53, 69, .75);
    color: #fff;
    font-size: 1.25rem;
}

/* ============================================================================
   Photo strip arrows (occasion page)

   The strip itself has always scrolled. On a phone that is the right and only
   affordance - a row of photos is a thing your thumb swipes. On a desktop it
   left a trackpad gesture and the native scrollbar, so five photos looked like
   three and a bit with no way to reach the rest.
   ============================================================================ */

.event-gallery-wrap {
    position: relative;
}

.event-gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;

    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;

    border: 1px solid rgba(0, 0, 0, .08);
    border-radius: 50%;
    background-color: rgba(255, 255, 255, .92);
    color: #212529;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .18);

    cursor: pointer;
    transition: opacity .15s ease, background-color .15s ease;
}

    .event-gallery-nav:hover {
        background-color: #fff;
    }

    .event-gallery-nav.prev {
        left: 6px;
    }

    .event-gallery-nav.next {
        right: 6px;
    }

/* Hidden rather than disabled at each end: a chevron pointing at nothing is
   worse than no chevron, and the strip's own edge already says where it stops.
   visibility keeps the layout still so the photos do not shift as you scroll. */
.event-gallery-wrap.at-start .event-gallery-nav.prev,
.event-gallery-wrap.at-end .event-gallery-nav.next,
.event-gallery-wrap.no-overflow .event-gallery-nav {
    opacity: 0;
    visibility: hidden;
}

/* The actual question is "does this device have a mouse", not "is the window
   wide" - a phone held in landscape is wider than a small laptop window, and a
   touch laptop is both. Where the swipe is natural, two chevrons on top of the
   photos are just two photos you cannot see. */
@media (hover: none), (pointer: coarse) {
    .event-gallery-nav {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .event-gallery-nav {
        transition: none;
    }
}
