@import '../settings/tw';

/*
 * The gap was 55px between rows and 40px between columns, which is wider than
 * the cards' own padding by a factor of nearly three: the grid read as three
 * separate columns of unrelated boxes rather than one set. Matching the gap to
 * the card padding is what makes a grid read as a group.
 */
.mhub-grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    margin: 0 auto;
    padding: 0;

    @media (max-width: 1024px) {
      grid-template-columns: repeat(2, 1fr);
    }

    @media (max-width: 768px) {
      grid-template-columns: 1fr;
    }
}

/*
 * Lifted, not outlined. Every card carried a 1px hairline as well as a shadow,
 * and on a page holding thirteen of them that is thirteen rectangles drawn in
 * ink around content that a shadow already separates from the #F4F7FF ground.
 * Dropping the border takes a whole layer of line work off the screen and
 * changes nothing about where one card ends and the next begins.
 *
 * There is no `height` here either. The rule used to set `height: 90%` with a
 * comment claiming it equalised the cards; grid items stretch to the row height
 * on their own, so all the 90% did was shrink each card to nine tenths of the
 * height it already had and leave a ragged strip of ground under the short ones.
 */
.mhub-card {
    background: white;
    border-radius: 16px;
    padding: 20px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    border: 0;
    box-shadow: 0 1px 2px rgba(17, 24, 39, 0.04), 0 4px 12px -6px rgba(17, 24, 39, 0.08);

    .shortcode-copy {
        cursor: pointer;
        margin-left: 10px;
        
        i {
            color: #085df2;
        }
    }
}

.mhub-ct-wrapper{
    display: flex;
    align-items: center;
    gap: 12px;
}

/*
 * A 60px circle around a 30px logo is half air, and at that size the tile
 * competed with the card title for first read. 48px in a rounded square puts
 * the logo back in charge and matches the 38px glyph tiles in the status groups
 * below closely enough that the two halves of the page look related.
 */
.mhub-icon {
    width: 48px;
    height: 48px;
    flex: none;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /*
     * Frontend Meetings sets its own tile colour inline per feature, which wins
     * over this. Integrations has no per-card colour to set, and without a tile
     * the logos float in the corner while every status card below sits in one:
     * the sort of small inconsistency that makes two halves of one page look
     * like two different products.
     */
    background: #F1F4FA;

    svg,
    img {
      width: 30px;
      height: 30px;
    }

    i::before {
        font-size: 28px;
        width: 28px;
        height: 28px;
        line-height: 28px;
        color: #324eb5;
    }
}

/*
 * Now an h3 on the integration cards, so the margin has to be stated: both the
 * browser and wp-admin give a heading their own, and inheriting either would
 * push the title off the icon it sits beside.
 */
.mhub-title {
    margin: 0;
    padding: 0;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: #101724;
}

/*
 * `min-height: 60px` was padding every card out to the height of the longest
 * description in the set. Grid rows already equalise card heights, so all it
 * did was open a gap under the short ones.
 */
.mhub-description {
    margin: 0;
    color: #5C6779;
    font-size: 13.5px;
    line-height: 1.55;
}

/*
 * No rule above the footer. The card's own edge already closes the description,
 * and `margin-top: auto` puts the control on the floor of every card in the row
 * at the same height, which is the alignment the line was standing in for.
 */
.mhub-toggle-container {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-top: 4px;
    margin-top: auto;
}

.mhub-toggle-text {
    color: #5C6779;
    font-size: 13.5px;
}

/*
 * Shown to free users in place of the toggle.
 *
 * This was a filled blue button, and there are five of them on the page under
 * the free plugin, plus the filled blue "Upgrade to Pro" in the banner at the
 * foot. Six primaries is no primary: everything shouts, the eye picks a
 * starting point at random, and the one button we actually want clicked has no
 * more weight than the five repeats above it. Tinted, they still read as
 * buttons and the banner keeps the only filled control on the screen.
 *
 * Colour is re-declared on every state on purpose: WordPress admin ships
 * `a:hover, a:focus, a:active` rules at specificity (0,1,1) that outrank a
 * single class at (0,1,0), which is how a label on a tinted control silently
 * turns admin-blue on hover.
 */
.mhub-upgrade-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 36px;
    /* One notch off the left, so the lock does not push the pair right of centre. */
    padding: 8px 16px 8px 14px;
    border: 0;
    border-radius: 10px;
    background: #EAF0FE;
    /* 7.0:1 on the tint. */
    color: #0A4CC4;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    transition: background-color 0.18s ease, color 0.18s ease;

    &:hover {
        background: #DCE7FD;
        color: #08409E;
    }

    &:focus-visible {
        outline: 2px solid #085DF2;
        outline-offset: 2px;
        background: #DCE7FD;
        color: #08409E;
    }

    &:active {
        background: #CEDCFB;
        color: #08409E;
    }

    /*
     * #5C6779 on #EEF1F6 measures 5.05:1. The previous #7C8698 measured
     * 3.24:1 and failed AA for text: disabled has to read as unavailable,
     * not as unreadable.
     */
    &:disabled {
        background: #EEF1F6;
        color: #5C6779;
        cursor: not-allowed;
    }
}

@media (prefers-reduced-motion: reduce) {
    .mhub-upgrade-btn {
        transition: none;
    }
}

/*
 * The toggle is reachable by keyboard (role="switch", tabIndex 0), so it needs
 * a visible focus ring like any other control.
 */
.mhub-toggle:focus-visible {
    outline: 2px solid #085DF2;
    outline-offset: 2px;
}

/*
 * Groups on the Integrations screen.
 *
 * The description under each heading is the point: buyers scan headings to find
 * themselves, and the outcome line is what turns a row of logos into a
 * statement that the plugin was built for their kind of business.
 */
/*
 * Shared page header. Type scale copied from All Meetings on purpose: that
 * screen set the house style and the rest of the plugin was still opening with
 * a bare h1, which made the screens read as separate products.
 */
.mhub-page-header {
    margin-bottom: 24px;
}

/*
 * Sidebar plus content on the Integrations screen.
 *
 * The screen is five groups deep and every one of them was always rendered, so
 * finding the marketplaces meant scrolling past eight cards you had already
 * decided about. The sidebar turns that into one click, and - the part that
 * matters more - it lists what the plugin supports in one short block you can
 * read without scrolling at all.
 */
.mhub-layout {
    display: grid;
    /*
     * 236px, not the 212px this held while the rail was only a list of category
     * names. The search field moved in above them, and a 212px rail left the
     * placeholder about 137px of room once the icon and the clear button had
     * taken their padding - enough to clip "Search integrations".
     */
    grid-template-columns: 236px minmax(0, 1fr);
    gap: 28px;
    align-items: start;
}

/*
 * The filter rail: the search field and the category list, which do one job
 * between them.
 *
 * Sticky here rather than on the list, because the whole point is narrowing the
 * set without hunting for either control again after scrolling.
 *
 * 52px, not 32. wp-admin's admin bar is 32px and fixed, so a 32px offset parks
 * the rail exactly against its underside: measured, the clearance was 0px, the
 * search field's top edge read as clipped by the black bar, and the field's 3px
 * focus ring had nowhere to paint. 32 for the bar plus a 20px gap is what the
 * rest of this page's spacing uses.
 */
.mhub-rail {
    position: sticky;
    top: calc(var(--wp-admin--admin-bar--height, 32px) + 20px);
    display: grid;
    /*
     * `minmax(0, 1fr)`, not the default `auto`. Below 960px the category list
     * becomes a sideways-scrolling row of chips, and an auto track sizes to that
     * row's max-content - so the rail grew past the viewport and took the page
     * into horizontal scroll. `.mhub-layout` guards its own tracks the same way.
     */
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
}

.mhub-cats__heading {
    margin: 0 0 10px;
    padding: 0 10px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    /* 5.0:1 on the page ground. #6B7585 measured 4.35 there and failed AA. */
    color: #5C6779;
}

.mhub-cats__list {
    margin: 0;
    padding: 0;
    list-style: none;

    li {
        margin: 0 0 2px;
    }
}

/*
 * A full-width button, not a link: nothing navigates, and a link that does not
 * go anywhere is the wrong promise to make to a keyboard or a screen reader.
 *
 * Colour is re-declared on every state. WordPress admin styles `a` and buttons
 * inside `#wpbody` with its own scheme rules, several of them at specificity
 * (0,1,1), so a single class stating the resting colour once loses the moment a
 * pointer arrives.
 */
.mhub-cats__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    min-height: 38px;
    padding: 8px 10px;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: #3C4657;
    font-size: 14px;
    /*
     * Regular at rest, so that weight is free to mean one thing here: this is
     * the category you are looking at. Every row carried 500, which spends the
     * loudest signal in the rail on all six equally and leaves the selected one
     * marked by tint alone.
     */
    font-weight: 400;
    line-height: 1.3;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.16s ease, color 0.16s ease;

    &:hover {
        background: #EDF1F8;
        color: #101724;
    }

    &:focus-visible {
        outline: 2px solid #085DF2;
        outline-offset: 1px;
        color: #101724;
    }

    &:active {
        background: #E2E9F4;
        color: #101724;
    }

    /*
     * A category the current search has emptied. Kept in place and greyed
     * rather than removed: a list that reshuffles while you type loses you the
     * map of what is supported at the moment you are reading it.
     */
    /*
     * #5C6779 on white measures 5.72:1. The previous #9AA3B2 measured 2.54:1,
     * which left the disabled category label effectively unreadable.
     */
    &:disabled {
        background: transparent;
        color: #5C6779;
        cursor: not-allowed;
    }

    /*
     * Tinted, not filled. A saturated blue block is the heaviest thing on a
     * page whose entire content is white cards on near-white, so the eye went
     * to the filter before the thing being filtered - and the filter is
     * navigation, not the point of the screen. The tint marks the current place
     * without out-shouting the content, which is how a sidebar should behave.
     */
    &.is-selected {
        background: #E7EFFE;
        /* 7.0:1 on the tint. */
        color: #0A4CC4;
        font-weight: 600;

        &:hover,
        &:focus-visible,
        &:active {
            background: #DBE7FD;
            color: #08409E;
        }
    }
}

.mhub-cats__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * The count is the sidebar's second job: it says how much is behind each name
 * before you spend a click finding out.
 *
 * A bare number, not a chip. Six pills stacked down the rail added six more
 * shapes to a page already made of rounded rectangles, to carry one digit each.
 * Setting the number lighter than the label is enough to say it is secondary,
 * which is the only thing the pill was doing.
 */
.mhub-cats__count {
    flex: none;
    /* 5.0:1 on the #F4F7FF page ground. A count is still text. */
    color: #5C6779;
    font-size: 12.5px;
    /*
     * Follows the row rather than stating a weight of its own, so the selected
     * row reads as one bold line instead of a bold name beside a lighter number.
     * Size and colour are what mark the count as secondary.
     */
    font-weight: inherit;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
}

.mhub-cats__item:disabled .mhub-cats__count {
    color: #9AA3B2;
}

/* 4.9:1 on the selected row tint. */
.mhub-cats__item.is-selected .mhub-cats__count {
    color: #2E62CE;
}

@media (prefers-reduced-motion: reduce) {
    .mhub-cats__item {
        transition: none;
    }
}

/*
 * Below the point where a 212px rail leaves the cards too narrow, the sidebar
 * becomes a row of chips above the content. It scrolls sideways rather than
 * wrapping to three lines and pushing the thing it filters off the screen.
 */
@media (max-width: 960px) {
    .mhub-layout {
        grid-template-columns: minmax(0, 1fr);
        gap: 18px;
    }

    .mhub-rail {
        position: static;
    }

    .mhub-cats__heading {
        display: none;
    }

    .mhub-cats__list {
        display: flex;
        gap: 8px;
        overflow-x: auto;
        /* Room for the focus ring, which would otherwise be clipped. */
        padding: 3px;
        margin: -3px;
        scrollbar-width: thin;

        li {
            margin: 0;
            flex: none;
        }
    }

    .mhub-cats__item {
        border: 1px solid #E3E8F0;
        background: #fff;

        &.is-selected {
            border-color: #085DF2;
        }
    }
}

.mhub-page-header__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

/*
 * `min-width: 0`, without which the description below cannot shrink.
 *
 * A flex item's floor is its content, and `.mhub-page-header__desc` is
 * `white-space: nowrap`, so its content has no wrapping point: the text block
 * refused to go narrower than the sentence and pushed the page wider than the
 * viewport. Measured on a 360px screen, the document scrolled to 390px and the
 * whole admin slid sideways - on every screen built on this header, not only
 * the one it was found on. With the floor removed the ellipsis this rule always
 * intended actually engages.
 */
.mhub-page-header__text {
    min-width: 0;
}

/*
 * The page title, on All Meetings' scale.
 *
 * This was a flat 26px/700 while All Meetings ran 22px rising to 25px on a wide
 * screen, so the five screens built on `PageHeader` read a size larger than the
 * one screen that had been through a type pass. The note on
 * `.mhub-am-header__title` is the reason to move this one rather than that one:
 * 26px "read a size larger than the sibling product at every width".
 *
 * 600, not 700. At this size on white, 700 stops reading as emphasis and starts
 * reading as a heavier typeface - the same conclusion the empty state reached
 * about its own title.
 *
 * #0f172a measures 16.66:1 on the page ground.
 */
.mhub-page-header__title {
    margin: 0;
    padding: 0;
    font-size: 22px;
    line-height: 1.15;
    font-weight: 600;
    letter-spacing: -0.02em;
    color: #0f172a;
}

@media (min-width: 1536px) {
    .mhub-page-header__title {
        font-size: 25px;
    }
}

/*
 * One line, truncated, matching the header on All Meetings.
 *
 * A description that wraps stops being a description, the title stops being the
 * biggest thing in its own block, and the header's height changes from screen to
 * screen - which is what makes headers look like different components on
 * different pages. The longest of the five descriptions is 66 characters, so
 * the 92ch cap truncates nothing in practice; it is there to stop a future one
 * running away.
 *
 * #475569 rather than the #5C6779 this used to carry: both clear AA on the page
 * ground (7.07:1 and 5.34:1), but the stronger one is the token the rest of the
 * plugin uses, and All Meetings already moved to it.
 */
.mhub-page-header__desc {
    margin: 6px 0 0;
    max-width: 92ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13.5px;
    line-height: 1.6;
    color: #475569;
}

.mhub-page-header__actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/*
 * Filters the cards in place rather than navigating. The page is five groups
 * deep once every builder and marketplace is listed, which is past the point
 * where scanning beats searching.
 */
/*
 * Resting state is a filled field with no hard border, which is what stops a
 * search box from reading as a form input you are required to complete. The
 * border only arrives on focus, along with the ground turning white, so the
 * field lifts off the page at the moment it becomes the thing you are using.
 */
/*
 * Every rule below is scoped under `.mhub-search` to win a specificity fight
 * with WordPress core, not for tidiness. Measured in the browser: core's
 * load-styles.php ships `input[type="search"]` at (0,1,1) and
 * `input[type="search"]:focus` at (0,2,1), which beat a bare
 * `.mhub-search__input` at (0,1,0) on every single property. Before this scope
 * the field rendered as a stock admin input - white, grey 1px border, 2px
 * radius - and none of the design below was visible on screen.
 *
 * `.mhub-search .mhub-search__input` is (0,2,0), which beats (0,1,1) on the
 * class column; at focus it is (0,3,0) against core's (0,2,1). Do not flatten
 * these selectors back out.
 */
.mhub-search {
    position: relative;
    display: flex;
    align-items: center;
    /*
     * Fills the rail. It was a fixed 280px while it sat in the header's action
     * corner, where nothing constrained it; in a 236px column that width would
     * simply overflow.
     */
    width: 100%;
}

.mhub-search .mhub-search__icon {
    position: absolute;
    left: 13px;

    /*
     * 3.27:1 on the field's #EEF2F8 ground. #8A93A5 measured 2.75:1, which is
     * under the 3:1 an icon owes, and the magnifier is the only thing marking
     * this control as a search rather than a text box.
     */
    color: #7C8698;
    pointer-events: none;
    transition: color 0.18s ease;
}

.mhub-search .mhub-search__input {
    width: 100%;
    min-height: 40px;
    /* Right padding leaves room for the clear button. */
    padding: 9px 38px 9px 37px;
    border: 1px solid transparent;
    border-radius: 9px;
    background: #EEF2F8;
    color: #101724;
    font-size: 14px;
    line-height: 1.3;
    transition: background-color 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;

    &::placeholder {
        /*
         * Measured, not estimated. #6B7585 on this field's #EEF2F8 ground is
         * 4.15:1, not the 4.6:1 this comment used to claim, and AA asks 4.5:1
         * of placeholder text like any other. #475569 is 6.9:1 here, and on a
         * tinted ground it reads as quiet as the white-ground placeholders
         * elsewhere do.
         */
        color: #475569;
    }

    &:hover {
        background: #E7EDF6;
    }

    /*
     * Both `:focus` and `:focus-visible`: WordPress admin styles inputs on
     * plain `:focus` with its own blue box-shadow, so leaving `:focus` alone
     * means the admin's ring paints instead of this one.
     *
     * The ring is a 3px solid-alpha shadow, not a blurred glow. A glow at this
     * size reads as roughly a 1px edge to anybody tabbing through, which is the
     * person the ring exists for.
     */
    &:focus,
    &:focus-visible {
        background: #fff;
        border-color: #085DF2;
        outline: none;
        box-shadow: 0 0 0 3px rgba(8, 93, 242, 0.18);
    }

    /* Our own clear button is rendered instead. */
    &::-webkit-search-cancel-button,
    &::-webkit-search-decoration {
        appearance: none;
        display: none;
    }
}

/*
 * `:has()`, not a sibling selector. The icon is rendered before the input so it
 * can be positioned against the field without a wrapper, and `+` only reaches
 * forwards, so `input:focus + icon` would never match anything here.
 *
 * `.is-filled` is set from React whenever the field has a value, so the icon
 * stays brand-coloured after focus moves away while a filter is still applied.
 * It also covers any browser without `:has()`: the icon simply stays grey on
 * focus there, which costs nothing that matters.
 */
.mhub-search:has( .mhub-search__input:focus ) .mhub-search__icon,
.mhub-search.is-filled .mhub-search__icon {
    color: #085DF2;
}

.mhub-search .mhub-search__clear {
    position: absolute;
    right: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: #6B7585;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;

    &:hover {
        background: #DCE3EE;
        color: #101724;
    }

    &:focus-visible {
        outline: 2px solid #085DF2;
        outline-offset: 1px;
        color: #101724;
    }

    &:active {
        background: #CBD5E5;
        color: #101724;
    }
}

/*
 * The icon sits before the input in the DOM so the sibling selector above can
 * reach it on focus, but it must not come before the field in the tab order or
 * to a screen reader. It is aria-hidden and not focusable, so neither happens.
 */
@media (prefers-reduced-motion: reduce) {
    .mhub-search .mhub-search__input,
    .mhub-search .mhub-search__icon,
    .mhub-search .mhub-search__clear {
        transition: none;
    }
}

.mhub-search__empty {
    margin: 0;
    padding: 28px 0;
    color: #5C6779;
    font-size: 15px;
}

/*
 * The invitation that closes the screen, wearing the same card as the
 * integrations above it.
 *
 * Dashed rather than solid, so it reads as an opening in the set rather than a
 * fourteenth product: a solid white card in this slot gets inspected like an
 * integration before the reader works out it is a question.
 *
 * It is also the only card allowed to be the second loudest thing on the page.
 * Everything else went quiet in the minimal pass so the Pro banner could hold
 * the single filled control; this one earns a filled button back, because a
 * question nobody notices collects nothing. It still sits well under the gold
 * banner in weight, so the order of the two asks is never in doubt.
 */
.mhub-request-row {
    /*
     * The same 20px that separates any two cards. This card is meant to read as
     * the next slot in the set - that is the whole reason it stopped being a
     * full-width band - and it sits in its own grid, so the gap the grid would
     * have given it has to be spelled out here.
     */
    margin-top: 20px;
}

/*
 * Near-black, not blue.
 *
 * The page already spends blue on thirteen upgrade buttons and a search field,
 * so a blue card here asked to be noticed in the one colour the reader has
 * already learned to skim past. Black is the only strong colour the screen does
 * not otherwise use: it separates by hue rather than by volume, which is what
 * keeps the gold Pro banner below it the heaviest thing on the page.
 */
.mhub-request {
    position: relative;
    border: 1.5px dashed #D3D8E2;
    /*
     * A bloom in the top-left corner rather than a flat tint. It puts the
     * card's brightest point directly behind the mark, which is where the eye
     * enters a card anyway, and fades out before it reaches the text.
     */
    background:
        radial-gradient(120% 95% at 14% -10%, #EDEFF4 0%, rgba(237, 239, 244, 0) 62%),
        linear-gradient(180deg, #FDFDFE 0%, #F4F5F8 100%);
    /* A wider, softer, neutral shadow: lifted, not louder. */
    box-shadow: 0 2px 4px rgba(17, 24, 39, 0.03), 0 12px 28px -16px rgba(17, 24, 39, 0.35);
    /* The dashed edge eats 1.5px a side that the solid cards do not. */
    padding: 18.5px;
    transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

/*
 * The whole card is the click target, not just the button.
 *
 * The action's `::after` is stretched over the card, which is honest here in a
 * way it would not be on the integration cards: those carry a switch and a
 * state and clicking the body should do nothing, while this one has exactly one
 * thing you can do with it. It also means the hover below is telling the truth
 * rather than decorating a dead box, and it turns a 266px button into a 305px
 * card as a target.
 */
.mhub-request__action::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

.mhub-request:hover {
    border-color: #AEB6C6;
    box-shadow: 0 4px 8px rgba(17, 24, 39, 0.05), 0 18px 38px -18px rgba(17, 24, 39, 0.5);
    transform: translateY(-2px);
    cursor: pointer;
}

/*
 * The ring belongs on the card, because the card is what a click lands on. The
 * anchor keeps its own ring as the fallback for browsers without `:has()`.
 */
/*
 * The ring stays admin blue even though the card went black. Every other focus
 * ring on the screen is #085DF2, and a focus ring is a system affordance: a
 * keyboard user learns one colour for "you are here", and it should not change
 * per component.
 */
.mhub-request:has( .mhub-request__action:focus-visible ) {
    outline: 2px solid #085DF2;
    outline-offset: 3px;
    border-color: #AEB6C6;
}

/*
 * With the card ringed, the anchor's own ring is a second box inside the first.
 * Dropped only where `:has()` actually works, so a browser without it still
 * shows the button ring rather than nothing at all.
 */
@supports selector(:has(*)) {
    /*
     * Scoped through the card on purpose. This block sits above the anchor's
     * own rules in the file, so at equal specificity the later `outline` would
     * win and both rings would paint.
     */
    .mhub-request .mhub-request__action:focus-visible {
        outline: none;
    }
}

/*
 * The one tile on the page that is not a vendor logo, so it is the one that can
 * carry colour. Filled rather than tinted: at 48px a gradient chip reads as the
 * card's subject, which is the whole job of the mark here.
 */
.mhub-request__icon {
    background: linear-gradient(140deg, #2C3444 0%, #111827 100%);
    /* 17.74:1 on the tile. */
    color: #fff;
    box-shadow: 0 6px 14px -6px rgba(17, 24, 39, 0.55);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.mhub-request:hover .mhub-request__icon {
    transform: translateY(-1px);
    box-shadow: 0 8px 18px -6px rgba(17, 24, 39, 0.65);
}

/*
 * Full width, because it is the only thing in this card's footer. The other
 * cards put a state beside their control and need the space between them.
 *
 * Colour is re-declared on every state. WordPress admin ships
 * `a:hover, a:focus, a:active { color: <scheme blue> }` at specificity (0,1,1),
 * which outranks this single class at (0,1,0), so without these the label
 * changes colour on hover on its own.
 */
.mhub-request__action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    /*
     * Anchors here inherit content-box, unlike the `button` elements beside
     * them, so `min-height` was adding the 18px of padding on top of itself and
     * the control measured 58px against a 40px minimum.
     */
    box-sizing: border-box;
    min-height: 42px;
    padding: 9px 16px;
    border: 0;
    border-radius: 11px;
    /* Two stops, not one: a flat fill at this size reads as a form submit. */
    background: linear-gradient(140deg, #1D2635 0%, #111827 100%);
    /* 17.74:1 on the fill. */
    color: #fff;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
    box-shadow: 0 6px 14px -8px rgba(17, 24, 39, 0.75);
    transition: background-color 0.18s ease, box-shadow 0.18s ease, color 0.18s ease;

    /*
     * The card's own hover drives this too, since the whole card is the target
     * and a pointer anywhere on it is about to press this button.
     */
    &:hover {
        background: linear-gradient(140deg, #111827 0%, #05080F 100%);
        color: #fff;
        box-shadow: 0 8px 18px -8px rgba(17, 24, 39, 0.85);
        text-decoration: none;
    }

    .mhub-request:hover & {
        background: linear-gradient(140deg, #111827 0%, #05080F 100%);
        color: #fff;
        box-shadow: 0 8px 18px -8px rgba(17, 24, 39, 0.85);
    }

    &:focus,
    &:focus-visible {
        outline: 2px solid #085DF2;
        outline-offset: 2px;
        background: #111827;
        color: #fff;
        box-shadow: none;
    }

    &:active {
        background: #05080F;
        color: #fff;
    }
}

/* The footer holds one full-width control, so it needs no row layout. */
.mhub-request .mhub-toggle-container {
    display: block;
}

/* Nudges out on hover: the arrow is the only motion cue the link has. */
.mhub-request__arrow {
    transition: transform 0.18s ease;
}

/* Hovering anywhere on the card moves it, because anywhere on the card clicks. */
.mhub-request:hover .mhub-request__arrow,
.mhub-request__action:focus-visible .mhub-request__arrow {
    transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
    .mhub-request,
    .mhub-request__icon,
    .mhub-request__action,
    .mhub-request__arrow {
        transition: none;
    }

    .mhub-request:hover,
    .mhub-request:hover .mhub-request__icon {
        transform: none;
    }

    .mhub-request:hover .mhub-request__arrow,
    .mhub-request__action:focus-visible .mhub-request__arrow {
        transform: none;
    }
}

.mhub-page-intro {
    margin: 0 0 28px;
    color: #5C6779;
    font-size: 15px;
}

.mhub-group {
    margin-bottom: 44px;

    /*
     * Nothing here, because the request card always follows the last group and
     * owns the distance to it. Two boxes both contributing to one gap is how it
     * came to be 8px: this rule said 8, `.mhub-request-row` said 4, the margins
     * collapsed to the larger, and the request card ended up sitting less than
     * half a card-gap below the card above it.
     */
    &:last-of-type {
        margin-bottom: 0;
    }
}

/*
 * No rule under the heading. A full-width hairline over every group drew four
 * more lines across the page to separate things that 44px of space already
 * separates. Space is the cheaper separator and it leaves nothing to look at.
 */
.mhub-group__head {
    margin-bottom: 16px;
}

.mhub-group__title {
    margin: 0 0 3px;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: #101724;
}

.mhub-group__desc {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.5;
    /* 5.0:1 on the page ground. #6B7585 measured 4.35 there and failed AA. */
    color: #5C6779;
}

/*
 * Shown where a card's host plugin is missing, in place of the switch.
 *
 * The switch used to be rendered here regardless, and flipping it saved a flag
 * the loader then ignored because `is_plugin_active()` was false: the control
 * reported success and did nothing.
 */
.mhub-install-link {
    display: inline-flex;
    align-items: center;
    /* Anchors inherit content-box here; without this the padding stacks on top
     * of `min-height` and the link stands taller than the buttons beside it. */
    box-sizing: border-box;
    min-height: 36px;
    padding: 8px 14px;
    border: 0;
    border-radius: 10px;
    /* Same tinted treatment as the upgrade button: both are secondary here. */
    background: #EAF0FE;
    color: #0A4CC4;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
    transition: background-color 0.18s ease, color 0.18s ease;

    /*
     * Colour re-declared on every state. WordPress admin ships
     * `a:hover, a:focus, a:active { color: <scheme blue> }` at specificity
     * (0,1,1), which outranks this single class at (0,1,0), so without these
     * the label changes colour on hover on its own.
     */
    &:hover {
        background: #DCE7FD;
        color: #08409E;
        text-decoration: none;
    }

    &:focus,
    &:focus-visible {
        outline: 2px solid #085DF2;
        outline-offset: 2px;
        background: #DCE7FD;
        color: #08409E;
        box-shadow: none;
    }

    &:active {
        background: #CEDCFB;
        color: #08409E;
    }
}

@media (prefers-reduced-motion: reduce) {
    .mhub-install-link {
        transition: none;
    }
}

/*
 * The state reads as a pill rather than bare text. It used to be the last thing
 * in a table-like row, where the shared column edge did the separating; inside
 * a card of its own there is no such edge, so without a shape it reads as a
 * second label someone forgot to style.
 *
 * The dot is kept, and so is the word. The whole point of this group is a
 * yes-or-no answer, and a reader who cannot separate the green from the grey
 * has to get that answer from somewhere.
 */
.mhub-state {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px 4px 8px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;

    /*
     * `align-items: center` centres the label's line box, not its letters: the
     * font reserves room above the caps, so the word sits low inside a box that
     * is itself perfectly centred and the dot beside it reads high. Measured at
     * 0.5px above the label's cap-height centre, and 0.5px is the nudge that
     * takes it to zero. A whole pixel would overshoot to 0.5px the other way,
     * and a round dot has no strokes to knock off the pixel grid.
     */
    &::before {
        content: "";
        position: relative;
        top: 0.5px;
        width: 7px;
        height: 7px;
        border-radius: 50%;
        flex: none;
    }

    /* 5.46:1 on white, 5.04:1 on its own ground. */
    &--on {
        color: #0E7757;
        background: #E6F4EE;

        &::before {
            background: #0E7757;
        }
    }

    /*
     * Was #8A93A5, which measured 3.09:1 on white and failed WCAG AA for text.
     * #5C6779 measures 5.63:1 on white and 5.20:1 here. The dot stays lighter
     * than the label on purpose: it is decoration beside the word, not the
     * thing carrying the meaning, so the 3:1 non-text threshold applies to it.
     */
    &--off {
        color: #5C6779;
        background: #F0F3F8;

        &::before {
            background: #AAB3C2;
        }
    }
}
  
/*
 * The knob carries a shadow and the track eases rather than fading linearly.
 * Both are what make a switch feel like an object that moved rather than two
 * colours that swapped, and it is the only moving part on the screen.
 */
.mhub-toggle {
    width: 44px;
    height: 26px;
    flex: none;
    background: #D8DEE9;
    border-radius: 999px;
    position: relative;
    cursor: pointer;
    transition: background-color 0.22s cubic-bezier(0.32, 0.72, 0, 1);

    /*
     * The hit target, which the painted track is too short to be.
     *
     * The switch reads at 44x26, so it met the 44px minimum on one axis only
     * and was 18px short on the other. `::after` is already the knob, so the
     * area is bought here instead. It grows vertically and not horizontally on
     * purpose: the row is `space-between` with its label to the left, and a
     * sideways expansion would put an invisible target over that text.
     */
    &::before {
        content: '';
        position: absolute;
        inset: -9px 0;
    }

    &::after {
      content: '';
      position: absolute;
      width: 22px;
      height: 22px;
      background: #fff;
      border-radius: 50%;
      top: 2px;
      left: 2px;
      box-shadow: 0 1px 2px rgba(17, 24, 39, 0.18), 0 2px 6px -2px rgba(17, 24, 39, 0.2);
      transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
    }

    &.active {
        background-color: #085df2;

        &::after {
            transform: translateX(18px);
        }
    }
}

@media (prefers-reduced-motion: reduce) {
    .mhub-toggle,
    .mhub-toggle::after {
        transition: none;
    }
}

/*
 * Frontend Meetings has no rules here any more.
 *
 * That screen is built on Tailwind utilities (assets/src/scss/tailwind.scss,
 * scoped behind `.mhub-tw`), so the ~800 lines of `.mhub-fm__*` that used to sit
 * at the foot of this file are gone rather than left to rot. It still imports
 * this stylesheet for `.mhub-toggle`, which stays hand-written: the switch is
 * shared with other screens and is the one control in the plugin whose easing
 * is tuned by hand.
 */
