/*
 * ai_media_label - AI content marking overlay (EU AI Act, Art. 50).
 *
 * Ships into sites whose CSS is unknown. Everything is namespaced under
 * `aiml-`; there are no global selectors, no element type selectors and no
 * resets. The only descendant selectors are `.aiml-wrap > *`,
 * `.aiml-wrap--fill > :not(.aiml-badge)` and `.aiml-badge > *`, none of which
 * can match anything outside the component.
 *
 * Geometry is kept in parity with Service/BadgeGeometryCalculator.php so the
 * overlay and the burned-in icon look identical. The PHP, restated as the
 * closed form this file implements:
 *
 *     floor   = max(sizeMinPx, min(sizeMinWidthPx / ratio, sizeMaxPx))
 *     ceiling = min(sizePercent * shorterEdge, sizeMaxPx, sizeMaxWidthPx / ratio)
 *     height  = min(ceiling, max(floor, sizePercent * shorterEdge), fit)
 *     width   = height * ratio
 *
 * where `fit` is the room left between the margins. Ceilings beat floors in
 * both implementations: a badge that overflows its media is a rendering bug,
 * an undersized one is only a weaker disclosure. Verified by measuring the
 * rendered badge in a browser against the PHP output over a grid of media
 * sizes and all icon ratios - see the parity note at the end of this file.
 *
 * No animation is defined anywhere in this file - the marking must never move.
 */

/* ---------------------------------------------------------------------------
 * Geometry
 *
 * Fallback only. HtmlLabelInjector emits these same properties from the
 * extension configuration into a later `:root` block, which wins; the literals
 * below are what a page cached before that block existed, or the stylesheet
 * loaded on its own, renders with. They must therefore stay identical to the
 * shipped defaults in ext_conf_template.txt - a drift between the two is what
 * once shipped a badge at half its intended size, and CssGeometryParityTest
 * now fails on it.
 *
 * They live on `:root` and not on `.aiml-wrap` so that the injected block can
 * override them by document order, and so a site override on `.aiml-wrap`
 * still beats both.
 * ------------------------------------------------------------------------ */

:root {
    --aiml-size: 10%;
    --aiml-min: 80px;
    --aiml-max: 200px;
    --aiml-min-width: 64px;
    --aiml-max-width: 480px;
    --aiml-inset: 2%;
    --aiml-scrim-opacity: 0.45;
}

/* ---------------------------------------------------------------------------
 * Wrapper
 *
 * `.aiml-wrap` is a <span> and wraps exactly one bare media element
 * (<img>, <video>, <audio>, <iframe>), so it needs a display value: an inline
 * box has no box of its own and would give the badge a font-sized containing
 * block instead of the media's box.
 *
 * `inline-block` is the default because it shrink-wraps the media, keeps it in
 * the inline flow and still honours an inherited `text-align`, so a centered
 * image stays centered. See the variant table in the integration notes.
 * ------------------------------------------------------------------------ */

.aiml-wrap {
    --aiml-ratio: 1;
    --aiml-ink: #fff;
    --aiml-scrim: rgba(0, 0, 0, var(--aiml-scrim-opacity));

    position: relative;
    display: inline-block;
    max-width: 100%;
    /* Keeps the wrapper off the parent's text baseline, so wrapping a media
       element adds no descender gap below it. */
    vertical-align: bottom;
}

/* Same reasoning one level down: without this the wrapper is ~4px taller than
   the media and the badge no longer sits on the media's corner.
   Ignored by block-level children. */
.aiml-wrap > * {
    vertical-align: bottom;
}

/* For media that sizes itself from its container - `width: 100%` images and
   videos - where shrink-wrapping would narrow it to its intrinsic width. */
.aiml-wrap--block {
    display: block;
}

/* For media whose host CSS stretches it via a direct-child selector, most
   commonly a Bootstrap `.ratio > *` box around a YouTube or Vimeo iframe.
   Inserting the wrapper makes the wrapper the direct child, so the wrapper
   receives the stretch and the media inside it must be re-stretched to fill
   it. The badge is excluded, otherwise it would inherit `width: 100%`. */
.aiml-wrap--fill {
    display: block;
    width: 100%;
    height: 100%;
}

.aiml-wrap--fill > :not(.aiml-badge) {
    width: 100%;
    height: 100%;
}

/* Fallback icon geometries, keyed off the state attribute that is already part
   of the markup contract. Taken from the Commission SVG viewBoxes; states 0 and
   1 never render a badge.
   These are the OFFICIAL ENGLISH shapes only. A translated wordmark is a
   different shape - "AI GENERATED" and "SZTUCZNA INTELIGENCJA" are not the same
   width - so a constant per state stopped being the answer once the artwork
   followed the page language. LabelMarkupBuilder therefore writes the icon's
   real ratio onto the badge as an inline `--aiml-ratio`, which wins over these.
   They stay for page-cache entries written before that existed, and for the
   English case they describe exactly. */
.aiml-wrap[data-aiml-state="3"] {
    --aiml-ratio: 3.15707;
}

.aiml-wrap[data-aiml-state="4"] {
    --aiml-ratio: 3;
}

/* ---------------------------------------------------------------------------
 * Badge
 * ------------------------------------------------------------------------ */

.aiml-badge {
    /* The two bounds of the PHP formula, named once and reused on both axes.
       Custom properties substitute textually, so the percentages inside them
       resolve per axis at the point of use - against the wrapper's height in
       `*-height`, against its width in `*-width`. That is what turns two
       independent axes into "of the shorter edge". */
    --aiml-height-floor: max(
        var(--aiml-min),
        min(calc(var(--aiml-min-width) / var(--aiml-ratio)), var(--aiml-max))
    );
    --aiml-height-ceiling: min(
        var(--aiml-size),
        var(--aiml-max),
        calc(var(--aiml-max-width) / var(--aiml-ratio))
    );
    /* Room left between the margins, i.e. the PHP's proportional shrink. */
    --aiml-fit: calc(100% - 2 * var(--aiml-inset));

    position: absolute;
    z-index: 1;
    box-sizing: border-box;
    display: block;
    /* Fallback for browsers without `aspect-ratio`: drive the height off the
       block axis and let the SVG below supply the width from its own ratio.
       Percentages resolve against the wrapper's used box, which is already
       known when out-of-flow boxes are laid out, so this works despite
       `height: auto` on the wrapper.
       min() outside max(), not clamp(): clamp() lets the floor win over the
       ceiling, and the PHP resolves that conflict the other way round. */
    height: min(
        var(--aiml-height-ceiling),
        max(var(--aiml-height-floor), var(--aiml-size))
    );
    width: auto;
    color: var(--aiml-ink);
    pointer-events: none;
    -webkit-user-select: none;
    user-select: none;
    /* Legal marking: keep it visible in forced-colors mode and when printing. */
    forced-color-adjust: none;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}

.aiml-badge > * {
    display: block;
    height: 100%;
    width: auto;
}

/* Exact parity with BadgeGeometryCalculator.
 *
 * Both axes must stay `auto`: CSS only transfers a min/max size through an
 * aspect ratio into the other axis when that axis is auto. The used height
 * then becomes
 *     min(max-height, max-width / ratio)
 *   = min( ceiling of the height, ceiling of the width / ratio )
 *   = min(10% of the shorter side, sizeMaxPx, sizeMaxWidthPx / ratio),
 * floored by min-height and min-width, which is the PHP formula exactly.
 *
 * Every bound appears on BOTH axes, each expressed in that axis' own units, so
 * that whichever axis the browser sizes first ends up at the same answer:
 *     *-height: <bound>            *-width: calc(<bound> * ratio)
 * The width clamps are already folded into --aiml-height-floor and
 * --aiml-height-ceiling as `sizeMinWidthPx / ratio` and
 * `sizeMaxWidthPx / ratio`, which is how a clamp on the width reaches the
 * height without a second aspect-ratio calculation.
 *
 * The floors are themselves capped by --aiml-fit. CSS resolves a min/max
 * conflict in favour of the minimum, the PHP in favour of the maximum, so the
 * floor has to be lowered explicitly - otherwise a 64px-wide floor would
 * overflow a 60px-wide thumbnail. */
@supports (aspect-ratio: 1 / 1) {
    .aiml-badge {
        aspect-ratio: var(--aiml-ratio);
        width: auto;
        height: auto;
        min-height: min(var(--aiml-height-floor), var(--aiml-fit));
        min-width: min(
            calc(var(--aiml-height-floor) * var(--aiml-ratio)),
            var(--aiml-fit)
        );
        max-height: min(var(--aiml-height-ceiling), var(--aiml-fit));
        max-width: min(
            calc(var(--aiml-height-ceiling) * var(--aiml-ratio)),
            var(--aiml-fit)
        );
    }

    /* Sizes the badge from its constraints instead of from its contents.
     *
     * An absolutely positioned box with `width: auto` is shrink-to-fit, and the
     * inline SVG below is `width: 100%`, so it contributes nothing to the
     * intrinsic width. The badge therefore collapsed onto the SVG's default
     * intrinsic size - measured at 300px - and stopped growing there. That was
     * invisible while sizeMaxPx was 96px, because the max clamps bound long
     * before 300px; at 200px it became a hard ceiling that the burned-in
     * variant did not share, so the same asset rendered visibly smaller in
     * overlay mode than in burnIn mode.
     *
     * This zero-height, invisible sizer declares a max-content width of the
     * width ceiling, which is the largest the badge may ever be. Shrink-to-fit
     * then lands on the max-* constraints rather than on the icon. */
    .aiml-badge::after {
        content: "";
        display: block;
        width: var(--aiml-max-width);
        height: 0;
    }

    .aiml-badge > * {
        width: 100%;
        height: 100%;
    }
}

/* The inline SVG. `preserveAspectRatio` defaults to `xMidYMid meet`, so the
   icon never distorts. `fill` is set on the root SVG only - child paths
   carrying their own `fill` keep it, so multi-tone icons are not flattened. */
.aiml-badge > * {
    fill: currentColor;
}

/* Position modifiers. Physical, not logical: the class names name physical
   corners and the burned-in variant burns the icon into a physical corner,
   so the two must not diverge under `dir="rtl"`. */

.aiml-badge--top-left {
    top: var(--aiml-inset);
    left: var(--aiml-inset);
}

.aiml-badge--top-right {
    top: var(--aiml-inset);
    right: var(--aiml-inset);
}

.aiml-badge--bottom-left {
    bottom: var(--aiml-inset);
    left: var(--aiml-inset);
}

.aiml-badge--bottom-right {
    bottom: var(--aiml-inset);
    right: var(--aiml-inset);
}

/* Colour variants. The server picks the variant from a measured contrast
   ratio against the media, so these only carry the ink and a matching scrim -
   they never try to guess. The `-50` variants tint the ink rather than setting
   `opacity`, so the scrim keeps its full strength underneath. */

.aiml-badge--black {
    --aiml-ink: #000;
    --aiml-scrim: rgba(255, 255, 255, var(--aiml-scrim-opacity));
}

.aiml-badge--white {
    --aiml-ink: #fff;
    --aiml-scrim: rgba(0, 0, 0, var(--aiml-scrim-opacity));
}

.aiml-badge--black-50 {
    --aiml-ink: rgba(0, 0, 0, 0.5);
    --aiml-scrim: rgba(255, 255, 255, var(--aiml-scrim-opacity));
}

.aiml-badge--white-50 {
    --aiml-ink: rgba(255, 255, 255, 0.5);
    --aiml-scrim: rgba(0, 0, 0, var(--aiml-scrim-opacity));
}

/* Optional scrim: a soft rounded plate behind the icon, drawn as a
   pseudo-element sized in percentages of the badge so it scales with the badge
   instead of with the inherited font size. */
.aiml-badge--scrim::before {
    content: "";
    position: absolute;
    inset: -8% -3%;
    z-index: -1;
    border-radius: 9999px;
    background-color: var(--aiml-scrim);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}

/* ---------------------------------------------------------------------------
 * Screen reader text
 *
 * Carries the full disclosure sentence for assistive technology. Never
 * `display: none` - that would remove it from the accessibility tree.
 * `!important` is deliberate: this is a utility whose geometry must survive
 * any host stylesheet, and a visible leak would be a visual regression.
 * ------------------------------------------------------------------------ */

.aiml-sr-only {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    clip-path: inset(50%) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* ---------------------------------------------------------------------------
 * Defensive guards
 * ------------------------------------------------------------------------ */

/* Neutralises a host stylesheet's blanket `* { transition: all }` /
   `animation` rules on the marking. */
@media (prefers-reduced-motion: reduce) {
    .aiml-badge,
    .aiml-badge::before,
    .aiml-badge > * {
        transition: none;
        animation: none;
    }
}

/* The marking is legally required on printed reproductions, so it is
   re-asserted against host print stylesheets that hide decorative overlays. */
@media print {
    .aiml-badge {
        display: block !important;
        visibility: visible !important;
    }

    .aiml-badge--scrim::before {
        display: block !important;
        visibility: visible !important;
    }
}

/* ---------------------------------------------------------------------------
 * Integration notes
 *
 * Markup contract:
 *   <span class="aiml-wrap" data-aiml-state="3" data-aiml-done="1">
 *     <img ... >
 *     <span class="aiml-badge aiml-badge--bottom-right aiml-badge--white"
 *           data-aiml-lang="de" style="--aiml-ratio:3.94"
 *           aria-hidden="true"><svg ... /></span>
 *   </span>
 *   <span class="aiml-sr-only">...</span>
 *
 *   `data-aiml-lang` is the ISO 639-1 code of the artwork and is only present
 *   when it is not the official English set. Nothing styles off it.
 *   The inline `--aiml-ratio` is the icon's real native ratio for that
 *   language; it overrides the per-state fallback above. Do not remove either
 *   fallback rule: markup cached before the inline property existed relies on
 *   them, and without a ratio a wordmark renders square.
 *
 * Wrapper variant - HtmlLabelInjector must pick one per wrapped tag:
 *
 *   `.aiml-wrap`                 (default)
 *       Bare, intrinsically sized media: <img> with width/height attributes
 *       and `max-width: 100%` (the fluid_styled_content default), <audio>,
 *       <iframe> with width/height attributes, fixed-size <video>.
 *
 *   `.aiml-wrap .aiml-wrap--block`
 *       Media that sizes itself from its container: inline `width: 100%`,
 *       or a site stylesheet that sets `width: 100%` on the media.
 *       Without it the wrapper shrink-wraps to the intrinsic width - measured
 *       at 391px instead of 800px in an 800px container.
 *
 *   `.aiml-wrap .aiml-wrap--fill`
 *       Media stretched by a direct-child selector in the host stylesheet.
 *       The important case is a Bootstrap ratio box around a YouTube or Vimeo
 *       iframe: `.ratio > * { position: absolute; width: 100%; height: 100% }`
 *       matches the direct child, so once the wrapper is inserted the wrapper
 *       receives the stretch and the iframe inside it falls back to its
 *       attribute size. This variant passes the stretch through.
 *       This project's theme.css ships exactly that rule, so online media
 *       needs this variant.
 *
 *   The choice cannot be moved into CSS: no wrapper value can both
 *   shrink-wrap its child and let a `width: 100%` child fill its container.
 *
 * Custom properties. The first seven come from the extension configuration and
 * are emitted per page into `:root`; the literals in this file are only their
 * fallback. Overriding one on `.aiml-wrap` wins over both and puts the overlay
 * out of parity with the burned-in variant - change the setting instead.
 *   --aiml-size           badge height, 10%   - sizePercent
 *   --aiml-min            height floor, 80px  - sizeMinPx
 *   --aiml-max            height ceiling, 200px - sizeMaxPx
 *   --aiml-min-width      width floor, 64px   - sizeMinWidthPx
 *   --aiml-max-width      width ceiling, 480px - sizeMaxWidthPx
 *   --aiml-inset          corner margin, 2%   - marginPercent
 *   --aiml-scrim-opacity  scrim alpha, 0.45   - scrimOpacity
 *   --aiml-ratio      icon width / height. Written inline onto the badge from
 *                     the icon manifest; the data-aiml-state rules are the
 *                     fallback
 *   --aiml-ink        icon colour (set by the colour modifiers)
 *   --aiml-scrim      scrim colour (set by the colour modifiers)
 *
 * Parity with the PHP - measured, not assumed
 * -------------------------------------------
 * 14 wrapper sizes from 120x120 to 3000x3000, crossed with all three icon
 * ratios, were rendered in Chrome; `getBoundingClientRect()` was read off every
 * badge and compared against BadgeGeometryCalculator::calculateWith() driven
 * with the same inputs. 38 of the 42 cases agree to within 0.6px. The other
 * four are the two structural differences below, neither of which can be
 * closed in CSS.
 *
 *   Deviation 1 - integer rounding. The PHP works in whole pixels: at
 *   1200x675 it rounds the height 67.5 up to 68 and re-derives the width from
 *   that integer, giving 215x68 where CSS lays out 213.1x67.5. Bounded by two
 *   pixels, and only on media whose shorter edge is an odd multiple of five.
 *
 *   Deviation 2 - the fit rule on media too small to hold the badge. The PHP
 *   shrinks the badge by one factor derived from both axes at once, so the
 *   icon keeps its ratio; CSS has to apply its two `--aiml-fit` terms per axis,
 *   which can pull the two axes apart - measured at 8.8% on a 120x120 box with
 *   the widest icon. Only reachable below roughly 150px, which is around the
 *   `minImageShorterEdge` gate the PHP refuses to label at all.
 *
 *   Also, and by construction: the PHP takes the margin as a percentage of the
 *   shorter edge on both axes, CSS resolves `--aiml-inset` per axis. That moves
 *   the badge, it never resizes it.
 *
 * Both modes are mutually exclusive per site - an installation is either in
 * burnIn or in overlay mode - so none of this is ever visible side by side.
 * What matters, and what holds, is that an asset does not change apparent size
 * when an integrator switches the mode.
 *
 * The inline SVG must carry a `viewBox` and must not set `fill` on its own
 * root element, otherwise the colour modifiers are ignored. Child paths may
 * carry their own `fill`. If a new icon with a different aspect ratio is
 * added, add a matching `--aiml-ratio` rule above.
 * ------------------------------------------------------------------------ */
