/*
 * Filament fixes
 * ----------------------------------------------------------------------------
 *
 * Loaded into both panels from their PanelsRenderHook::HEAD_END hook, AFTER
 * Filament's own stylesheets, so these rules win on equal specificity without
 * needing !important.
 *
 * NOT a republished vendor asset and not a custom theme. The install runs
 * hand-modified vendor files that were never published through Composer, so
 * anything that regenerates public/css/filament/** destroys work that cannot be
 * recovered from a package. This file sits beside them and overrides.
 */

/* ---------------------------------------------------------------------------
 * Select dropdowns near the foot of the page
 *
 * Filament builds its non-native selects on Choices.js and configures it with
 * position: "auto", which means Choices decides for itself whether to open
 * downward or upward and adds the class `is-flipped` to the outer container
 * when it chooses upward.
 *
 * Filament's Tailwind build then ships NO rules for `is-flipped`. Choices
 * flips, the class lands, and nothing acts on it: the panel keeps top: 100%,
 * opens downward anyway, hangs past the bottom of the document and grows the
 * scrollable page. That is the bug where reaching the foot of a form and
 * opening a long dropdown pushes the page longer instead of scrolling inside
 * the list.
 *
 * These are the positioning rules Choices' own stylesheet carries and
 * Filament's does not.
 * ------------------------------------------------------------------------- */

.choices.is-flipped .choices__list--dropdown,
.choices.is-flipped .choices__list[aria-expanded] {
    top: auto;
    bottom: 100%;
    margin-top: 0;
    margin-bottom: 0.5rem;
}

/*
 * The height ceiling, bound at BOTH levels.
 *
 * Filament already caps the nested scroller at 15rem. On this install it is not
 * binding: the list opens far taller than 240px, which is why flipping it
 * upward on its own made things worse rather than better — the panel stopped
 * extending the page and started covering the fields above it instead.
 *
 * Rather than guess which element carries the scroll, both are capped:
 *
 *   inner   the nested list, where Filament intends the scroll to happen and
 *           where it keeps the search box pinned above it. !important because
 *           something on this install is beating the shipped rule and the
 *           point of this file is to be the last word.
 *
 *   outer   the dropdown panel itself, set two rem LARGER so it never engages
 *           while the inner rule is working. It is the backstop for a markup
 *           shape the inner selector does not match, and it costs a search box
 *           that scrolls with the list, which is worth it against a dropdown
 *           with no ceiling at all.
 *
 * Explicitly not display:flex on the panel. Choices toggles the dropdown by
 * switching display between none and block, and a flex layout here would hold
 * a closed dropdown open.
 */
.choices__list--dropdown > .choices__list,
.choices__list[aria-expanded] > .choices__list {
    max-height: min(18rem, 45vh) !important;
    overflow-y: auto !important;
    overscroll-behavior: contain;
}

.choices__list--dropdown,
.choices__list[aria-expanded] {
    max-height: min(20rem, 50vh);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* ---------------------------------------------------------------------------
 * An empty select must not be shorter than a full one
 *
 * Choices renders the chosen value into a single-item list inside the box. With
 * nothing chosen AND no placeholder to fall back on, that list holds no text
 * node at all, so it contributes no line box and the whole control collapses to
 * roughly two thirds the height of its neighbours. On a row of dropdowns the
 * odd one out reads as broken rather than as empty.
 *
 * The figure is not a guess. Filament's forms.css gives .choices__inner
 * line-height 1.5rem with 0.375rem of padding above and below, the ring is a
 * box-shadow rather than a border so it adds no layout, and box-sizing is
 * border-box throughout. A filled control is therefore exactly
 * 1.5 + 0.375 + 0.375 = 2.25rem tall, and asking for that as a minimum makes an
 * empty one match it to the pixel while leaving every filled one alone.
 *
 * Set on the box rather than on the inner list because that list is an
 * inline-block: giving an empty inline-block a height leaves the line box that
 * contains it taller than the text it stands in for, which would overshoot in
 * the other direction.
 * ------------------------------------------------------------------------- */

.choices[data-type*='select-one'] .choices__inner {
    min-height: 2.25rem;
}

/*
 * Everything above is the floor, not the finished behaviour.
 *
 * filament-fixes.js measures the room between the field and the edge of the
 * window each time a dropdown opens and overwrites both ceilings with the real
 * figure, so the list runs to the bottom of the page instead of stopping at an
 * arbitrary height. CSS cannot ask that question, which is the only reason the
 * script exists.
 *
 * These rules are what a member sees if the script fails to load: a bounded,
 * scrolling dropdown rather than one that grows the page. Deliberately kept
 * conservative for that reason.
 */

/* ---------------------------------------------------------------------------
 * AN OPEN DROPDOWN OUTRANKS EVERYTHING (owner, 2026-08-27)
 *
 * Filament gives the panel z-index 10, which wins against ordinary siblings and
 * loses to anything that raises itself: repeater row actions, sticky headers,
 * a section that establishes its own stacking context. The symptom is a delete
 * button or a reorder handle painted straight through the middle of the open
 * list.
 *
 * z-index is only ever compared INSIDE one stacking context, so a number on the
 * dropdown alone cannot answer this: if an ancestor of the field sits below the
 * ancestor of the button, no figure here changes the outcome. The number below
 * settles every contest within the field's own context, and filament-fixes.js
 * carries the same figure up the ancestor chain for the ones it cannot reach.
 *
 * The value is 2147483000 rather than the signed 32-bit maximum so there is
 * still room above it for anything that genuinely has to win — a modal overlay,
 * a toast — without those having to match an unbeatable number.
 * ------------------------------------------------------------------------- */

.choices.is-open {
    position: relative;
    z-index: 2147483000;
}

.choices.is-open .choices__list--dropdown,
.choices.is-open .choices__list[aria-expanded] {
    z-index: 2147483000;
}

/*
 * The field wrapper the select sits in, for the commonest case the script would
 * otherwise have to reach: Filament lays form fields out on a grid, and a grid
 * item takes a z-index while still position: static — so this lifts the whole
 * field without giving it a position it did not have, which would re-anchor
 * anything absolutely positioned inside it.
 */
.fi-fo-component-ctn:has(.choices.is-open),
[class*='fi-fo-field-wrp']:has(.choices.is-open) {
    z-index: 2147483000;
}

/*
 * ── IMAGE UPLOADS AS TILES, NOT FULL-WIDTH ROWS ─────────────────────────────
 *
 * FilePond lays every file out as a row spanning the whole panel: its own rule
 * is `.filepond--item { left: 0; right: 0 }`. With an image preview inside,
 * that gives a picture stranded in a wide band of empty grey, and five saved
 * avatar photos turn a form section into a wall of them.
 *
 * There is no option for this. FilePond's stylePanelLayout accepts only
 * integrated, compact and circle — passing "grid" sets an attribute that
 * nothing styles. A grid is made by giving the ITEM a width; FilePond reads
 * that width and does its own layout maths from it, which is why the absolute
 * positioning above is left alone.
 *
 * Five across on a wide panel, stepping down as it narrows. No height or
 * width is put on the picture itself: the tile bounds it, and each one keeps
 * its own shape inside that.
 *
 * Scoped to .ai-upload-tiles rather than applied to every FilePond, because a
 * list of documents SHOULD be full-width rows — it is only pictures that want
 * tiling. The class is put on by ->extraAttributes() at each image upload.
 */
.ai-upload-tiles .filepond--item {
    width: calc(20% - 0.5em);
}

/* Fewer across as the panel narrows, so a tile never becomes a postage stamp. */
@media (max-width: 1279px) {
    .ai-upload-tiles .filepond--item {
        width: calc(25% - 0.5em);
    }
}

@media (max-width: 1023px) {
    .ai-upload-tiles .filepond--item {
        width: calc(33.333% - 0.5em);
    }
}

@media (max-width: 767px) {
    .ai-upload-tiles .filepond--item {
        width: calc(50% - 0.5em);
    }
}

/*
 * The name and size sit ON the tile now rather than beside it, so they are
 * given a readable ground and kept to one line instead of pushing the tile
 * taller than the picture it describes.
 */
.ai-upload-tiles .filepond--file-info {
    min-width: 0;
}

.ai-upload-tiles .filepond--file-info .filepond--file-info-main,
.ai-upload-tiles .filepond--file-info .filepond--file-info-sub {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * ── AND THE TILE STAYS INSIDE ITS PANEL ─────────────────────────────────────
 *
 * Two things the grid needs beyond the item width. FilePond draws the preview
 * into a fixed-height box and letterboxes anything that does not match, which
 * on a dark panel reads as a grey slab around the picture; and the drop label
 * above needs clearing, or the first row of tiles sits under it.
 *
 * The picture is told to COVER its tile instead of fitting inside it: at five
 * across a tile is wide enough that a portrait fills it, and cropping a little
 * off the top and bottom of a preview costs nothing — the file itself is
 * untouched, this is only what the member looks at while choosing.
 */
.ai-upload-tiles .filepond--image-preview-wrapper,
.ai-upload-tiles .filepond--image-preview-overlay,
.ai-upload-tiles .filepond--image-preview {
    background: transparent;
}

.ai-upload-tiles .filepond--image-preview img,
.ai-upload-tiles .filepond--image-preview canvas {
    height: 100%;
    object-fit: cover;
    width: 100%;
}

/*
 * The item panel behind a letterboxed preview, which is the grey the tiles
 * were sitting on. Dark enough to disappear into the form on either theme.
 */
.ai-upload-tiles .filepond--item-panel {
    background-color: rgb(63 63 70 / 0.6);
}

/*
 * Dark mode: the sidebar's collapse and expand arrows and each group's
 * chevron take the primary color (owner, 2026-09-28), as they read as
 * controls rather than dim gray marks. Important, because the panel's own
 * dark rule for every icon button is.
 */
.dark .fi-sidebar .fi-sidebar-group-collapse-button .fi-icon-btn-icon,
.dark .fi-sidebar-header .fi-icon-btn .fi-icon-btn-icon {
    color: rgb(var(--primary-500)) !important;
}
.dark .fi-sidebar .fi-sidebar-group-collapse-button:hover .fi-icon-btn-icon,
.dark .fi-sidebar-header .fi-icon-btn:hover .fi-icon-btn-icon {
    color: rgb(var(--primary-400)) !important;
}

/* A COLORED MENU ITEM'S ICON IS THE COLOR OF ITS TEXT (owner, 2026-09-30:
   "Icons on primary color buttons must be same color as text", everywhere in
   the dashboard and admin panels). Filament draws the icon one shade lighter
   than the label in light mode (500 against 600), so a primary item such as
   Archive or Resend Email showed two shades side by side. Named per color,
   because Filament sets the 600 variable on the label only, so the icon has
   no --c-600 of its own to read. Dark mode already matches (both 400) and is
   left alone. */
html:not(.dark) .fi-dropdown-list-item.fi-color-primary .fi-dropdown-list-item-icon.text-custom-500 { color: rgb(var(--primary-600)) !important; }
html:not(.dark) .fi-dropdown-list-item.fi-color-danger .fi-dropdown-list-item-icon.text-custom-500 { color: rgb(var(--danger-600)) !important; }
html:not(.dark) .fi-dropdown-list-item.fi-color-success .fi-dropdown-list-item-icon.text-custom-500 { color: rgb(var(--success-600)) !important; }
html:not(.dark) .fi-dropdown-list-item.fi-color-warning .fi-dropdown-list-item-icon.text-custom-500 { color: rgb(var(--warning-600)) !important; }
html:not(.dark) .fi-dropdown-list-item.fi-color-info .fi-dropdown-list-item-icon.text-custom-500 { color: rgb(var(--info-600)) !important; }

/* CENTERED TEXT WRAPS BALANCED (owner, 2026-10-01: "text wrap balance
   centered text in the dashboard and admin panels"), so a centered line
   never leaves one word alone underneath. text-wrap is inherited, so setting
   it on the centered element reaches every heading and paragraph inside.
   Covers Tailwind's class, inline centering and Filament's own centered
   empty states and modals. :where() keeps it at zero specificity, so any
   block that sets its own wrapping still wins. */
:where(.text-center, [style*="text-align:center"], [style*="text-align: center"], .fi-ta-empty-state, .fi-modal-header, .fi-simple-header, .fi-no-notification) { text-wrap: balance; }