/* Rules every site group shares, the one stylesheet of our own that every layout links (<x-layout.head>): after
Bootstrap and before the group's site.css, so a group overrides a single rule by writing it again (BY's page
background, price-old look and header icon colour). Four parts that used to be four files, in the order they were
loaded in: the round icon buttons of a card, the flags of the language switcher, the brand colour, the general rules. */


/* ===== Round icon buttons: watchlist heart, request icon, video icon ===== */

/*
Base styling for the watchlist heart (resources/views/components/watchlist-
heart.blade.php) - shared across every subproject/itemType, loaded globally
alongside bootstrap-icons.min.css. A subproject that needs a different look
overrides .watchlist-heart in its own site.css (loaded after this file) or
passes different $icon/$iconActive props - this file only defines sensible
defaults, not a hard visual contract.

Semi-opaque white circle behind the icon so it stays visible over both dark
and light boat photos (explicit requirement - a plain icon with no backdrop
would disappear against a light-colored hull/sky).
*/

/* The round icon buttons on a card or gallery share one shape: the watchlist heart, <x-request.card-icon> (in the
site's brand colour, --brand-primary) and <x-video-icon>. Each group's site.css overrides specifics. */
.watchlist-heart,
.boat-request-icon,
.video-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, .85);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
    color: var(--brand-primary);
    font-size: 1.1rem;
    line-height: 1;
    transition: transform .15s ease;
}

.watchlist-heart:hover,
.boat-request-icon:hover,
.video-icon:hover {
    transform: scale(1.08);
}

/* The heart keeps a neutral grey (red once active) whatever the site colour is. */
.watchlist-heart {
    color: #495057;
    transition: transform .15s ease, color .15s ease;
}

.watchlist-heart.is-active {
    color: #dc3545;
}

/* Outline and filled icon are both in the markup (see the component); the
button's own state picks one. The span that wraps a custom SVG stays a flex
item like a bare <i> is, so the SVG gets no line box (no extra height). */
.watchlist-heart span {
    display: flex;
}

.watchlist-heart:not(.is-active) .wl-on,
.watchlist-heart.is-active .wl-off {
    display: none;
}

.watchlist-heart:disabled {
    opacity: .6;
}

/* Overlays on the image of a card tile or a detail page's gallery. The wrapping element needs position:relative:
the icon is a sibling of the card's <a>, not nested in it (a button inside a link is invalid HTML). Every corner has one
owner: heart top right, request icon bottom right (cards only), video icon bottom left on a card and bottom right
on a gallery (where no request icon sits). */
.watchlist-heart-card,
.boat-request-icon-card,
.video-icon-gallery,
.video-icon-card {
    position: absolute;
    z-index: 2;
}

.watchlist-heart-card {
    top: .5rem;
    right: .5rem;
}

.boat-request-icon-card,
.video-icon-gallery {
    bottom: .5rem;
    right: .5rem;
}

.video-icon-card {
    bottom: .5rem;
    left: .5rem;
}


/* ===== Flags of the language switcher ===== */

/* Language switcher flags (x-flag-icon, resources/views/components/
language-switcher.blade.php) - 3:2 ratio matching the component's own
viewBox, small rounded rect with a subtle border so pale flags (eg.
Poland) don't blend into a white dropdown. Shared, not per group: the
language switcher is one shared component, and a group without this CSS
renders its flags with no width/height at all. */
.flag-icon {
    display: inline-block;
    width: 1.3em;
    height: .867em;
    border-radius: 2px;
    overflow: hidden;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .15);
    vertical-align: middle;
    flex: 0 0 auto;
}

.flag-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}


/* ===== Brand colour ===== */

/* Bootstrap's blue -> the site group's brand colour, once for every group.

Bootstrap writes its blue in two ways: through --bs-primary(-rgb)/--bs-link-*
(borders, backgrounds and text of the .border-primary/.bg-primary/.text-primary
utilities and every plain link), and as literal values inside the component
rules (.btn-primary, .btn-outline-primary, the focus ring and the checked state of
form controls, the active page of .pagination, the active .dropdown-item ...).
The first kind is remapped in :root, the second one component by component below.

Each group's site.css defines the colour in its :root:
  --brand-primary        the colour itself
  --brand-primary-rgb    the same as "r, g, b" (the utilities need the triple)
  --brand-primary-hover  ~15% darker: hover of links and buttons
The pressed shade and the light focus border are derived here. Loaded before the
group's site.css, so a group can still override a single rule. */
:root {
    --brand-primary-hover-rgb: var(--brand-primary-rgb);
    --brand-primary-active: color-mix(in srgb, var(--brand-primary-hover), black 12%);
    --brand-primary-focus: color-mix(in srgb, var(--brand-primary), white 55%);
    --brand-primary-focus-ring: 0 0 0 .25rem rgba(var(--brand-primary-rgb), .25);

    --bs-primary: var(--brand-primary);
    --bs-primary-rgb: var(--brand-primary-rgb);
    --bs-link-color: var(--brand-primary);
    --bs-link-color-rgb: var(--brand-primary-rgb);
    --bs-link-hover-color: var(--brand-primary-hover);
    --bs-link-hover-color-rgb: var(--brand-primary-hover-rgb);
    --bs-focus-ring-color: rgba(var(--brand-primary-rgb), .25);
}

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

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

.btn-link {
    --bs-btn-color: var(--brand-primary);
    --bs-btn-hover-color: var(--brand-primary-hover);
    --bs-btn-active-color: var(--brand-primary-hover);
    --bs-btn-focus-shadow-rgb: var(--brand-primary-rgb);
}

.btn-close {
    --bs-btn-close-focus-shadow: var(--brand-primary-focus-ring);
}

.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    border-color: var(--brand-primary-focus);
    box-shadow: var(--brand-primary-focus-ring);
}

.form-check-input:checked {
    background-color: var(--brand-primary);
    border-color: var(--brand-primary);
}

.dropdown-menu {
    --bs-dropdown-link-active-bg: var(--brand-primary);
}

.pagination {
    --bs-pagination-hover-color: var(--brand-primary-hover);
    --bs-pagination-focus-color: var(--brand-primary-hover);
    --bs-pagination-focus-box-shadow: var(--brand-primary-focus-ring);
    --bs-pagination-active-bg: var(--brand-primary);
    --bs-pagination-active-border-color: var(--brand-primary);
}

.accordion {
    --bs-accordion-btn-focus-border-color: var(--brand-primary-focus);
    --bs-accordion-btn-focus-box-shadow: var(--brand-primary-focus-ring);
    --bs-accordion-active-color: var(--brand-primary-active);
    --bs-accordion-active-bg: rgba(var(--brand-primary-rgb), .1);
}


/* ===== General rules ===== */

/* Corner rounding of every group: Bootstrap's components and our own boxes (var(--bs-border-radius)) get 12px. */
:root {
    --line-soft: #e6e9ee;
    --text-soft: #6b7280;
    --bs-border-radius: 12px;
    --bs-border-radius-sm: 12px;
    --bs-border-radius-lg: 12px;
}

/* "Sticky footer": on a short page (eg. the contact topics page) the footer would sit right under the content
instead of at the bottom of the viewport. Plain flexbox, not position:fixed/sticky on the footer: it still scrolls
away on a long page, this only fills the leftover space on a short one. */
html, body {
    height: 100%;
}

body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background-color: #f6f8fa;
}

main {
    flex: 1 0 auto;
}

/* Placeholder of every input (search forms, contact form, ...). .form-control::placeholder is repeated because
Bootstrap's rule of that name (class plus pseudo-element) out-ranks a bare element selector regardless of order. */
input::placeholder,
.form-control::placeholder {
    color: #adb5bd;
    font-style: italic;
}

/* The shorthand, not --bs-card-border-color: that variable also colours the border of .card-header (the tab bar of the
BY and Regio detail pages), which is meant to stay Bootstrap's translucent line. */
.card {
    border: 1px solid var(--line-soft);
}

/* The logo (.navbar-brand.site-logo) in the brand colour, also on hover: through Bootstrap's own variables. */
.navbar {
    --bs-navbar-brand-color: var(--brand-primary);
    --bs-navbar-brand-hover-color: var(--brand-primary);
}

/* Header icon links (person, watchlist, <x-language-switcher>): their own class rather than text-body, so each group
controls the colour (the transparent header turns them white, which text-body would override). */
.nav-icon-link {
    display: flex;
    align-items: center;
    color: var(--bs-body-color);
    text-decoration: none;
}

.nav-icon-link:hover {
    color: var(--brand-primary);
}

/* The header's dropdowns (menu entries such as "Service", account, watchlist, language) open under the mouse already,
not only on a click - for devices that have a mouse; a finger still taps, and a click works as before. A menu
entry opens that way only while the menu stands in a row (from the breakpoint of the layout's navbar-expand class):
in the burger panel its list unfolds in the flow and would push the entries below it away from the mouse.
No margin above the list: a gap between the entry and its list would close it on the way there. */
@media (hover: hover) and (pointer: fine) {
    .navbar .dropdown:not(.nav-item):hover > .dropdown-menu {
        display: block;
    }

    /* A click on the entry has Bootstrap's script open the list too, which adds its 2px margin above it: the list
    that stood open under the mouse jumped down by those 2px. */
    .navbar .dropdown > .dropdown-menu[data-bs-popper] {
        margin-top: 0;
    }

    /* Bootstrap aligns an "end" list only once its script opened it. */
    .navbar .dropdown:hover > .dropdown-menu-end {
        right: 0;
        left: auto;
    }
}

@media (hover: hover) and (pointer: fine) and (min-width: 992px) {
    .navbar-expand-lg .nav-item.dropdown:hover > .dropdown-menu {
        display: block;
    }
}

@media (hover: hover) and (pointer: fine) and (min-width: 1200px) {
    .navbar-expand-xl .nav-item.dropdown:hover > .dropdown-menu {
        display: block;
    }
}

/* The burger button has no frame (owner, 2026-10-13): the three lines alone, standing at the right edge of the
page's content like the icons of the desktop bar. Bootstrap's ring stays for the keyboard focus only - after a click
or a tap it looked like the frame again. */
.navbar-toggler {
    border: 0;
    padding-right: 0;
}

.navbar-toggler:focus:not(:focus-visible) {
    box-shadow: none;
}

/* A little more room between the entries of the header's lists than Bootstrap's .25rem above and below each. */
.navbar .dropdown-menu {
    --bs-dropdown-item-padding-y: .45rem;
}

/* In the burger panel the icons (account, watchlist, language) stand at the LEFT edge, so their lists, which open
towards the left ("end") for the icons' place at the right edge of the desktop bar, ran out of the screen. Below the
breakpoint of the layout's navbar-expand class they open towards the right. */
@media (max-width: 991.98px) {
    .navbar-expand-lg .navbar-collapse .dropdown > .dropdown-menu-end {
        right: auto;
        left: 0;
    }
}

@media (max-width: 1199.98px) {
    .navbar-expand-xl .navbar-collapse .dropdown > .dropdown-menu-end {
        right: auto;
        left: 0;
    }
}

/* Old price next to a boat's price on a detail page (Regio and BKM; BY draws it smaller and muted). */
.boat-price-old {
    font-size: 1rem;
    color: #9ca3af;
    text-decoration: line-through;
}

/* What a preview of a not yet published article locks completely (the share card and the watchlist star/heart;
the buttons of the request forms are merely `disabled`): the markup carries `inert` (no click, no focus), this shows it. */
[data-preview-off] {
    opacity: .5;
}

/* A <select> of a search form with a chosen value (class set by the form's Alpine state): tinted in the
   group's main colour, so the fields that are set stand out. The reset "x" beside it is the sign that
   does not rely on colour. */
.form-select.is-set {
    background-color: rgba(var(--brand-primary-rgb), .14);
    border-color: var(--brand-primary);
}

/* Alpine hides [x-cloak] elements once it boots; this hides them before that. */
[x-cloak] {
    display: none !important;
}

/* The decoy characters inside an email address (<x-obfuscated-email>): hidden here and never by an inline style, so a
   parser that does not apply this stylesheet reads them as part of the address. display:none also keeps them out of a
   copied selection and the screen reader. */
.ci-s {
    display: none !important;
}

/* <x-wait-layer>: every "please wait" - a login/logout (over the page), a list loading (over its results, and over the
filter panel on a phone). Covers the nearest positioned box; fades in after a moment, so a quick answer never flashes
it (the animation starts again each time the layer is shown). */
.wait-layer {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.7);
    cursor: progress;
    animation: wait-layer-in 0.2s ease 0.15s both;
}

.wait-layer[hidden] {
    display: none;
}

/* The whole page, above Bootstrap's windows (1055): the watchlist window's login link starts a login too. */
.wait-layer-page {
    position: fixed;
    z-index: 2000;
}

.wait-layer-page .spinner-border,
.wait-layer-tall .spinner-border {
    width: 3rem;
    height: 3rem;
}

/* A long list: the spinner near the top, and it stays in the middle of the screen while the list scrolls. */
.wait-layer-tall {
    align-items: flex-start;
}

.wait-layer-tall .spinner-border {
    position: sticky;
    top: 45vh;
    margin-top: 3rem;
}

@keyframes wait-layer-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

/* <x-seo-spec>: the special SEO text below a list. Just a readable text block; a group that wants it different writes
the .seo-spec rules again in its own site.css. The old site put "&nbsp;<br>" in front of every block, here the heading's
top margin does that. */
.seo-spec {
    margin-top: 2.5rem;
}

.seo-spec h2 {
    margin: 1.5rem 0 0.75rem;
    font-size: 1.35rem;
}

.seo-spec > :first-child {
    margin-top: 0;
}

/* The introduction right below the h1 (no gap above, a little before the results). */
.seo-spec-top {
    margin: 0 0 1rem;
}

.seo-spec ul {
    margin-bottom: 1rem;
}

.seo-spec .textend {
    font-style: italic;
}

/* The store buttons of a group's mobile app (<x-app-badges>), one under the other, as on the old BY site's dark footer
(".footer--dark .footer__mobile-app .app", Carsten 2026-10-29): a box in the footer's colour with a faint border, the
logo and the store's name white, the small line above it muted; under the mouse the box turns a shade lighter and
gets a shadow. Only BY has an app, and its footer is dark (--by-dark), so the colours are those of a dark ground.
inline-flex + stretch: both boxes as wide as the wider one, not as the column. */
.app-badges {
    display: inline-flex;
    flex-direction: column;
    align-items: stretch;
    gap: .75rem;
}

.app-badge {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: 14px 16px;
    border: 1px solid #2e3447;
    border-radius: 12px;
    background-color: #191f33;
    color: #fff;
    text-decoration: none;
    transition: background-color .3s ease-in-out, box-shadow .3s ease-in-out;
}

.app-badge:hover,
.app-badge:focus-visible {
    background-color: #2e3447;
    box-shadow: 0 16px 32px rgba(0, 0, 0, .24);
    color: #fff;
}

.app-badge-logo {
    flex-shrink: 0;
}

.app-badge-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.app-badge-note {
    font-size: 12px;
    line-height: 16px;
    color: #767e94;
}

.app-badge-store {
    font-size: 16px;
    font-weight: 600;
    line-height: 24px;
    white-space: nowrap;
}

/* Phone: never wider than the column. On a narrow phone or with a bigger text size (BY's footer gives the buttons half
the width) they made the whole page wider than the screen, and Firefox on Android then showed the page shifted, the
burger button half outside (owner, 2026-11-05). So a little less padding, and the store's name may break between its
two words. From 576px the column has room; on a desktop the narrow sixth column lets the box reach into the page's
margin, which widens nothing. */
@media (max-width: 575.98px) {
    .app-badges {
        max-width: 100%;
    }

    .app-badge {
        gap: .5rem;
        padding: 12px;
    }

    .app-badge-store {
        white-space: normal;
    }
}

/* <x-boat-type-icon>'s line art - one rule for every instance on every group's pages, reading the colour from
--boat-type-icon-stroke (set inline on each instance's own <svg> root, see that component's docblock for why: its
SVGs used to carry their OWN <style> block with this same class name, which is not scoped to one <svg> and broke
as soon as two instances with different $stroke values shared a page). */
.boat-type-icon-stroke {
    fill: none;
    stroke: var(--boat-type-icon-stroke, #464a58);
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-miterlimit: 10;
}

/* The "msg=" notice (<x-message-banner>): the last child of every layout's <header>, floating just below the nav
   over the page instead of pushing it down (the header is fixed/absolute/relative in every group, so top:100% is
   its lower edge). Below the nav's own dropdowns (1000). Good news fades out once the visitor goes on with the page
   (shared/js/page-message.js); a warning stays until it is closed. */
.page-message {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 990;
    /* A little down into the page, where the eye is (owner, 2026-09-25): an eighth of the window, 1.5-8rem. */
    padding-top: clamp(1.5rem, 12vh, 8rem);
    pointer-events: none;
}

.page-message .alert {
    max-width: 720px;
    margin: 0 auto;
    pointer-events: auto;
}

/* A real frame in the full colour (owner, 2026-09-25) - Bootstrap's own alert border is a pale tint that hardly shows
   over a photo. Floating, so the thicker border moves nothing. */
.page-message .alert-success {
    border: 2px solid var(--bs-success);
}

.page-message .alert-danger {
    border: 2px solid var(--bs-danger);
}

.page-message-icon {
    font-size: 1.25em;
    line-height: 1.2;
}

/* Set by shared/js/page-message.js once the visitor goes on with the page (not after a fixed time, WCAG 2.2.1). */
.page-message-leaving {
    animation: page-message-out .6s ease forwards;
}

/* Paused while the mouse is on it or the keyboard is in it (its close button). */
.page-message-leaving:hover,
.page-message-leaving:focus-within {
    animation-play-state: paused;
}

@keyframes page-message-out {
    to {
        opacity: 0;
        visibility: hidden;
    }
}

/* The picture of a grid card (Regio, BKM; BY has horizontal list cards). 4:3: many adverts have portrait photos, which
object-fit: cover crops harder the wider the box is - 3:2 left a sliver that read as "distorted". */
.boat-grid-img {
    aspect-ratio: 4 / 3;
    object-fit: cover;
}

/* Faded illustration (<x-empty-list>, <x-error-page>): shape and size; each group's site.css sets the
picture (.empty-list-img, .error-illustration-404/-500). 80% of the column on a phone, at most 546px
(owner, 2026-09-30). The pictures are square, so contain never crops.
A background-image DIV, not an <img>: mask-image is a silent no-op on an <img> in Chrome. The mask
fades the edge into the page (owner: no hard border); the ellipse 50% 42% was tuned by eye with the
owner, the -webkit- copy is for older Safari. */
.faded-illustration {
    max-width: 546px;
    width: 80%;
    aspect-ratio: 1 / 1;
    margin-left: auto;
    margin-right: auto;
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
    -webkit-mask-image: radial-gradient(ellipse 50% 42% at center, #000 60%, transparent 100%);
    mask-image: radial-gradient(ellipse 50% 42% at center, #000 60%, transparent 100%);
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}

/* The box at the top of a search form that says which list this is and lets the visitor leave it: the watchlist
switch (<x-filter.watchlist-checkbox>) and the seller of a seller's list (<x-filter.seller-checkbox>). A light box; a
group whose form has other paddings overrides it in its site.css. */
.filter-top-box {
    margin-bottom: 1rem;
    padding: .5rem;
    border-radius: var(--bs-border-radius);
    background-color: var(--bs-light);
}

/* The one-time privacy notice of a video (<x-video-embed>, <x-video-lightbox-modal>) in place of the iframe until the
visitor clicks through it: centred over the still-empty 16:9 box, so the video's own size never shifts once it loads. */
.video-consent {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    width: 100%;
    height: 100%;
    padding: 1.5rem;
    text-align: center;
    color: #fff;
}

.video-consent p {
    max-width: 32rem;
    margin: 0;
}

/* <x-trap-link>, the scraper trap: outside the window instead of display:none, which a careful scraper skips. */
.trap-link {
    position: absolute;
    left: -10000px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* <x-skip-link> ("Zum Inhalt springen"): hidden until the first Tab gives it the focus, then a box at the top left,
above BY's fixed header. Its target <main id="content" tabindex="-1"> takes the focus without a frame (it is no control). */
.skip-link:focus {
    position: fixed;
    top: .5rem;
    left: .5rem;
    z-index: 2000;
    padding: .5rem 1rem;
    border-radius: .375rem;
    background-color: #fff;
    color: var(--bs-body-color);
    font-weight: 600;
    text-decoration: none;
    outline: 2px solid var(--brand-primary);
    outline-offset: 2px;
}

#content:focus {
    outline: none;
}

/* Esc over a header list that the mouse holds open hides it until the mouse leaves its entry (WCAG 1.4.13,
shared/js/header-menus.js sets the class). A list opened by a click (.show) is left to Bootstrap. */
@media (hover: hover) and (pointer: fine) {
    .navbar .dropdown.hover-dismissed:hover > .dropdown-menu:not(.show) {
        display: none;
    }
}

/* The header's menu toggles ("Service", watchlist, language, account) are <button>s, since they open a menu and lead
nowhere: they look exactly like the links they used to be (no button background, frame or padding, text at the start,
as wide as the link was). */
button.nav-link.dropdown-toggle,
button.nav-icon-link.dropdown-toggle {
    background: none;
    border: 0;
    text-align: start;
}

button.nav-link.dropdown-toggle {
    width: 100%;
}

button.nav-icon-link.dropdown-toggle {
    padding: 0;
}

/* Form fields named only by a placeholder (the request forms, the blog comment form) are wrapped in
<label class="field-hint"> with the field's name in .field-hint-label. While the field is empty the name is invisible
and the field looks as it always did (the placeholder says the same); once something is typed or chosen, the
placeholder is gone, and the name shows small on the field's top edge, so the visitor still sees what the field is
for. Nothing moves: the name lies over the border. A select whose empty choice has no text (the partner forms'
material ...) shows its name always - it would otherwise be a blank box. For screen readers the label names the field
at any time. */
.field-hint {
    position: relative;
    display: block;
    margin: 0;
}

.field-hint-label {
    position: absolute;
    top: 0;
    left: .6rem;
    max-width: calc(100% - 1.2rem);
    overflow: hidden;
    padding: 0 .25rem;
    transform: translateY(-50%);
    border-radius: .25rem;
    background-color: #fff;
    color: #6c757d;
    font-size: .7rem;
    line-height: 1.2;
    white-space: nowrap;
    text-overflow: ellipsis;
    pointer-events: none;
    opacity: 0;
}

/* Four rules, not one list: a :has() inside a :has() is not allowed, and one invalid selector would drop a whole list. */
.field-hint:has(> input:not(:placeholder-shown)) > .field-hint-label {
    opacity: 1;
}

.field-hint:has(> textarea:not(:placeholder-shown)) > .field-hint-label {
    opacity: 1;
}

.field-hint:has(> select):not(:has(> select option[value=""]:checked)) > .field-hint-label {
    opacity: 1;
}

.field-hint:has(> select option[value=""]:checked:empty) > .field-hint-label {
    opacity: 1;
}
