/*!
 * Mobinex component layer
 * ---------------------------------------------------------------------------
 * Loaded LAST, after style.css / mobileCss / custom.css, so it can restyle
 * without those files changing. It is keyed to the class names the markup
 * already carries — every jQuery selector in script.js keeps matching, and no
 * blade file has to change for this to take effect.
 *
 * Component shapes come from the Inex design system as implemented in
 * `inex-react` (`button.styles.ts`, `input.styles.ts`). Values come from
 * tokens.css. See openspec/changes/design-system-reskin/design.md §11.4.
 *
 * Tokens only. A colour literal in this file is a defect.
 * ---------------------------------------------------------------------------
 */

/* ===========================================================================
 * 0. The page surface — task 1.8.12
 *
 * Nothing in the codebase set a background on `html` or `body`; both computed to
 * rgba(0,0,0,0) and the design relied entirely on the user agent painting white
 * behind it. That holds in a default desktop browser and nowhere else:
 *
 *   - a browser in dark mode paints a dark canvas, and every page whose sections
 *     do not set their own background renders near-black ink on it. The
 *     registration wizard — step one of the purchase flow — was unreadable;
 *   - Android and iOS webviews with dark theming do the same, and webviews are
 *     precisely what routes/mobile.php serves;
 *   - forced-colors and high-contrast modes have nothing to anchor to.
 *
 * `color-scheme: light` states that this is a light-only design, so the UA stops
 * auto-darkening form controls and scrollbars to match a dark system theme.
 * =========================================================================== */

:root {
    color-scheme: light;
}

body {
    background-color: var(--color-additional-background-white);
    color: var(--color-text-icons-primary);
}

/* ===========================================================================
 * 0b. The real bold face — task 2.3 (M3)
 *
 * See the file header note: `light` was declared at 400 only, so every
 * font-weight:700 rendered as a synthetic bold. This adds the actual FiraGO Bold
 * as the 700 face of the same family. Same file the `lightb` family already
 * loads, so no extra bytes.
 * =========================================================================== */

@font-face {
    font-family: light;
    src: url(../fonts/FiraGO-Bold.woff2) format("woff2"),
         url(../fonts/FiraGO-Bold.ttf) format("truetype");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

/* ===========================================================================
 * 2b. Skip link — task 2.18
 *
 * Seven header controls sit before <main> on every page, with no way past them
 * by keyboard. The link is the first focusable element in the document and stays
 * out of sight until it takes focus, so it costs sighted users nothing.
 * =========================================================================== */

.skip-link {
    position: absolute;
    left: var(--spacing-xxs);
    top: -100%;
    z-index: 1000;
    padding: var(--spacing-xxxxs) var(--spacing-xxs);
    background-color: var(--color-additional-background-white);
    color: var(--color-text-icons-primary);
    border-radius: var(--radius-xs);
    box-shadow: 0 2px 8px rgba(33, 38, 47, 0.2);
    transition: none;
}

.skip-link:focus {
    top: var(--spacing-xxs);
}

/* ===========================================================================
 * 1. Focus — task 1.2
 *
 * There was no focus style anywhere in the product: reset.css sets
 * `button,button:focus,input,textarea,textarea:focus{outline:0}`, which makes
 * the whole site unusable by keyboard.
 *
 * Specificity matters here. `button:focus` is (0,1,1); a bare `:focus-visible`
 * is (0,1,0) and would LOSE to the reset no matter how late it loads. Every
 * selector below therefore names its element or class to reach (0,1,1) or
 * higher, and wins on source order.
 *
 * :focus-visible, not :focus — a mouse click on a button must not leave a ring
 * behind it. Browsers decide; keyboard and assistive tech get the indicator.
 * =========================================================================== */

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible {
    outline: 2px solid var(--color-secondary-base);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}

/* Checkboxes and radios need their own rule and their own !important. reset.css
 * sets `input[type=checkbox],input[type=radio]{...outline:0!important...}` at
 * (0,2,1) WITH !important, which beats the (0,1,1) `input:focus-visible` above no
 * matter how late components.css loads — so 56 controls across 21 views (the
 * marketing preferences, unsubscribe, payment options, parking) had no keyboard
 * focus indicator at all. Matching the weight is the only way past it. */
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
    outline: 2px solid var(--color-secondary-base) !important;
    outline-offset: 2px;
}

/* On a brand-coloured or inverted surface the brand outline has no contrast
 * against its own background, so the indicator flips to white. */
.default-btn:focus-visible,
.blueBtn:focus-visible,
footer a:focus-visible,
.mob-menu a:focus-visible {
    outline-color: var(--color-text-icons-white);
    outline-offset: 3px;
}

/* Never remove the indicator without replacing it. */
*:focus-visible {
    outline-style: solid;
}

/* ===========================================================================
 * 2. Buttons — tasks 1.1 and 1.3
 *
 * `.default-btn` keeps its brand fill, its pill radius and its 14px label.
 * What changes:
 *   - min-height 41px -> 44px, the WCAG 2.2 minimum target (task 1.3);
 *   - a pressed state, which did not exist;
 *   - disabled stops sharing a colour with hover.
 *
 * That last one is a real bug being fixed, not a preference: `.default-btn.disabled`
 * and `.default-btn:hover` were both --color-secondary-light, so a disabled
 * button was indistinguishable from a hovered one.
 * =========================================================================== */

.default-btn {
    min-height: var(--size-hit-target-min);
    transition:
        background-color var(--duration-instant) var(--easing-standard),
        color var(--duration-instant) var(--easing-standard);
}

.default-btn:active:not(.disabled) {
    background-color: var(--color-secondary-dark);
}

.default-btn.disabled,
.default-btn[disabled],
.default-btn[aria-disabled="true"] {
    background-color: var(--color-op-additional-components-disable);
    color: var(--color-text-icons-disabled);
    pointer-events: none;
}

.default-btn.blue:active {
    background-color: var(--color-primary-dark);
}

/* Any control small enough to be hard to hit gets padded out to the minimum
 * without changing its visual box — flex centring does the work.
 *
 * `display` is set separately below, and deliberately NOT on .burger-icon. That
 * button is `.hidden-tab`, which reset.css hides at desktop and shows as flex
 * under 991px. Declaring display here matched .hidden-tab's specificity, came
 * later in the cascade and carried no media query, so it un-hid the mobile
 * burger on every desktop page. The alignment properties still apply once
 * .hidden-tab turns it into a flex container at the small breakpoint. */
.burger-icon,
.popup .close,
.new-card .close,
.lang .sel,
.owl-prev,
.owl-next {
    min-width: var(--size-hit-target-min);
    min-height: var(--size-hit-target-min);
    align-items: center;
    justify-content: center;
}

/* the ones with no visibility rule of their own still need to be flex boxes */
.popup .close,
.new-card .close,
.lang .sel,
.owl-prev,
.owl-next {
    display: inline-flex;
}

/* The same floor for the catalog's own controls, found by sweeping the page at
 * 375px: chips and tabs were 41px, the header avatar 40, the language picker 35.
 * Only min-height is set on the wide ones — they are already wider than 44 and
 * forcing min-width would stretch the narrow chips out of the row. */
.filter-type,
.filter-data,
.filter-button,
.tab-item,
.mob-local {
    min-height: var(--size-hit-target-min);
}

.auth-btn {
    min-width: var(--size-hit-target-min);
    min-height: var(--size-hit-target-min);
}

/* Matches the avatar it sits beside.
 *
 * The `align-items` here needs the full `header.authorized` prefix and it is not
 * decoration. `style.css` has `header.authorized .auth .name { align-items:
 * flex-end }` at (0,3,1); the `.auth .name` selector below is (0,2,0) and loses
 * to it. So the first version of this block raised the button to 44px for the
 * tap target and its centring silently did nothing — the name sat 13px above the
 * avatar's midline (measured: avatar centre 28px, name centre 14.7px).
 *
 * `flex-end` was harmless before this change, because the button was only as
 * tall as its text and there was no slack for it to push into. The 44px minimum
 * is what exposed it. A hit-target change is a layout change.
 *
 * The span's `padding-top: 3px` comes from `header.authorized .auth .name span`
 * and was tuned for that old text-height button; against a centred 44px box it
 * just pushes the label off-centre again.
 *
 * **It is `justify-content`, not `align-items`.** The button is
 * `flex-direction: column`, so the main axis is vertical and `align-items`
 * controls the HORIZONTAL placement. The original `align-items: flex-end` is
 * therefore right-alignment, not bottom-alignment — it is deliberate and is left
 * exactly as it is. An earlier attempt at this fix set `align-items: center`,
 * which did nothing vertically and would have quietly re-aligned the text
 * horizontally instead. */
.auth .name {
    min-height: var(--size-hit-target-min);
}

header.authorized .auth button.name {
    justify-content: center;
}

header.authorized .auth button.name > span {
    padding-top: 0;
}

/* The mailto:/tel: lines in the mobile menu are block-level links, so WCAG
 * 2.5.8's inline-sentence exception does not cover them. 24px is the AA minimum;
 * 44 would stretch the menu for no accessibility gain. */
.mob-menu a.item {
    min-height: 24px;
    display: flex;
    align-items: center;
}

/* ===========================================================================
 * 3. Inputs — task 1.1
 *
 * Inex's input is 56px tall with a 12px radius, a border that steps through
 * the alpha ladder on hover and focus, and a 4px ring on focus. Mobinex's is
 * 50px with an 8px radius and a flat mid-grey border whose focus state changes
 * only the background.
 *
 * Height and radius are LEFT ALONE here: five rules override the radius to 40px
 * for pill inputs, and changing the height moves every form's layout. Those are
 * §2 module decisions, per page. What this adds is the interaction ladder and
 * the focus ring, which are additive.
 * =========================================================================== */

.input-form input,
.input-form textarea,
.input-form select {
    transition:
        border-color var(--duration-instant) var(--easing-standard),
        box-shadow var(--duration-instant) var(--easing-standard),
        background-color var(--duration-instant) var(--easing-standard);
}

.input-form input:hover:not(:disabled):not(:focus),
.input-form textarea:hover:not(:disabled):not(:focus) {
    border-color: var(--color-additional-components-hover);
}

.input-form input:focus,
.input-form textarea:focus,
.input-form select:focus {
    border-color: var(--color-additional-components-pressed);
    box-shadow: 0 0 0 4px var(--component-shadow);
}

.input-form.error input:focus,
.input-form.error textarea:focus {
    border-color: var(--color-error-base);
}

.input-form input:disabled,
.input-form textarea:disabled {
    color: var(--color-text-icons-disabled);
    border-color: var(--color-op-additional-components-disable);
    cursor: not-allowed;
}

.input-form input::placeholder,
.input-form textarea::placeholder {
    color: var(--color-text-icons-placeholder);
}

/* ===========================================================================
 * 4. Surfaces that respond to interaction — task 1.1
 *
 * These already have an `.active` state driven by script.js. All that is added
 * is the hover and pressed steps of the same ladder, so the components feel
 * consistent rather than each inventing its own.
 * =========================================================================== */

.card-wrap,
.faqs-item,
.toggleable,
.tab-item,
.tab-button,
.toggleActive {
    transition:
        background-color var(--duration-instant) var(--easing-standard),
        border-color var(--duration-instant) var(--easing-standard);
}

.faqs-item button:hover,
.tab-button:hover:not(.active),
.tab-item:hover:not(.active) {
    background-color: var(--color-additional-components-primary);
}

/* ===========================================================================
 * 5. Elevation — task 1.1
 *
 * Nothing in the product had a box-shadow. Every floating layer sat flat on the
 * page behind a 1px border, which reads as dated more than any single colour
 * does, and makes it genuinely hard to tell what is on top of what.
 *
 * Additive only: these surfaces keep their existing borders, backgrounds and
 * radii. Elevation is layered on according to how far the surface floats —
 * a panel attached to its trigger gets less than a modal over the whole page.
 * =========================================================================== */

/* Attached to their trigger: dropdown panels, the inline new-card form. */
.dropdown .result,
.dropdown .hidden,
.double-dropdown .result,
.new-card,
.country-search .search-result,
.lang .showlang,
header.authorized .auth .drop {
    box-shadow: var(--elevation-md);
}

/* Detached, covering page content. */
.mob-menu,
.drop.log {
    box-shadow: var(--elevation-lg);
}

/* Modal, over everything. */
.popup .inner,
.popup-wrap .popup,
.instructions.popup .content {
    box-shadow: var(--elevation-xl);
}

/* Cards lift on hover. `.package-box` is the catalog card and the most-seen
 * surface in the product; `transform` is deliberately avoided so nothing
 * creates a containing block over a Stripe iframe (design.md §7.1). */
.package-box,
.card-wrap,
.blog-list .item,
.services-package .box {
    transition:
        box-shadow var(--duration-fast) var(--easing-standard),
        border-color var(--duration-instant) var(--easing-standard);
}

.package-box:hover,
.blog-list .item:hover,
.services-package .box:hover {
    box-shadow: var(--elevation-md);
}

.card-wrap:hover {
    box-shadow: var(--elevation-sm);
}

/* ===========================================================================
 * 6. Type scale — task 1.5
 *
 * reset.css ships sixteen fixed-pixel utilities, `.fz-8` through `.fz-34`,
 * stepping by 1-2px. They are used directly in the markup, so they cannot be
 * deleted; they are re-pointed at a fluid scale instead.
 *
 * Only sizes of 20px and up become fluid. Below that the design system's
 * desktop and mobile steps are the same value (16/16, 14/14, 12/12), so there
 * is nothing to interpolate between and a clamp would add motion for no reason.
 *
 * Each clamp interpolates from its mobile size at a 375px viewport to its
 * existing desktop size at 1366px — the top of the breakpoint scale. That means
 * **desktop rendering is unchanged**: at 1366px and above every clamp is pinned
 * to exactly the pixel value the utility had before. Only narrower viewports
 * see a smaller, proportional size, which is what these utilities needed
 * per-breakpoint overrides for previously.
 *
 * `.fz-12` is left alone: reset.css declares it `!important` and 12px is
 * already the floor of the scale.
 * =========================================================================== */

.fz-34 { font-size: clamp(26px, 22.97px + 0.807vw, 34px); }
.fz-32 { font-size: clamp(24px, 20.97px + 0.807vw, 32px); }
.fz-30 { font-size: clamp(24px, 21.73px + 0.605vw, 30px); }
.fz-28 { font-size: clamp(22px, 19.73px + 0.605vw, 28px); }
.fz-26 { font-size: clamp(20px, 17.73px + 0.605vw, 26px); }
.fz-24 { font-size: clamp(20px, 18.49px + 0.403vw, 24px); }
.fz-20 { font-size: clamp(18px, 17.24px + 0.202vw, 20px); }

/* Headings inherit the same treatment where a page has not given them a size.
 * Scoped to `main` so the dashboard, which is out of scope, is untouched.
 *
 * WRAPPED IN :where() ON PURPOSE — it contributes zero specificity, so any rule
 * a page sets for itself wins. Without it these were (0,1,2) and silently beat
 * the terms and privacy pages' own `h1 { font-size: 11pt }`: those are Word
 * exports where the clause headings are marked up as h1 but are meant to read as
 * bold body text, and all 35 of them jumped to 32px. A default must be
 * overridable by the thing it is a default FOR. */
:where(main h1:not([class*="fz-"])) { font-size: clamp(26px, 22.97px + 0.807vw, 34px); }
:where(main h2:not([class*="fz-"])) { font-size: clamp(22px, 19.73px + 0.605vw, 28px); }
:where(main h3:not([class*="fz-"])) { font-size: clamp(20px, 18.49px + 0.403vw, 24px); }

/* ===========================================================================
 * 7. Radius consistency — task 1.7.1
 *
 * Five scales were in play: 40px pills, 16px cards, 20px and 10px on the two
 * halves of a dropdown, 8px inputs, 5px `.br-5`. The dropdown was the worst of
 * it — a 40px pill trigger that squared off to 10px corners when opened, so the
 * control changed shape as you used it.
 *
 * Now that the panel has elevation (§5) it can be a detached surface, which is
 * both the modern pattern and simpler: the trigger stays a pill at all times
 * and the panel is its own rounded card floating below it.
 * =========================================================================== */

.dropdown.active .select {
    border-radius: var(--radius-maximum);
}

.dropdown .result,
.dropdown.active .result,
.dropdown .hidden,
.double-dropdown .result {
    border-radius: var(--radius-md);
    margin-top: var(--spacing-xxxxs);
    overflow: hidden;
}

.br-5 {
    border-radius: var(--radius-xs);
}

/* ===========================================================================
 * 8. Cards — task 1.7.2
 *
 * `.package-box` is the catalog card and the most-seen surface in the product.
 * It was a flat box with a 2px hairline that turned brand-red on hover, with
 * nothing to say it was a surface rather than an outline.
 *
 * It keeps its 16px radius (already `--radius-lg`), its 2px border and its
 * brand-on-hover treatment — the border width is deliberately unchanged, since
 * the card is a fixed 280px and the hover state swaps colour, not thickness, so
 * nothing reflows. What is added is resting elevation, so the card reads as
 * paper on the page.
 * =========================================================================== */

.package-box,
.blog-list .item,
.services-package .box {
    border-radius: var(--radius-lg);
    box-shadow: var(--elevation-sm);
}

/* The delivery-method chooser is three large `<button>` cards — the primary and
 * only decision on that step — and they carried a hairline and nothing else.
 * They play the same selectable-card role as `.package-box`, so they get the
 * same treatment. Semantics were already right: real buttons, focusable, with
 * `cursor: pointer`. */
.delivery_method {
    border-radius: var(--radius-lg);
    box-shadow: var(--elevation-sm);
    transition:
        box-shadow var(--duration-fast) var(--easing-standard),
        border-color var(--duration-instant) var(--easing-standard);
}

.delivery_method:hover {
    box-shadow: var(--elevation-md);
    border-color: var(--color-secondary-base);
}

.delivery_method.active {
    border-color: var(--color-secondary-base);
    box-shadow: var(--elevation-md);
}

/* An option the business has switched off. It stays visible — the warning text
 * is written by them and is worth reading — but it stops looking selectable, and
 * cannot be clicked into the dead end it used to lead to. */
.delivery_method.unavailable {
    background-color: var(--color-op-additional-components-disable);
    border-color: var(--color-divider-primary);
    box-shadow: none;
    cursor: not-allowed;
    pointer-events: none;
}

.delivery_method.unavailable .title,
.delivery_method.unavailable p {
    color: var(--color-text-icons-disabled);
}

.delivery_method.unavailable img {
    opacity: 0.4;
}

.delivery_method .unavailable-note {
    margin-top: var(--spacing-xxxxs);
    /* error-base is 4.61:1 on white, but this note sits on the card's disabled
     * tint, where it measured 4.45:1 — just under. The note is informational
     * text, not a disabled control, so 1.4.3 applies to it in full. */
    color: var(--color-error-dark) !important;
    font-size: var(--font-size-desktop-caption-1);
    line-height: var(--line-height-desktop-caption-1);
}

/* ===========================================================================
 * 9. Intrinsic image sizing — task 2.5.2
 *
 * Images carry `width`/`height` attributes so the browser can reserve space
 * before the file arrives. That fixes layout shift, but the attributes are also
 * PRESENTATIONAL width — and inside a flex row they become the flex base size,
 * so a 132x99 flag styled `height: 22px` rendered 95px wide and stretched
 * instead of 29px and correct.
 *
 * Restoring `width: auto` hands sizing back to CSS while the attributes still
 * supply the aspect ratio, so both properties hold at once.
 * =========================================================================== */

.country .flag,
.popup-wrap .popup .flag,
.country .arr {
    width: auto;
    flex-shrink: 0;
}

/* ===========================================================================
 * 10. Checkout summary fields — task 1.8.7
 *
 * The registration summary ("please check all the details") shows the customer's
 * own name, surname, email and phone in read-only inputs. `style.css` sets
 * `width: fit-content` on them, which for a replaced element resolves to the
 * intrinsic `size=20` box — a fixed 176px inside a 660px row. A normal email
 * overflowed it and was cut off mid-string, at exactly the step where the
 * customer is being asked to verify it.
 *
 * The view already declares `width: 100%` for the same selector in its own
 * inline block, but that block is emitted into <head> and loses to style.css.
 * This restores the intent from a sheet that loads last.
 * =========================================================================== */

.porting .pay .table .bottom ul li label .input.disabled {
    width: 100%;
    max-width: 340px;
}

/* ===========================================================================
 * 11. App-install prompt — task 1.8.2
 *
 * Scoped replacements for a <style> block the layout used to inject into <body>
 * at runtime. That block styled `.swal2-popup` and `.swal2-html-container`
 * globally and was never removed, so every SweetAlert shown afterwards on the
 * same page lost its background — including payment errors.
 *
 * The overlay's own hardcoded offsets (`top: -290px`, `bottom: 300px`) are left
 * alone deliberately: advert.png already carries the same wordmark and copy in
 * the artwork, so those offsets position around baked-in content. Re-laying it
 * out without the artwork to hand just renders the message twice.
 * =========================================================================== */

.app-install-popup {
    background: none;
    margin: 0;
    padding: 0;
    box-shadow: none;
}

.app-install-html {
    margin: 0 !important;
    overflow: hidden !important;
}

/* ===========================================================================
 * 12. Empty states — task 1.8.17
 *
 * `/cabinet/payment-options` already had a good one: it explains the state and
 * offers a single action. The listing pages did not — they rendered column
 * headers over blank space, so an empty account looked the same as a broken
 * page. These are the shared styles for the rows that fill that gap.
 * =========================================================================== */

.empty-state-row td,
.empty-state-row {
    padding: var(--spacing-lg) var(--spacing-xs);
}

/* Alignment and wrapping are set on the CHILDREN, not the cell. These tables
 * force `text-align: left` and `white-space: nowrap` on their cells, and the
 * paragraphs inherit both: the title sat flush left while the text block was
 * centred, and the sentence ran straight out of the card because it could not
 * break, overflowing the very max-width meant to contain it. */
.empty-state-title,
.empty-state-text {
    text-align: center;
    white-space: normal;
}

.empty-state-title {
    color: var(--color-text-icons-primary);
    font-size: var(--font-size-desktop-subtitle-4);
    line-height: var(--line-height-desktop-subtitle-4);
    margin-bottom: var(--spacing-xxxxs);
}

.empty-state-text {
    color: var(--color-text-icons-secondary);
    font-size: var(--font-size-desktop-body-3);
    line-height: var(--line-height-desktop-body-3);
    max-width: 46ch;
    margin: 0 auto;
}

/* The TicketWizz "no offers" state, which has its own class names. Same visual
 * language as the rows above: centred, icon in a soft disc, message demoted to
 * secondary. Only properties the view leaves undefined are set here. */
.no-offers-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--spacing-xxxxs);
}

.no-offers-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background-color: var(--color-additional-background-additional);
    color: var(--color-text-icons-secondary);
    margin-bottom: var(--spacing-xxxxs);
}

.no-offers-title {
    color: var(--color-text-icons-primary);
}

.no-offers-message {
    color: var(--color-text-icons-secondary);
    max-width: 46ch;
}

/* ===========================================================================
 * 12b. Cabinet chrome — task 2.1 (M1)
 *
 * Lifted out of the two cabinet layouts, which declared these inline and almost
 * identically. The footer rule was scoped only by where the <style> sat; the
 * :has() selector restores that scope explicitly so it cannot leak to other pages.
 * =========================================================================== */

.cabinet-checkout,
.cabinet .home {
    /* 60px kept exactly as it was — snapping this to the spacing scale is §1.7.6. */
    margin-bottom: 60px;
}

.breadcrumb-item.active {
    color: var(--color-secondary-base);
}

@media (max-width: 576px) {
    main:has(.container.cabinet) ~ footer {
        display: none;
    }
}

/* ===========================================================================
 * 12c. Voice-minutes list — task 3.2.2b
 *
 * `.head` is `justify-content: space-between` with no gap, so the expiry line and
 * the time-left line met exactly at the midpoint with nothing between them —
 * "Expiration Date 2026-09-01" ran straight into "Time left: 58 min".
 *
 * `.or` is a rule-either-side divider built for the word "or"; this list puts a
 * whole sentence in it, and as a flex container the text shrank ahead of the
 * rules and broke onto three lines.
 * =========================================================================== */

#minutes-wrapper .package-box .head {
    gap: var(--spacing-xxs);
}

#minutes-wrapper .or {
    white-space: nowrap;
}

/* ===========================================================================
 * 12d. Login feedback — task 3.2.2e
 *
 * The login script set this colour inline, with four different literals for two
 * meanings: #ff2020 / #2b8e00 on web and #fc6178 / #3cc500 on mobile. Measured
 * against white, all four fail WCAG AA for normal text — 3.83, 4.22, 2.95 and
 * 2.29 to 1 — and the mobile pair fails the large-text threshold too.
 *
 * --color-error-base is 5.95:1 and --color-success-text is 5.06:1.
 * =========================================================================== */

.error_container.is-error {
    color: var(--color-error-base);
}

.error_container.is-success {
    color: var(--color-success-text);
}

/* ===========================================================================
 * 12e. Package card tags — task 2.3 (M3)
 *
 * Lifted out of the three package components, each of which declared these inline
 * AFTER @endsection — so a 47-package listing emitted 94 copies. Two literals
 * came with it: #D9C179 is --color-warning-base and #24303F is
 * --color-additional-background-inverse.
 *
 * `font: 12px firago` + `font-weight: 600` is preserved as it was. BPG Arial is
 * declared at 400 only, so that 600 is a synthetic weight — the same class of
 * problem as §0b, but there is no BPG Arial bold file to point at.
 * =========================================================================== */

.tags {
    position: absolute;
    top: 58px;
    left: -20px;
    display: flex;
    justify-content: center;
    text-align: center;
    gap: 5px;
    padding: 5px;
    border-radius: 10px;
    background-color: var(--color-warning-base);
}

.tag {
    color: var(--color-additional-background-inverse);
    font: 12px firago;
    font-weight: 600;
}

/* ===========================================================================
 * 12f. SweetAlert2 dialogs — task 3.1.4
 *
 * The library ships its own visual language: a #7066E0 purple confirm button,
 * 5px corners, no elevation and #545454 text. None of it is ours, and a modal is
 * the loudest thing on the page.
 *
 * The button rules are scoped to `.swal2-styled` on purpose. SweetAlert2 adds
 * that class only when it is doing its own button styling, so every call that
 * already passes `buttonsStyling: false` and its own `customClass` keeps exactly
 * the buttons it asked for.
 * =========================================================================== */

.swal2-popup {
    border-radius: var(--radius-lg);
    box-shadow: var(--elevation-lg);
}

.swal2-title {
    color: var(--color-text-icons-primary);
    font-size: var(--font-size-desktop-subtitle-2);
    line-height: var(--line-height-desktop-subtitle-2);
    font-weight: 700;
}

/* Scoped through .swal2-popup because SweetAlert2 styles this one at (0,2,0);
 * a bare .swal2-html-container loses to it and silently does nothing. */
.swal2-popup .swal2-html-container {
    color: var(--color-text-icons-secondary);
    font-size: var(--font-size-desktop-body-2);
    line-height: var(--line-height-desktop-body-2);
}

.swal2-actions {
    gap: var(--spacing-xxxxs);
}

.swal2-confirm.swal2-styled,
.swal2-cancel.swal2-styled,
.swal2-deny.swal2-styled {
    min-height: var(--size-hit-target-min);
    padding: 0 var(--spacing-sm);
    border-radius: var(--radius-maximum);
    font-size: var(--font-size-desktop-body-3);
    font-weight: 700;
    box-shadow: none;
    transition: background-color var(--duration-instant) var(--easing-standard);
}

.swal2-confirm.swal2-styled {
    background-color: var(--color-secondary-base);
    color: var(--color-text-icons-white);
}

.swal2-confirm.swal2-styled:hover {
    background-color: var(--color-secondary-dark);
}

/* Cancel and deny read as the quiet option rather than a second solid button. */
.swal2-cancel.swal2-styled,
.swal2-deny.swal2-styled {
    background-color: transparent;
    color: var(--color-text-icons-primary);
    border: 1px solid var(--color-text-icons-secondary);
}

.swal2-cancel.swal2-styled:hover,
.swal2-deny.swal2-styled:hover {
    background-color: var(--color-additional-components-primary);
}

/* ===========================================================================
 * 12g. SweetAlert 1 shell — task 1.8.3
 *
 * v1 is still the dominant alert library: 98 calls to v2's 35, across 25 views.
 * Tokenising v2 alone (§3.1.4) therefore made the split MORE visible, not less —
 * a v2 dialog looked like the product and a v1 dialog looked like 2014, in a
 * different typeface.
 *
 * Same shell as §12f so the two are indistinguishable. The button COLOUR is not
 * set here: v1 writes it inline, and forcing it would flatten the greens and reds
 * that 18 call sites choose on purpose. sweetalert-defaults.js changes only the
 * library's own default.
 * =========================================================================== */

/* The vendor sheet paints the in-dialog error badge #EA7D7D, which is 2.72:1
 * against its own white "!" — under both the 4.5:1 for text and the 3:1 that
 * 1.4.11 asks of a meaningful graphic. This is the badge shown when a validation
 * error is raised inside a v1 dialog, so it is exactly the moment it has to read.
 * --color-error-base carries white at 5.90:1. */
.sweet-alert .sa-error-container .icon {
    background-color: var(--color-error-base);
}

.sweet-alert {
    /* v1 ships its own font stack and overrides the global `*` rule. */
    font-family: light, sans-serif;
    border-radius: var(--radius-lg);
    box-shadow: var(--elevation-lg);
}

.sweet-alert h2 {
    color: var(--color-text-icons-primary);
    font-size: var(--font-size-desktop-subtitle-2);
    line-height: var(--line-height-desktop-subtitle-2);
    font-weight: 700;
}

.sweet-alert p {
    color: var(--color-text-icons-secondary);
    font-size: var(--font-size-desktop-body-2);
    line-height: var(--line-height-desktop-body-2);
}

.sweet-alert .confirm,
.sweet-alert .cancel {
    min-height: var(--size-hit-target-min);
    padding: 0 var(--spacing-sm);
    border-radius: var(--radius-maximum);
    font-size: var(--font-size-desktop-body-3);
    font-weight: 700;
    /* v1 paints a glow in the button's own colour; the elevation scale covers
     * depth for the whole dialog and the glow just looks dated. */
    box-shadow: none !important;
}

.sweet-alert .cancel {
    background-color: transparent !important;
    color: var(--color-text-icons-primary);
    border: 1px solid var(--color-text-icons-secondary);
}

/* ===========================================================================
 * 12h. Skeleton loading — task 1.7.4
 *
 * For content that arrives over AJAX. A skeleton says how much is coming and
 * where it will sit; "Checking..." says neither, and a blank area is
 * indistinguishable from a failure.
 * =========================================================================== */

.skeleton {
    background-color: var(--color-additional-background-additional);
    background-image: linear-gradient(
        90deg,
        var(--color-additional-background-additional) 0%,
        var(--color-additional-components-primary) 40%,
        var(--color-additional-background-additional) 80%
    );
    background-size: 300% 100%;
    animation: mx-shimmer 1.4s ease-in-out infinite;
    border-radius: var(--radius-sm);
    /* Anything inside is a size hint, not something to read. */
    color: transparent;
    user-select: none;
    pointer-events: none;
}

/* Available to screen readers, invisible on screen. NOT called `.visually-hidden`:
 * the ticketwizz views already define that name as `display: none !important`,
 * which would hide this from assistive tech as well. */
.visually-hidden-text {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.skeleton-line {
    height: 14px;
    margin-bottom: var(--spacing-xxxxxs);
}

.skeleton-block {
    height: 64px;
    margin-bottom: var(--spacing-xxxxs);
}

@keyframes mx-shimmer {
    from { background-position: 150% 50%; }
    to   { background-position: -50% 50%; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
    }
}

/* ===========================================================================
 * 12i. Error pages — task 2.7
 *
 * The wrapper is a flex ROW, so the action added in §2.7 sat next to the message
 * rather than under it, and its top margin skewed it against align-items: center.
 * Stacking is also what lets these pages carry a message plus an action at all.
 * =========================================================================== */

main .main-page .errorWrapper {
    flex-direction: column;
    gap: var(--spacing-sm);
    text-align: center;
}

/* The message is markup'd as `<div class="border-btn">` — a brand-bordered pill
 * identical to a real button, so the page showed two controls and only one did
 * anything. Inside the error wrapper it reads as the heading it actually is. */
main .main-page .errorWrapper .border-btn {
    border: 0;
    padding: 0;
    border-radius: 0;
    height: auto;
    min-height: 0;
    color: var(--color-text-icons-primary);
    font-size: var(--font-size-desktop-subtitle-2);
    line-height: var(--line-height-desktop-subtitle-2);
    font-weight: 700;
    cursor: default;
}

/* ===========================================================================
 * 12j. Outlined buttons on the dark menu — task 2.2 (M2)
 *
 * .border-btn is brand-base red on both label and border. Fine on white (4.55:1),
 * but the mobile menu is navy, where the same red is 2.94:1 — failing the text
 * threshold and the 3:1 that 1.4.11 asks of a component boundary.
 * =========================================================================== */

.mob-menu .border-btn,
/* mobileCss/style.css repeats this rule as `.mob-menu .nav .border-btn` (0,3,0),
 * which outranked the two-class selector above — so the fix landed on the web
 * header but not the mobile one, where the button is nested in .nav. Matching
 * that specificity; components.css loads after mobileCss, so this wins. */
.mob-menu .nav .border-btn {
    color: var(--color-secondary-light);
    border-color: var(--color-secondary-light);
}

/* ===========================================================================
 * 12k. Subscription status pills — task 2.4 (M4)
 *
 * These lived in inline <style> blocks duplicated across four views —
 * includes/header, mobile/includes/header, front/cabinet/cabinet and
 * mobile/cabinet/cabinet. The header copy shipped on EVERY page.
 *
 * Five of the six variants failed 1.4.3 at 14px, where 4.5:1 is required:
 *
 *     Active / .number   #4EE335 on #F5FFF8   1.66:1
 *     Suspended          #e33535 on #FCE5E5   3.62:1
 *     Terminated         #e33535 on #FCE5E5   3.62:1
 *     Deactivated        #e33535 on #E4FF00   3.85:1
 *     Pending            #0c0c0ce0 on #FFE662 12.11:1   <- the one that passed
 *
 * #4EE335 is the unmapped "second green"; #50C878 (--color-success-base) is no
 * better at 2.08:1. Both are decorative greens that cannot carry text. The
 * washes are the recognisable part of these pills, so they are kept exactly and
 * only the ink is darkened onto the existing role tokens:
 *
 *     --color-success-text  #2F7D48  ->  4.95:1 on the green wash
 *     --color-error-dark    #9A2435  ->  6.53:1 on the red wash, 6.95:1 on yellow
 * =========================================================================== */

.number,
.cabinet .home .status {
    border-radius: var(--radius-maximum);
    padding: 4px 8px;
    font-size: 14px;
    font-family: light;
    font-weight: bold;
}

.number {
    border: 1px solid var(--color-success-border);
    margin-left: 10px;
}

.number.Active,
.cabinet .home .status.Active {
    background: var(--color-additional-background-success-wash);
    color: var(--color-success-text);
}

.number.Pending,
.cabinet .home .status.Pending {
    background: var(--color-status-pending-fill);
    color: var(--color-text-icons-primary);
}

.number.Suspended,
.number.Terminated,
.cabinet .home .status.Suspended,
.cabinet .home .status.Terminated {
    background: var(--color-additional-background-error-wash);
    color: var(--color-error-dark);
}

.number.Deactivated,
.cabinet .home .status.Deactivated {
    background: var(--color-status-deactivated-fill);
    color: var(--color-error-dark);
}

/* The bare .number base was green-on-wash too — same 1.66:1. */
.number {
    background: var(--color-additional-background-success-wash);
    color: var(--color-success-text);
}

/* ===========================================================================
 * 12l. eSIM activation details — task 2.4 (M4)
 *
 * Replaces two inline style attributes on .addit-list in the mobile cabinet.
 * =========================================================================== */

.addit-list--qr {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    margin-bottom: 20px;
}

.addit-list--centered {
    text-align: center;
    margin-bottom: 20px;
}

/* ===========================================================================
 * 12m. Warning alert — task 2.4 (M4)
 *
 * .alert had danger/success variants but no warning one, so the eSIM fallback
 * added in §12l would have rendered as a bare bordered box. 5.21:1.
 * =========================================================================== */

.alert-warning {
    background-color: var(--color-additional-background-warning-wash);
    border-color: var(--color-warning-border);
    color: var(--color-warning-text);
    max-width: 500px;
    margin: 10px;
}

/* ===========================================================================
 * 12n. Cabinet section headings — task 2.4c
 *
 * The section titles were <div class="title">. As <h2> they keep every declared
 * property from .title (18px / lightb / uppercase / flex / margin-bottom) and
 * pick up only one thing .title never set: the UA's top margin. Reset it, scoped
 * so no other .title on the site is touched.
 * =========================================================================== */

.cabinet .home h2.title {
    margin-top: 0;
    /* the heading reset sets line-height: 1; the div inherited 1.2 (21.6px at
     * 18px), so without this every section title tightens by 3.6px */
    line-height: inherit;
}

.title--wrap {
    overflow-wrap: anywhere;
}

/* ===========================================================================
 * 12o. Wide tables and breadcrumb hit areas — task 2.4d
 *
 * The my-packages table is 365px of content in a 332px column, which cascaded
 * up to MAIN and gave the page a 4px horizontal scroll at 390px.
 *
 * The breadcrumb link is 38x13 — under the 24x24 that WCAG 2.5.8 asks of a
 * target. It is a navigation control, not a link inside a sentence, so the
 * inline exception does not apply. Padding grows the hit area to 25px and the
 * matching negative margin keeps the trail sitting exactly where it did.
 * =========================================================================== */

.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.breadcrumb-item a {
    padding-block: 6px;
    margin-block: -6px;
}

/* ===========================================================================
 * 12p. Sub-section headings — task 2.4d
 *
 * settings went h1 -> h4 with nothing between. The h4 was unclassed, so it took
 * the UA's 1em; a bare h2 would take 1.5em and grow. These are the h4's measured
 * values, so the element changes level without changing appearance.
 * =========================================================================== */

.subsection-title {
    font-size: 16px;
    font-weight: 400;
    line-height: 16px;
    margin: 0;
}

/* ===========================================================================
 * 12q. Language switcher — task 2.6 (M6)
 *
 * Six languages in a `flex-wrap: nowrap` row with a 24px gap need 413px
 * (293px of text + 5 gaps) but get 390px, so flexbox shrank every item 3-5px
 * below its own text width — each language name overflowed its box and the row
 * still pushed the document 3px wide. Wrapping is inert wherever the row already
 * fits, and it keeps working as languages are added.
 * =========================================================================== */

.lang {
    flex-wrap: wrap;
}

/* ===========================================================================
 * 12r. Registration summary — task 2.6 (M6)
 *
 * --color-secondary-base is 4.55:1 on pure white, so it fails the moment it sits
 * on anything tinted: 4.38:1 measured here for both the eSIM-compatibility
 * consent label and the "Go back" button. --color-secondary-dark is the same
 * brand hue one step down and holds 5.55-5.95:1 across white, #FAFAFA and
 * #F7F7F7. Bold does not help — 14px only earns the 3:1 threshold at 18.66px+.
 *
 * select2 ships its placeholder at #999 (2.85:1). Note that the design system's
 * own --color-text-icons-placeholder is 2.53:1, so it cannot be used for
 * placeholder TEXT either; secondary ink is the readable choice at 6.97:1.
 * =========================================================================== */

.borderless-btn {
    color: var(--color-secondary-dark);
}

.select2-container--default .select2-selection--single .select2-selection__placeholder,
.select2-selection__placeholder {
    color: var(--color-text-icons-secondary);
}

/* ===========================================================================
 * 12s. Plan-your-trip block — spacing
 *
 * 559x568 for a heading, a paragraph and three fields. Measured, the height was
 * mostly stacked padding rather than content:
 *
 *   80px above the title   .plan-trip pads 40px, then .title adds 40px on top
 *   24px under the form    .package-wrap .container pads 24px inside a block
 *                          that already pads 40px below
 *   14px label -> field    .drop-wrap sets gap:10px AND the label sets
 *                          margin-bottom:4px, so both apply
 *   20px between fields    x3, wrapped into a column at this width
 *
 * Everything below lands on the 4-based scale in tokens.css. Nothing is
 * restructured — the block keeps its markup and its flex behaviour.
 * =========================================================================== */

.plan-trip {
    padding-block: var(--spacing-md);
}

.plan-trip .title {
    /* .package-wrap .title supplies a top margin the padding already provides */
    margin-top: 0;
    margin-bottom: var(--spacing-xxxs);
}

.plan-trip .text {
    /* margins collapse here: the gap under the title is whichever of the two is
       larger, and .text was contributing 20px against the title's 12px. */
    margin-top: 0;
    margin-bottom: var(--spacing-xxs);
}

.plan-trip .container {
    padding-bottom: 0;
}

/* Below 576px the winner is `.main-page .package-wrap .container` with
 * `padding-bottom: 24px !important` — a rule meant for the package cards, which
 * the plan-trip form happens to sit inside. Matching its weight is the only way
 * past it; scoped to .plan-trip so the cards keep their padding. */
.main-page .plan-trip .container#switch {
    padding-bottom: 0 !important;
}

.plan-trip .drop-wrap {
    gap: var(--spacing-xxxxs);
    margin-bottom: var(--spacing-xxxs);
}

/* the gap above already separates the label from its field */
.plan-trip .drop-wrap > label {
    margin-bottom: 0;
}

/* ===========================================================================
 * 12t. Text on the dark cards, and the success chip — task 2.8b
 *
 * `.cabinet .text` is a generic rule. At HEAD it was #9E9E9E, which measures
 * 4.99:1 on the navy `.box.active` card — poor, but legible. The §0.7 grey
 * collapse moved it to --color-text-icons-secondary, i.e. dark ink, and on that
 * same navy it drops to **1.89:1**. `.box.active` already declares
 * `color: var(--color-text-icons-white)`, so the fix is to stop overriding it:
 * inherit gives 13.38:1 and stays correct if the card's colour ever changes.
 *
 * The payment-history chip is white on --color-success-base, which is 2.13:1 —
 * the same green that could not carry text in §2.4, here as a background instead
 * of a foreground. --color-success-text takes it to 5.06:1.
 * =========================================================================== */

/* NOT scoped to .cabinet: the public /faq page renders the same cards outside it,
   where the text measured the same 1.23:1. */
.faqs .box.active .text,
.faqs .box.active .title,
.faqs .box.active p,
.cabinet .service-bundles .box.active .text,
.cabinet .service-bundles .box.active .sm-title {
    color: inherit;
}

.label.label-success {
    background-color: var(--color-success-text);
}

/* ===========================================================================
 * 12u. Carousel dots — task 2.15
 *
 * The dot IS the button: 9x9 (22x9 when active), painted with its own
 * background. Measured, one carousel's dots sat 19px apart centre to centre —
 * under 24, so they failed 2.5.8 on size AND on the spacing exception that would
 * otherwise excuse the size.
 *
 * Padding grows the target. `box-sizing: content-box` keeps the declared 9px as
 * the CONTENT box rather than the border box, and `background-clip: content-box`
 * keeps the paint inside it — so the visible dot is unchanged and only the
 * clickable area grows. The extra width also pushes the centres past 24px.
 * =========================================================================== */

/* owl.carousel.min.css sets `padding: 0 !important` on these and
 * `background-clip: initial` at (0,3,1), so a class-only rule loses on both.
 * Matching its selector shape — components.css loads after it, so equal
 * specificity resolves here. */
.owl-carousel .owl-dots button.owl-dot {
    box-sizing: content-box;
    padding: 8px 3px !important;
    background-clip: content-box;
}

/* ===========================================================================
 * 13. Copy-to-clipboard — task 1.8.8
 *
 * For the eSIM manual-install pair (SM-DP+ address and activation code). The
 * value stays selectable text; the button sits beside it rather than replacing
 * it, so nothing is hidden from someone reading the codes off the screen.
 * =========================================================================== */

.copyable {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xxxxs);
    flex-wrap: wrap;
    justify-content: center;
}

.copyable-value {
    word-break: break-all;
}

.copy-value {
    padding: 2px var(--spacing-xxxs);
    font-size: var(--font-size-desktop-caption-1);
    line-height: var(--line-height-desktop-caption-1);
    min-height: var(--size-hit-target-min);
    min-width: var(--size-hit-target-min);
    flex-shrink: 0;
}

.copy-value.is-copied {
    border-color: var(--color-success-base);
    color: var(--color-success-base);
}

/* ===========================================================================
 * 14. Package ordinal — task 1.8.15
 *
 * Secondary to the package name, not competing with it: quieter colour, smaller
 * step, and a separating dot so it reads as a qualifier rather than part of the
 * title.
 * =========================================================================== */

.package-ordinal {
    color: var(--color-text-icons-secondary);
    font-size: var(--font-size-desktop-caption-1);
    line-height: var(--line-height-desktop-caption-1);
    white-space: nowrap;
}

.package-ordinal::before {
    content: "·";
    margin: 0 var(--spacing-xxxxxs);
}

/* ===========================================================================
 * 15. Motion — task 1.4
 *
 * A reduced-motion request is honoured across the board, including the owl
 * carousels and the mobile loading spinner. Transitions collapse rather than
 * disappear so that state changes are still perceivable.
 * =========================================================================== */

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    .owl-carousel .owl-item {
        transition: none !important;
    }

    /* The mobile loader spins forever; leave it visible but still. */
    .spinner {
        animation: none !important;
    }
}

/* ===========================================================================
 * 16. Registration stepper overflow — task 2.14
 *
 * `.porting-header .steps` is `display:flex; gap:40px` over five steps, each a
 * 50px circle plus its label. Between the 992px breakpoint and roughly 1100px
 * the five do not fit: the row overflows its container, which pushed
 * `document.scrollWidth` past `clientWidth` and clipped the last label — the
 * fifth step read "Checkou" with the "t" cut off.
 *
 * Measured on `join-us/customer-details` at 1013px: scrollWidth 1017 vs
 * clientWidth 1013. The 4px escaped every element-level check, because no
 * single box extended past the viewport — the overflow was the FLEX ROW's
 * content width, which `getBoundingClientRect()` on the children does not show.
 *
 * The gap becomes fluid instead of fixed. At 1333px and above `3vw` exceeds
 * 40px and clamps there, so every width the design was drawn at is byte-for-byte
 * unchanged; only the range that was already broken compresses. `min-width: 0`
 * lets a flex item shrink below its content width, which is what allows the
 * labels to reflow rather than force the row wider.
 *
 * `min-width: 0` is unscoped on purpose. It was briefly wrapped in a min-width
 * query one pixel above the 576 breakpoint, which the breakpoint gate correctly
 * rejected — that value is not on the {576, 768, 992, 1200, 1366} scale, and
 * inventing a neighbouring breakpoint to dodge an overlap is how a scale rots.
 * It needs no guard: at narrow widths the row already fits, so allowing an item
 * to shrink below its content width changes nothing there.
 *
 * (Note for anyone adding a comment here: the gate greps this file for media
 * queries, so do not write a literal off-scale one even inside a comment.)
 * =========================================================================== */

.porting .porting-header .steps {
    gap: clamp(16px, 3vw, 40px);
}

.porting .porting-header .steps li {
    min-width: 0;
}


/* ===========================================================================
 * 17. `.main-title` line-height — task 1.8.4
 *
 * `instructions/compatible` rendered its page title as a `<div class="main-title">`
 * and so had NO heading of any level — measured: zero h1-h6 in the rendered page.
 * It is now an `<h1>`, which is the whole point.
 *
 * That promotion is not free. reset.css sets `h1,h2,...{line-height:1}` while a
 * `<div>` picks up the global `*{line-height:1.2}`, so the same text would have
 * gone from 38.4px to 32px of line box. `.main-title` never set its own
 * line-height, so it had nothing to defend itself with.
 *
 * Pinning it here keeps the promotion visually inert, and does nothing to the
 * other `.main-title` elements — they are divs already computing to 1.2.
 * =========================================================================== */

.main-title {
    line-height: 1.2;
}

/* ===========================================================================
 * 18. `.copyable` inherited a border it should never have had — task 1.8.8
 *
 * `style.css` carries `.bundle-detail .addit-list li span { border: 2px solid
 * black }`, written when each list item held exactly ONE span: the value. §1.8.8
 * added a `.copyable` WRAPPER around that value plus a button, so two spans now
 * match the blanket rule and the customer saw a black box nested inside a black
 * box, with the red button pill inside that. Three borders, two of them pure
 * `black` — a colour that is not in the palette at all.
 *
 * This is a regression introduced by this change, not a pre-existing defect: the
 * wrapper is a layout container and has no business drawing anything.
 *
 * Only the wrapper is neutralised. The VALUE keeps its 2px border, because that
 * is what the single span looked like before §1.8.8 and the point of the change
 * was to add a copy button, not to restyle the field.
 *
 * Specificity: the blanket rule is (0,2,2); this is (0,3,2), so it wins on merit
 * rather than on load order.
 * =========================================================================== */

.bundle-detail .addit-list li span.copyable {
    border: 0;
    padding: 0;
    border-radius: 0;
}

/* ===========================================================================
 * 19. Empty-state row broke below 992px — task 1.8.17
 *
 * `style.css` turns the history rows into a CSS grid on narrow screens:
 *
 *     @media (max-width: 991px) {
 *         .payment-history tbody tr { display: grid; grid-template-columns: 1fr 1fr 1fr }
 *     }
 *
 * **`colspan` does nothing to a grid item.** §1.8.17's empty state is one
 * `<td colspan="3">`, which is honoured while the table is a real table and
 * ignored the moment it becomes a grid — the cell lands in track 1 only, about
 * 30px wide at 375px, and "You have no bundle" stacked one word per line.
 *
 * `grid-column: 1 / -1` is the grid equivalent of the colspan. It is applied
 * unscoped on purpose: above 991px the row is a normal `table-row`, the cell is
 * a `table-cell`, and `grid-column` is simply ignored there. Adding a media
 * query to mirror the one in style.css would just be a second place to keep in
 * sync.
 *
 * Two more inherited properties have to be undone, both from
 * `.cabinet table td { white-space: nowrap; width: 25% }`:
 *
 *   * `nowrap` is right for a data cell holding one value and wrong for a
 *     sentence — it is what made the words stack rather than wrap;
 *   * `width: 25%` pins the cell to a quarter of the table whatever the grid
 *     says.
 *
 * That rule is (0,1,2), so the first version of this block — `.empty-state-row >
 * td`, (0,1,1) — lost to it and only `grid-column` took effect. The selector
 * below is (0,2,3) and wins on specificity rather than on load order.
 * =========================================================================== */

.cabinet table tr.empty-state-row > td {
    grid-column: 1 / -1;
    width: auto;
    white-space: normal;
    text-align: center;
}

/* ===========================================================================
 * 20. Home carousel arrows were positioned against the viewport — task 2.2c
 *
 * `custom.css` places them with `position: absolute; top: 0; bottom: 0;` and
 * `left: -5%` / `right: -5%`, meaning "just outside the slider". But nothing in
 * their ancestor chain is positioned, so the containing block was the **initial
 * containing block** — the viewport — and -5% resolved against 1280px instead of
 * the slider's 632px.
 *
 * Measured at 1280 before this rule:
 *
 *   .owl-prev   left -54  right  -14   entirely off-screen left
 *   .owl-next   left 1304 right 1344   entirely off-screen right, 64px of
 *                                      horizontal page scroll
 *   both        height 900             the viewport's height, not the slider's
 *
 * So both arrows were invisible and unusable, and the next one dragged a
 * scrollbar onto the home page. Nobody designs arrows you cannot see; the -5%
 * was written expecting a positioned parent that was never there.
 *
 * After: prev 42-86, next 681-725, height 624 — on screen, inside the page, at
 * the slider's own height. Document overflow 0.
 *
 * `.blog-slider` appears in exactly one view (`front/home/index`), so this is as
 * narrow as it looks. Below 576px `custom.css` hides both arrows, and this rule
 * changes nothing there.
 *
 * NOT caused by this change: verified by disabling §1.2's
 * `min-width/min-height: 44px` on the arrows at runtime — the overflow stayed at
 * exactly 64px, because -5% fixes the RIGHT edge and the tap target only moves
 * the left one.
 *
 * Only visible once the carousel has real images. The slides are CMS uploads and
 * `public/uploads` does not exist locally, so until placeholders were generated
 * this page measured clean — see §0.10.
 * =========================================================================== */

.blog-slider {
    position: relative;
}

/* ===========================================================================
 * 21. Delivery-step validation message — task 1.8.5
 *
 * The delivery forms have no native validation, and `required` is the wrong tool
 * there: all three panels hide their fields in a way that breaks the browser's own
 * message — select2 clips its select to 1×1, and the sim_data inputs sit on the
 * back face of a flip card. The browser blocks the submit and anchors its
 * explanation somewhere the user cannot see.
 *
 * So the message is rendered in the page instead, immediately above the submit
 * button — a control the user is by definition looking at, since they just clicked
 * it. `--color-error-base` is the palette's error role, which by Inex's own rule
 * equals `secondary-dark`, so it reads as an error without being mistaken for a
 * call to action.
 * =========================================================================== */

.delivery-validation {
    margin: var(--spacing-xxs) 0;
    color: var(--color-error-base);
    font-size: var(--font-size-desktop-caption-1);
    line-height: var(--line-height-desktop-caption-1);
    text-align: center;
}

.delivery-validation:empty {
    display: none;
}

.delivery-validation:focus-visible {
    outline: 2px solid var(--color-secondary-base);
    outline-offset: 2px;
}
