/* ---------- Mobile viewport safety net ----------
   Applies everywhere, not just to one page: this app is full of tables and
   full-viewport modals (choir/song-library tables, ChoirMusicPage's Music
   Event modal, MainLayout's Help modal, Admin's modals, etc.), and any one
   of them growing a single pixel wider than the device's viewport used to
   make the ENTIRE page scroll sideways (or, on iOS Safari, get shrunk down
   so all of it is visible at once, since Safari shrinks the whole page to
   fit rather than only the offending element) -- not just that one table
   or modal. box-sizing: border-box means a width: 100% element with its own
   padding/border no longer silently grows past its container by exactly
   that padding/border, which was happening on several inputs and panels
   here. overflow-x: hidden on html/body is the actual backstop: anything
   that still ends up wider than the viewport (a long unbroken word, a wide
   table that isn't in its own scrollable wrapper, etc.) gets clipped at the
   page level instead of dragging the whole page sideways -- any content
   that specifically needs its own horizontal scroll still gets it from its
   own overflow-x: auto wrapper (e.g. .song-library-scroll below), which
   this rule does not affect. */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/* position: relative here is NOT decorative -- it works around a
   long-standing iOS Safari (WebKit) bug where setting overflow-x: hidden on
   the root <html>/<body> can make position: fixed descendants fail to
   render relative to the viewport at all. MainLayout's .logout-bar (the
   fixed icon-button bar across the top of every page: Choir Music, Song
   Library, Admin, Help, Sign out) is exactly that kind of descendant, and
   went missing on iPad Safari after the overflow-x: hidden rule below was
   added for the "page shrinks to fit" mobile issue -- per spec, overflow on
   an ancestor shouldn't affect a position: fixed element's containing block
   (only transform/filter/perspective/contain do that), but this is a real
   WebKit quirk, not spec-compliant behavior, and giving html/body their own
   explicit positioning context is the commonly-used workaround. Harmless
   everywhere else: it doesn't change layout, and nothing here relies on
   html/body being the (default) static positioning context. */
html, body {
    position: relative;
    max-width: 100%;
    overflow-x: hidden;
}

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

.stub-page {
    max-width: 640px;
    /* Backstop for any wide content a stub page renders without its own
       dedicated scroll wrapper (e.g. SongLibraryPage's outer choir-table on
       a very narrow phone, or a long unbroken string) -- lets that content
       scroll horizontally within this page's own box instead of relying on
       html/body's overflow-x: hidden (see app.css's mobile viewport safety
       net above) to just clip it out of reach. Content that already has its
       own scrollable wrapper (e.g. .song-library-scroll) never grows this
       box in the first place, so this rule is a no-op for the common case. */
    overflow-x: auto;
    /* MainLayout's .app-content already reserves just enough space for the
       fixed top bar (see MainLayout.razor.css), so this only needs a small
       breathing gap on top of that — not another large margin stacked on
       top of it, which is what made the gap above every stub page (Admin,
       About, Choir Music) look too tall. */
    margin: 8px auto 0;
    padding: 0 24px;
}

.stub-page h3 {
    margin: 0 0 8px;
}

.stub-page p {
    color: rgba(0, 0, 0, 0.6);
}

/* Standard Blazor template styling for #blazor-error-ui (MainLayout.razor)
   -- this app's app.css had lost this rule at some point (the div itself
   was still there, just with nothing to keep it hidden/positioned), which
   left the "An unhandled error has occurred" banner with no default-hidden
   state at all. z-index 1001 clears .logout-bar's own 1000 (see
   MainLayout.razor.css) so the banner is never hidden underneath it. */
#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1001;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

/* Custom reconnection UI -- see MainLayout.razor's own
   #components-reconnect-modal markup for why this exists (it replaces
   blazor.web.js's own auto-injected, unstyled default modal of the same
   id) and wwwroot/js/reconnect.js for what actually watches the
   -rejected state below. z-index 10000 so a dropped/expired circuit's
   modal always sits above every other overlay in the app. */
#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 10000;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.45);
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}

/* Only one of these three shows at a time -- whichever matches the class
   the framework (or, for -rejected, reconnect.js just before it reloads)
   currently has set on the modal itself, per the selectors just below. */
#components-reconnect-modal .components-reconnect-state {
    display: none;
    background: #ffffff;
    border-radius: 12px;
    padding: 24px 28px;
    max-width: 320px;
    text-align: center;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
    font-size: 14px;
    color: #1a1a1a;
}

#components-reconnect-modal.components-reconnect-show #components-reconnect-current,
#components-reconnect-modal.components-reconnect-failed #components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected #components-reconnect-rejected {
    display: block;
}

.reconnect-retry-btn {
    margin-top: 12px;
    padding: 8px 20px;
    border: none;
    border-radius: 20px;
    background: #4f8cff;
    color: #ffffff;
    font-size: 13px;
    cursor: pointer;
}

.reconnect-retry-btn:hover,
.reconnect-retry-btn:focus-visible {
    background: #3d76e0;
    outline: none;
}

/* Suppresses BOTH connection-loss UIs -- this custom reconnect modal and
   the framework's separate #blazor-error-ui banner -- for as long as
   ChoirMusicPage's offline-copy overlay is open (see offline-modal.js's
   open()/close(), the only place that toggles this class on <body>).
   !important because #components-reconnect-modal's own -show/-failed/
   -rejected rules above otherwise set display:flex regardless of what the
   framework's connection state is doing; this has to win over all three
   unconditionally while someone's actually reading their offline copy --
   see reconnect.js's own top comment for why a rejected reconnect's
   auto-reload is deferred, not just hidden, for the same reason. */
body.choirmusic-offline-modal-open #components-reconnect-modal,
body.choirmusic-offline-modal-open #blazor-error-ui {
    display: none !important;
}
