/* AI Cloud component plugin: Navigation Shell */

/* ===== Sidebar ===== */
#app-sidebar,#app-sidebar *{
    transition: none!important;
}
.sidebar{
    position: fixed;
    top: 0;
    left: 0;
    /* rail + lap: the extra width runs UNDER .content, which starts at --mp-rail and paints
       over it by DOM order — see the z-index note below. The lap is added to the RIGHT
       padding too, so the nav
       column keeps its pre-overlap width and the space either side of it stays equal. */
    width: calc(var(--mp-rail) + var(--mp-pane-lap));
    height: 100vh;
    padding: 20.5px calc(var(--mp-sb-pad) + var(--mp-pane-lap)) 20.5px var(--mp-sb-pad);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* auto, not a number: see foundation's .content. The pane covers the rail by DOM order,
       so neither needs a level — and a level here would force one there, which turns the
       pane into a stacking context and buries every modal under the app bar. The mobile
       drawer is unaffected: it states its own z-index:990 inside the @media block, where it
       does have to cover the content. */
    z-index: auto;
    /* NO backdrop-filter ON THE RAIL, and it moved rather than went — see the drawer rule
       inside @media(max-width:1000px), which is where it does something.

       As a RAIL this element is body's first child and <html> is flat by construction
       (foundation's `background:var(--bg)`, deliberately flat since the gradient moved to
       .content). Blurring a uniform backdrop returns the uniform backdrop. Measured at
       1440 and 1024, dpr 2: removing the blur moved at most 1 of 255 in any channel, on
       under 1.2% of pixels; the control that tinted the rail moved 250 of 255 on 99.8%.

       It was not free, though. backdrop-filter makes an element sample everything painted
       behind it, so a window resize — which cannot change this element's geometry at all,
       fixed at a fixed width and 100vh — still forced a full render-surface readback and a
       22px gaussian every frame. Paint flashing on the installation showed each nav row
       lighting up on a drag that could not have moved any of them.

       AS A DRAWER IT IS A DIFFERENT ELEMENT. Below 1000px this becomes a full-viewport
       overlay at z-index 990 sitting ON TOP of .content, so its backdrop is the pane
       gradient and the blur is load-bearing. That is why the declaration is restated there
       rather than overridden back on from here: no !important, no breakpoint arithmetic,
       and the declaration lives where it earns its keep. */
    background: var(--mp-sidebar);
}

.sidebar-head{
    position:relative;
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:10px;
    margin-left:-2px;
    margin-bottom:32px;
}

.sidebar-head .brand{
    flex:1 1 auto;
    min-width:0;
    margin-bottom:0;
}

.sidebar-toggle{
    position:relative;
    width:28px;
    height:28px;
    min-width:28px;
    display:flex;
    align-items:center;
    justify-content:center;
    flex-shrink:0;
    margin:0;
    padding:0;
    border:1px solid var(--mp-fill-07);
    border-radius:5px;
    background:var(--mp-fill-035);
    box-shadow:none;
    color:var(--mp-ink-muted);
    cursor:pointer;
    opacity:.52;
    transform:none;
    transition:.18s ease;
}

.sidebar-toggle:hover{
    opacity:1;
    color:var(--highlight);
    background:var(--mp-fill-07);
    border-color:var(--mp-hl-20);
}

.sidebar-toggle svg{
    width:14px;
    height:14px;
    fill:none;
    stroke:currentColor;
    stroke-width:2.4;
    stroke-linecap:round;
    stroke-linejoin:round;
    transition:.22s ease;
}


/* ===== Brand ===== */
.brand{
    --mp-brand-ring-size: 30px;
    --mp-brand-planner-gradient: linear-gradient(135deg,var(--highlight) 0%,#38bdf8 56%,var(--highlight) 100%);
    display: flex;
    align-items: center;
    gap: 10px;
    padding-left: 7px;
    color: var(--mp-ink-strong);
    font-size:var(--mp-fs-3xl,24px);
    letter-spacing: -1px;
    text-decoration: none;
}

.brand-mark{
    position:relative;
    width:var(--mp-brand-ring-size);
    height:var(--mp-brand-ring-size);
    flex:0 0 var(--mp-brand-ring-size);
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border-radius:50%;
    filter:drop-shadow(0 0 12px rgba(0,234,94,.25));
}

.brand-mark:before{
    content:'';
    position:absolute;
    inset:0;
    border-radius:50%;
    pointer-events:none;
    background:conic-gradient(from 0deg,#062932 0deg,#072731 38deg,#073234 47deg,#07543e 59deg,#058f4f 76deg,#00d062 97deg,#00de66 106deg,#00dd65 118deg,#02d86e 128deg,#2ab6b4 167deg,#36a9cf 182deg,#36a5d3 187deg,#3197c1 197deg,#2d8cb3 217deg,#267798 234deg,#0c3740 272deg,#082e34 279deg,#062932 360deg);
    -webkit-mask:radial-gradient(farthest-side,transparent calc(100% - var(--mp-brand-ring-size) * .143 - 1px),#000 calc(100% - var(--mp-brand-ring-size) * .143));
    mask:radial-gradient(farthest-side,transparent calc(100% - var(--mp-brand-ring-size) * .143 - 1px),#000 calc(100% - var(--mp-brand-ring-size) * .143));
    transform:rotate(0deg);
}

.brand-mark:after{display:none!important}
.brand-mark.is-animating:before{animation:mpBrandLogoIntro 1.05s cubic-bezier(.2,.8,.2,1) both}

.brand-text{
    display:flex;
    align-items:baseline;
    min-width:0;
    line-height:var(--mp-lh-none,1);
}

.brand-text strong{
    color:transparent;
    font-size:0;
    letter-spacing:var(--mp-ls-tighter,-0.03em);
    white-space:nowrap;
}

.brand-text strong:before,
.brand-text strong:after{
    font-size:var(--mp-fs-xl,18px);
    font-weight:800;
    letter-spacing:var(--mp-ls-tighter,-0.03em);
}

.brand-text strong:before{
    content:'Marketing';
    color:var(--mp-ink-strong);
}

.brand-text strong:after{
    content:'Planner';
    color:var(--highlight);
    background:var(--mp-brand-planner-gradient);
    background-clip:text;
    -webkit-background-clip:text;
    -webkit-text-fill-color:transparent;
    text-shadow:0 0 18px var(--mp-hl-12);
}

.brand-text span{display:none!important}

@keyframes mpBrandLogoIntro{
    0%{transform:rotate(0deg);opacity:.78;filter:brightness(.94)}
    68%{transform:rotate(380deg);opacity:1;filter:brightness(1.12)}
    100%{transform:rotate(360deg);opacity:1;filter:brightness(1)}
}

/* ===== Nav ===== */
.sidebar nav{
    display:flex;
    flex-direction:column;
    gap:8px;
}

.sidebar nav a{
    display: flex;
    align-items: center;
    gap: 13px;
    min-height: 40px;
    padding: 8px 8px;
    overflow: hidden;
    border: 1px solid transparent;
    border-radius: 5px;
    color: var(--mp-ink-nav);
    font-size:var(--mp-fs-md,14px);
    font-weight:var(--mp-fw-semibold,600);
    letter-spacing:var(--mp-ls-tight,-0.01em);
    transition: color .18s ease,background .18s ease,border-color .18s ease,opacity .2s ease;

}

.sidebar nav a:hover,
.sidebar nav a.active{
    color:var(--mp-ink-invert);
    background:linear-gradient(135deg,var(--green-soft),var(--mp-fill-04));
    border-color:var(--mp-hl-20);
}

/* GEOMETRY ONLY, AND THE COLOUR THAT USED TO BE HERE IS THE REASON WHY.

   mp_nav_icon() emits this wrapper wherever a nav glyph is wanted, not only in the rail. A
   colour declared on the bare class therefore lands on the CHILD of every host that renders
   one, and a colour on the child beats a colour inherited from the parent whatever the source
   order — so a host could not tint its own glyph, only discover that it had not.

   Measured on v1.12.491: the Dashboard's Projects and Open-tasks KPI tiles rendered
   --mp-ink-muted inside a .kpi-icon that declares --mp-ink-brand, while the two tiles calling
   mp_icon() took the brand ink correctly. Nothing in the tree chose that split; it was whichever
   helper each tile happened to call. The shortcut row had already met the same wall and worked
   around it with a local color:inherit, so a second owner existed for a rule the first owner
   should not have had.

   The rail's resting tone is stated below, scoped to .sidebar. */
.nav-icon{
    width:22px;
    height:22px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    flex-shrink:0;
    transition:color .18s ease;
}

/* The rail's resting tone. Hover and active override it below at higher specificity, and the
   drawer below 1000px is this same element, so one selector covers both shapes. */
.sidebar .nav-icon{
    color:var(--mp-ink-muted);
}

.nav-icon svg{
    width:21px;
    height:21px;
    fill:none;
    stroke:currentColor;
    stroke-width:2;
    stroke-linecap:round;
    stroke-linejoin:round;
}

.nav-label{
    display:inline-block;
    max-width:170px;
    overflow:hidden;
    white-space:nowrap;
    text-overflow:ellipsis;
    opacity:1;
    transform:translateX(0);
    transition:opacity .18s ease .14s,transform .22s cubic-bezier(.2,.8,.2,1) .14s,max-width .22s ease .08s;
}

.sidebar nav a:hover .nav-icon,
.sidebar nav a.active .nav-icon{color:var(--highlight)}

/* ===== Sidebar user / footer ===== */
.user{
    display:flex;
    align-items:center;
    gap:12px;
    margin-top:auto;
    padding:10px;
    /*border:1px solid var(--mp-fill-08);*/
    border-radius:5px;
    /*background:linear-gradient(135deg,var(--mp-fill-045),var(--mp-fill-018));*/
}

.user > .user-main,
.user > .user-avatar,
.user:before,
.user a:after,
.user-main,
.user-meta,
.user-role,
.user-pill{
    display:none!important;
    content:none!important;
}

.user-footer-inline{
    display:flex;
    align-items:center;
    justify-content:flex-start;
    gap:12px;
    width:100%;
}

.user-footer-inline .user-avatar,
.user:not(:has(.user-footer-inline)) > .user-avatar{
    display:flex!important;
    align-items:center;
    justify-content:center;
    width:56px;
    height:56px;
    min-width:56px;
    max-width:56px;
    min-height:56px;
    max-height:56px;
    flex:0 0 56px;
    padding:0;
    margin:0;
    border-radius:5px;
    font-size:var(--mp-fs-xl,18px);
    font-weight:var(--mp-fw-heavy,800);
    line-height:var(--mp-lh-none,1);
    text-decoration:none;
    color:#06111d;
    border-color:transparent;
    background:linear-gradient(135deg,#00ea5e,#38bdf8);
    box-shadow:0 0 22px rgba(0,234,94,.14), inset 0 1px 0 rgba(255,255,255,.30);
}



.user-footer-inline .user-logout,
.user-footer-inline .logout-btn,
.user-footer-inline .btn-logout,
.user:not(:has(.user-footer-inline)) > .user-logout{
    display:flex;
    align-items:center;
    justify-content:center;
    gap:10px;
    height:56px;
    min-height:56px;
    max-height:56px;
    flex:1 1 auto;
    width:auto;
    padding:0 18px;
    margin:0;
    border-radius:5px;
}

.user-logout{
    display:flex!important;
    align-items:center;
    justify-content:space-between;
    padding:11px 4px 2px;
    border-top:1px solid var(--mp-fill-065);
    border-radius:5px;
    color:var(--mp-ink-muted);
    font-size:var(--mp-fs-sm,12px);
    font-weight:var(--mp-fw-heavy,800);
    letter-spacing:var(--mp-ls-wider,0.08em);
    text-transform:uppercase;
}

.user-logout:hover{
    color:var(--mp-ink-danger)!important;
    background:var(--mp-danger-08);
}

.user-logout span:first-child{
    font-size:var(--mp-fs-xs,11px);
    letter-spacing:var(--mp-ls-wider,0.08em);
}

.logout-icon{
    color:var(--mp-danger-ink);
    font-size:var(--mp-fs-base,13px);
    line-height:var(--mp-lh-none,1);
}

/* ===== Collapsed sidebar =====
   Single source of truth: this geometry is shared by the resolved state
   (body.sidebar-collapsed, applied by JS) AND the pre-paint state core stamps on
   <html> before JS runs (html.sidebar-collapsed-preload). Grouping both selectors
   means they cannot drift apart -- drift between the two was what made the rail
   flash a wider layout and a stray toggle chevron on every route/module load. */
html.sidebar-collapsed-preload,
body.sidebar-collapsed{
    /* THE RAIL IS DERIVED, NOT CHOSEN, and the difference is a pixel somebody could see.
       This block used to restate --mp-sb-pad as 10px beside a round 70px rail. The content
       box that produced was 50px, the 40px button centred inside it, and the button's left
       edge landed on 15 — a number written in no token, arrived at as (70 - 40) / 2 + 10.
       Expanded, that same edge is --mp-sb-pad itself: 14. So every nav glyph and the logo
       mark stepped one pixel right on collapse, while each state stayed perfectly symmetric
       about its own rail — which is why the symmetry assertion in app_shell_pane_test.js
       passed on both sides of it.

       Stating the relation instead: rail = padding + button + padding. The content box is
       then EXACTLY the button width, so `align-items:center` on the collapsed nav has
       nothing left to centre and alignment stops depending on two numbers agreeing by
       accident. --mp-sb-pad is now one value for the whole shell, in both states.

       COST, stated because it is real: the visible rail is 68px rather than 70, so the pane
       and the app bar start two pixels further left when collapsed. The alternative kept the
       70 by moving --mp-sb-pad to 15 everywhere, which aligns just as exactly and shifts the
       EXPANDED column instead — the reference rather than the deviation.

       AND WHY 68 IS A LITERAL RATHER THAN THE calc() THAT SAYS THE SAME THING. It was a
       calc for one build. getPropertyValue on a custom property returns its SPECIFIED
       value, so --mp-rail stopped reading as a length and started reading as
       'calc(14px*2 + 40px)' — correct in the layout, unparseable to anything that treats
       the token as a number. The first such consumer broke immediately: three assertions
       in app_shell_pane_test.js that compare the pane, the bar and the rail element
       against it. A published token changing TYPE is a wider blast radius than the drift
       the calc was preventing, so the arithmetic is asserted in that suite instead —
       rail == --mp-sb-pad * 2 + --mp-nav-btn, read out of this file. A comment cannot go
       stale quietly when a test is holding the same claim. */
    --mp-nav-btn:40px;
    --mp-rail:68px;
}

/* The collapsed rail used to restate its own width, its own padding AND a matching
   margin-left on .content — three numbers that had already drifted (266 against 268). It
   states the tokens now; sidebar, app bar and pane all derive from them. */

/* Login renders without a rail; keep its content flush during preload. */
html.slug-login.sidebar-collapsed-preload .content{margin-left:0px!important}

html.sidebar-collapsed-preload .sidebar-head,
body.sidebar-collapsed .sidebar-head{
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    margin-bottom: 22px;
}

html.sidebar-collapsed-preload .sidebar-head .brand,
body.sidebar-collapsed .sidebar-head .brand{flex:0 0 auto}

html.sidebar-collapsed-preload .sidebar-toggle,
body.sidebar-collapsed .sidebar-toggle{
    width: 50px;
    height: 50px;
    min-width: 22px;
    border: 0;
    background: transparent;
    box-shadow: none;
    opacity: 0;
    position: absolute;
    top: -7px;
    left: -3px;
    border-radius: 50px;
}

html.sidebar-collapsed-preload .sidebar-toggle:hover,
body.sidebar-collapsed .sidebar-toggle:hover{
    opacity:.95;
    color:var(--highlight);
    background:var(--mp-fill-045);
}

html.sidebar-collapsed-preload .sidebar-toggle svg,
body.sidebar-collapsed .sidebar-toggle svg{
    width:13px;
    height:13px;
    transform:rotate(180deg);
}

html.sidebar-collapsed-preload .sidebar .brand,
body.sidebar-collapsed .sidebar .brand{
    justify-content: center;
    margin-bottom: 4px;
    padding-left: 2px;
}

html.sidebar-collapsed-preload .sidebar .brand-text,
html.sidebar-collapsed-preload .sidebar .user-meta,
html.sidebar-collapsed-preload .sidebar .user-logout span:first-child,
html.sidebar-collapsed-preload .sidebar .user-footer-inline .logout-btn span:first-child,
html.sidebar-collapsed-preload .sidebar .user-footer-inline .btn-logout span:first-child,
body.sidebar-collapsed .sidebar .brand-text,
body.sidebar-collapsed .sidebar .user-meta,
body.sidebar-collapsed .sidebar .user-logout span:first-child,
body.sidebar-collapsed .sidebar .user-footer-inline .logout-btn span:first-child,
body.sidebar-collapsed .sidebar .user-footer-inline .btn-logout span:first-child{
    display:none!important;
}

html.sidebar-collapsed-preload .sidebar .nav-label,
body.sidebar-collapsed .sidebar .nav-label{
    max-width:0;
    opacity:0;
    transform:translateX(-6px);
    transition:opacity .08s ease,transform .08s ease,max-width .1s ease;
}

html.sidebar-collapsed-preload .sidebar .brand-mark,
body.sidebar-collapsed .sidebar .brand-mark{
    --mp-brand-ring-size:36px;
    width:var(--mp-brand-ring-size);
    height:var(--mp-brand-ring-size);
    flex-basis:var(--mp-brand-ring-size);
}

html.sidebar-collapsed-preload .sidebar nav,
body.sidebar-collapsed .sidebar nav{align-items:center}

html.sidebar-collapsed-preload .sidebar nav a,
body.sidebar-collapsed .sidebar nav a{
    justify-content: center;
    gap: 0;
    /* Same token the rail is derived from, so the button and the space around it cannot
       drift apart. A literal here would let the two disagree silently again. */
    width: var(--mp-nav-btn);
    height: var(--mp-nav-btn);
    padding: 0;
}

html.sidebar-collapsed-preload .sidebar .nav-icon,
body.sidebar-collapsed .sidebar .nav-icon{
    width:24px;
    height:24px;
}

html.sidebar-collapsed-preload .sidebar .user,
body.sidebar-collapsed .sidebar .user{
    align-items:center;
    gap:12px;
    padding:8px;
}

html.sidebar-collapsed-preload .sidebar .user-footer-inline,
html.sidebar-collapsed-preload .sidebar .user:not(:has(.user-footer-inline)),
body.sidebar-collapsed .sidebar .user-footer-inline,
body.sidebar-collapsed .sidebar .user:not(:has(.user-footer-inline)){
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:10px;
}

html.sidebar-collapsed-preload .sidebar .user-footer-inline .user-avatar,
html.sidebar-collapsed-preload .sidebar .user-footer-inline .user-logout,
html.sidebar-collapsed-preload .sidebar .user-footer-inline .logout-btn,
html.sidebar-collapsed-preload .sidebar .user-footer-inline .btn-logout,
html.sidebar-collapsed-preload .sidebar .user:not(:has(.user-footer-inline)) > .user-avatar,
html.sidebar-collapsed-preload .sidebar .user:not(:has(.user-footer-inline)) > .user-logout,
body.sidebar-collapsed .sidebar .user-footer-inline .user-avatar,
body.sidebar-collapsed .sidebar .user-footer-inline .user-logout,
body.sidebar-collapsed .sidebar .user-footer-inline .logout-btn,
body.sidebar-collapsed .sidebar .user-footer-inline .btn-logout,
body.sidebar-collapsed .sidebar .user:not(:has(.user-footer-inline)) > .user-avatar,
body.sidebar-collapsed .sidebar .user:not(:has(.user-footer-inline)) > .user-logout{
    display:flex;
    align-items:center;
    justify-content:center;
    width: 32px;
    height: 32px;
    min-width: 32px;
    max-width: 32px;
    min-height: 32px;
    max-height: 32px;
    flex:0 0 48px;
    padding:0;
    margin:0;
    overflow:hidden;
    border-radius:5px;
}

html.sidebar-collapsed-preload .sidebar .user-footer-inline .user-avatar,
html.sidebar-collapsed-preload .sidebar .user:not(:has(.user-footer-inline)) > .user-avatar,
body.sidebar-collapsed .sidebar .user-footer-inline .user-avatar,
body.sidebar-collapsed .sidebar .user:not(:has(.user-footer-inline)) > .user-avatar{font-size:var(--mp-fs-md,14px)}

html.sidebar-collapsed-preload .sidebar .user-footer-inline .user-logout,
html.sidebar-collapsed-preload .sidebar .user-footer-inline .logout-btn,
html.sidebar-collapsed-preload .sidebar .user-footer-inline .btn-logout,
html.sidebar-collapsed-preload .sidebar .user:not(:has(.user-footer-inline)) > .user-logout,
body.sidebar-collapsed .sidebar .user-footer-inline .user-logout,
body.sidebar-collapsed .sidebar .user-footer-inline .logout-btn,
body.sidebar-collapsed .sidebar .user-footer-inline .btn-logout,
body.sidebar-collapsed .sidebar .user:not(:has(.user-footer-inline)) > .user-logout{font-size:0}

html.sidebar-collapsed-preload .sidebar .user-footer-inline .logout-icon,
html.sidebar-collapsed-preload .sidebar .user:not(:has(.user-footer-inline)) > .user-logout .logout-icon,
body.sidebar-collapsed .sidebar .user-footer-inline .logout-icon,
body.sidebar-collapsed .sidebar .user:not(:has(.user-footer-inline)) > .user-logout .logout-icon{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    margin:0;
    font-size:var(--mp-fs-xl,18px);
    line-height:var(--mp-lh-none,1);
}

/* ===== Sidebar preload stability =====
   Geometry is already shared above, so the preload state can never disagree with
   the resolved collapsed state. The only thing preload must add is an instant
   paint: suppress transitions so the rail does not tween while it snaps into the
   collapsed layout before body.sidebar-collapsed takes over. */
html.sidebar-collapsed-preload .sidebar,
html.sidebar-collapsed-preload .sidebar *,
html.sidebar-collapsed-preload .content{
    transition:none!important;
}

/* ===== Mobile navigation drawer ===== */
.mobile-app-header,
.mobile-menu-toggle,
.mobile-menu-backdrop{display:none}

@media(max-width:1000px){
    html,
    body{min-height:100%}

    body.mobile-menu-open{overflow:hidden}

    .mobile-app-header{
        position:fixed!important;
        inset:0 0 auto 0!important;
        display:flex!important;
        align-items:center;
        justify-content:space-between;
        gap:16px;
        height:72px!important;
        min-height:72px!important;
        max-height:72px!important;
        padding:12px 16px;
        z-index:1000;
        background:linear-gradient(180deg,var(--mp-topbar-hi),var(--mp-topbar-lo));
        border-bottom:1px solid var(--mp-fill-08);
        box-shadow:0 18px 42px var(--mp-sh-22);
        backdrop-filter:blur(18px);
    }

    .mobile-app-brand,
    .mobile-app-brand .brand,
    .mobile-app-brand a{
        display:flex;
        align-items:center;
        min-width:0;
    }

    .mobile-app-brand .brand,
    .mobile-app-brand a{
        gap:10px;
        color:var(--mp-ink-strong);
        font-size:var(--mp-fs-3xl,24px);
        font-weight:var(--mp-fw-heavy,800);
        letter-spacing:-1px;
        text-decoration:none;
        position: relative;
        left: -4px;
    }

    .mobile-app-brand .brand-mark{
        --mp-brand-ring-size:30px;
        width:var(--mp-brand-ring-size);
        height:var(--mp-brand-ring-size);
        flex:0 0 var(--mp-brand-ring-size);
    }

    .mobile-app-brand .brand-text{
        display:flex!important;
        min-width:0;
        max-width:calc(100vw - 106px);
        overflow:hidden;
        white-space:nowrap;
        text-overflow:ellipsis;
    }

    /* ═══ THE ICON IS THE CONTROL ═══
       Everything that used to make this a button — the gradient, the border, the inner ring,
       the lift shadow — is gone, so the nine dots are the whole of what is drawn. That is not
       only a visual decision. With no box edge left to read, the thing a person aligns against
       is the INK, which is why the geometry below is stated against the app bar's own trailing
       control rather than against this element's box.

       ONE RAIL DOWN THE RIGHT OF TWO STACKED BARS. mail-service's notification bell sits in
       .mp-appbar one bar below: 32px wide, right edge on --mp-content-pad. The dot grid is the
       same 32px on the same gutter, so the two read as one column — 3 dots of 6px and 2 gaps of
       7px. Before this release both bars put their trailing control's OUTER EDGE on the gutter,
       which is the same rule producing two different answers, because one control is a 32px box
       and the other a 48px finger target: 8px between the centres and 15px between the visible
       right edges, on the two controls a person reads as a pair.

       THE HIT BOX IS NOT THE INK, and only one of them is visible. 48px is what a finger needs;
       the surplus 16px is centred on the ink, so 8px of it hangs past the gutter. Pulling the
       BUTTON outboard rather than shrinking it is what keeps reach and alignment from having to
       be traded against each other.

       MEASURED, NOT ASSERTED: tests/behavior/mobile_nav_rail_test.js renders this header and the
       real bell in one page at 480px and reads both rectangles back. It reads the bell's width
       from mail-service's own stylesheet rather than from a literal, so retuning --mp-notif-size
       fails that suite until this is retuned with it. That coupling is the point — they are one
       rail, and the alternative is a second hardcoded 32px here that drifts in silence. */
    .mobile-menu-toggle{
        --mp-menu-dot:6px;
        --mp-menu-gap:7px;
        --mp-menu-pitch:calc(var(--mp-menu-dot) + var(--mp-menu-gap));
        position:relative;
        display:grid!important;
        place-items:center;
        width:48px;
        height:48px;
        min-width:48px;
        max-width:48px;
        min-height:48px;
        max-height:48px;
        flex:0 0 48px;
        /* Half the difference between the finger target and the 32px ink column. */
        margin-inline-end:calc((32px - 48px) / 2);
        padding:0;
        z-index:1020;
        appearance:none;
        -webkit-appearance:none;
        border:0;
        /* Nothing is painted here any more; the radius survives only because an outline follows
           it, and the focus ring is now the one thing that draws a box. */
        border-radius:var(--radius,5px);
        background:none;
        line-height:0;
        cursor:pointer;
        transition:opacity .12s ease;
    }

    /* No border and no background means no colour left to carry focus, so the ring is drawn on
       the hit area — which is also the honest thing to show, since the hit area is what a key
       press activates and it is larger than the glyph. */
    .mobile-menu-toggle:focus-visible{
        outline:2px solid var(--mp-brand-ring,#38bdf8);
        outline-offset:2px;
    }

    /* Press feedback without geometry. The old button lifted 1px; on a bare glyph that reads as
       a jitter, and a transform here would make this element a containing block for anything
       fixed inside it. Opacity moves nothing. */
    .mobile-menu-toggle:active{opacity:.55}

    .mobile-menu-toggle span{
        position:absolute;
        left:50%;
        top:50%;
        display:block;
        width:var(--mp-menu-dot);
        height:var(--mp-menu-dot);
        border-radius:2px;
        background:var(--highlight);
        transform-origin:center center;
        /* --mp-menu-x/y are resolved from the nth-child rules below. Custom properties resolve at
           computed-value time, so this transform reads the winning value regardless of the fact
           that it is declared first — which is what lets the grid be one transform and six
           coordinates rather than nine positions to keep in step. */
        transform:translate(calc(-50% + var(--mp-menu-x,0px)),calc(-50% + var(--mp-menu-y,0px)));
        transition:transform .26s cubic-bezier(.2,.8,.2,1),width .22s ease,height .22s ease,border-radius .22s ease,opacity .2s ease;
    }

    /* Three columns, three rows, one pitch, addressed over the 9-cell reading order. Stated as
       the STRUCTURE rather than as nine hand-placed cells: a grid written cell by cell can drift
       one cell at a time and still look plausible. */
    .mobile-menu-toggle span:nth-child(3n+1){--mp-menu-x:calc(0px - var(--mp-menu-pitch))}
    .mobile-menu-toggle span:nth-child(3n+2){--mp-menu-x:0px}
    .mobile-menu-toggle span:nth-child(3n+3){--mp-menu-x:var(--mp-menu-pitch)}
    .mobile-menu-toggle span:nth-child(-n+3){--mp-menu-y:calc(0px - var(--mp-menu-pitch))}
    .mobile-menu-toggle span:nth-child(n+4):nth-child(-n+6){--mp-menu-y:0px}
    .mobile-menu-toggle span:nth-child(n+7){--mp-menu-y:var(--mp-menu-pitch)}

    /* Open: every dot becomes a bar and gets out of the way, and the two ends of one diagonal
       stay to cross. Written as "all of them, then the two exceptions" so the count of dots and
       the count of faders cannot disagree — the four-dot version listed both halves by hand and
       would have left a dot behind the moment a fifth was added. */
    .mobile-menu-toggle.is-open span{
        width:28px;
        height:3px;
        border-radius:99px;
        opacity:0;
        transform:translate(-50%,-50%) scale(.4);
    }

    .mobile-menu-toggle.is-open span:nth-child(1){opacity:1;transform:translate(-50%,-50%) rotate(45deg)}
    .mobile-menu-toggle.is-open span:nth-child(9){opacity:1;transform:translate(-50%,-50%) rotate(-45deg)}

    .mobile-menu-backdrop{
        position:fixed!important;
        inset:0;
        display:block;
        z-index:980;
        background:var(--mp-backdrop);
        backdrop-filter:blur(6px);
        opacity:0;
        pointer-events:none;
        transition:opacity .2s ease;
    }

    body.mobile-menu-open .mobile-menu-backdrop{
        opacity:1;
        pointer-events:auto;
    }

    /* THE BLUR LIVES HERE. As a rail this element blurs a flat <html> and the base rule
       therefore states none; as a drawer it covers .content and blurs the pane gradient,
       which is a real surface treatment. Same 22px, stated once, on the form that uses it. */
    .sidebar,
    body.sidebar-collapsed .sidebar{
        backdrop-filter:blur(22px);
        -webkit-backdrop-filter:blur(22px);
        position:fixed!important;
        top:0!important;
        right:0!important;
        bottom:0!important;
        left:0!important;
        width:100vw!important;
        max-width:none;
        height:100dvh!important;
        max-height:100dvh!important;
        margin:0!important;
        float:none!important;
        padding:94px 22px 24px;
        overflow-y:auto;
        z-index:990;
        border-right:0;
        border-left:0;
        opacity:0;
        visibility:hidden;
        pointer-events:none;
        transform:scale(.96)!important;
        transform-origin:center center;
        transition:opacity .22s ease,transform .24s cubic-bezier(.2,.8,.2,1),visibility 0s linear .24s,box-shadow .24s ease;
    }

    body.mobile-menu-open .sidebar,
    body.mobile-menu-open.sidebar-collapsed .sidebar{
        opacity:1;
        visibility:visible;
        pointer-events:auto;
        transform:scale(1)!important;
        box-shadow:0 24px 80px var(--mp-sh-50);
        transition:opacity .22s ease,transform .24s cubic-bezier(.2,.8,.2,1),visibility 0s linear 0s,box-shadow .24s ease;
    }

    .sidebar-toggle,
    body.sidebar-collapsed .sidebar-head,
    .sidebar-head{display:none!important}

    body.sidebar-collapsed .sidebar .brand-text,
    .brand-text{display:flex!important}

    body.sidebar-collapsed .sidebar .brand-mark,
    .sidebar .brand-mark{
        --mp-brand-ring-size:30px;
        width:var(--mp-brand-ring-size);
        height:var(--mp-brand-ring-size);
        flex-basis:var(--mp-brand-ring-size);
    }

    body.sidebar-collapsed .sidebar nav,
    .sidebar nav{
        display:grid;
        grid-template-columns:repeat(3,1fr);
        gap:14px;
        width:100%;
        max-width:540px;
        margin-top:0;
        margin-left:auto;
        margin-right:auto;
        align-self:center;
        align-items:stretch;
        align-content:flex-start;
    }

    body.sidebar-collapsed .sidebar nav a,
    .sidebar nav a{
        flex-direction:column;
        justify-content:center;
        align-items:center;
        align-self:start;
        gap:12px;
        width:auto;
        height:auto;
        min-height:0;
        aspect-ratio:1 / 1;
        padding:12px 8px;
        border-radius:14px;
        border-color:var(--mp-fill-07);
        background:var(--mp-fill-02);
        font-size:var(--mp-fs-base,13px);
        text-align:center;
        overflow:visible;
    }

    body.sidebar-collapsed .sidebar nav .nav-icon,
    .sidebar nav .nav-icon{width:28px;height:28px}

    body.sidebar-collapsed .sidebar nav .nav-icon svg,
    .sidebar nav .nav-icon svg{width:28px;height:28px}

    body.sidebar-collapsed .sidebar .nav-label,
    .sidebar .nav-label{
        display:inline-block!important;
        width:auto;
        max-width:none;
        opacity:1;
        transform:none;
        transition:none;
    }

    body.sidebar-collapsed .sidebar nav .nav-label,
    .sidebar nav .nav-label{
        max-width:100%;
        font-size:var(--mp-fs-base,13px);
        font-weight:var(--mp-fw-semibold,600);
        line-height:var(--mp-lh-snug,1.25);
        letter-spacing:var(--mp-ls-tight,-0.01em);
        text-align:center;
        white-space:normal;
        overflow:hidden;
    }

    body.sidebar-collapsed .sidebar .user,
    .sidebar .user{
        justify-content:flex-start;
        min-height:auto;
        padding:10px;
    }

    body.sidebar-collapsed .content,
    .content{
        margin-left:0!important;
        /* min-height:100dvh stopped a short page leaving shell background below it. The
           pane is bounded to the viewport now, so that cannot arise — and the floor would
           win over the height and push the document past the viewport by the bar's own
           height. */
        min-height:0;
        padding:88px var(--mp-content-pad) 28px!important;
    }

    body.mobile-menu-open .content,
    body.mobile-menu-open.sidebar-collapsed .content{
        margin-left:0!important;
        padding-top:88px!important;
        transform:none!important;
    }

    .content > h1:first-of-type,
    .page-header{margin-top:0}
}


.floating-add-btn{
    width: 34px;
    height: 34px;
    min-width: 34px;
    max-width: 34px;
    min-height: 34px;
    max-height: 34px;
    flex: 0 0 34px;
    border-radius: 5px;
    background: var(--mp-hl-16)!important;
    border: 1px solid var(--mp-hl-28)!important;
    color: var(--green);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size:var(--mp-fs-2xl,20px);
    font-weight:var(--mp-fw-regular,400);
    line-height: 54px;
    text-decoration: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    box-shadow:0 16px 34px var(--mp-hl-18);
    transition:.18s ease;
}

.floating-add-btn:hover{
    transform:translateY(-1px) scale(1.02);
    box-shadow:0 22px 42px var(--mp-hl-24);
}


/* ===== Logout transition ===== */
html.mp-logout-transition-active,
html.mp-logout-transition-active body{
    overflow:hidden;
}

/* Logout uses the same brand throbber language as successful login, in reverse. */
.mp-logout-throbber-overlay{
    --mp-logout-orb-size:clamp(300px, 34vw, 440px);
    --mp-logout-ring-width:9px;
    position:fixed;
    inset:0;
    z-index:99999;
    pointer-events:none;
    overflow:hidden;
    background:
        radial-gradient(circle at 50% 50%, var(--mp-hl-16), var(--mp-accent-09) 34%, transparent 62%),
        var(--mp-throbber-scrim);
    backdrop-filter:blur(18px) saturate(1.12);
    -webkit-backdrop-filter:blur(18px) saturate(1.12);
    opacity:0;
    animation:mp-logout-throbber-overlay-in .22s ease forwards;
}

.mp-logout-throbber-overlay::before{
    content:"";
    position:absolute;
    inset:0;
    background:
        radial-gradient(circle at 50% 50%, var(--mp-hl-13), transparent 28%),
        radial-gradient(circle at calc(50% + 44px) calc(50% + 28px), var(--mp-accent-12), transparent 36%),
        linear-gradient(135deg, var(--mp-throbber-a), var(--mp-throbber-b));
    opacity:.96;
}

.mp-logout-throbber-mark{
    position:fixed;
    left:0;
    top:0;
    width:var(--mp-logout-orb-size);
    height:var(--mp-logout-orb-size);
    border-radius:50%;
    opacity:0;
    will-change:transform, opacity, filter;
    isolation:isolate;
    overflow:visible;
    background:transparent;
    box-shadow:none;
}

.mp-logout-throbber-mark::before{
    content:"";
    position:absolute;
    inset:-56px;
    z-index:0;
    border-radius:50%;
    background:
        radial-gradient(circle, var(--mp-hl-13), var(--mp-accent-08) 38%, var(--mp-throbber-sheen) 68%, transparent 76%);
    box-shadow:0 0 96px var(--mp-hl-20), 0 0 132px var(--mp-accent-12);
    opacity:.92;
    transform:scale(.86);
    animation:mp-logout-throbber-halo 1.05s ease-in-out infinite;
}

.mp-logout-throbber-mark::after{
    content:"";
    position:absolute;
    inset:0;
    z-index:1;
    border-radius:50%;
    background:conic-gradient(from 180deg, rgba(56,189,248,.08), rgba(0,234,94,.96), rgba(56,189,248,.86), rgba(0,234,94,.12));
    -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - var(--mp-logout-ring-width) - 1px), #000 calc(100% - var(--mp-logout-ring-width)));
    mask:radial-gradient(farthest-side, transparent calc(100% - var(--mp-logout-ring-width) - 1px), #000 calc(100% - var(--mp-logout-ring-width)));
    box-shadow:0 0 34px var(--mp-hl-22), 0 0 52px var(--mp-accent-14);
    animation:mp-logout-throbber-spin 1.16s cubic-bezier(.22,1,.36,1) both;
}

.mp-logout-throbber-core{
    position:absolute;
    inset:0;
    z-index:2;
    border-radius:50%;
    background:radial-gradient(circle, transparent 58%, var(--mp-hl-06) 70%, transparent 72%);
    opacity:.34;
    pointer-events:none;
}

@keyframes mp-logout-throbber-overlay-in{
    to{opacity:1;}
}

@keyframes mp-logout-throbber-halo{
    0%,100%{transform:scale(.86);opacity:.36;}
    48%{transform:scale(1.16);opacity:.92;}
}

@keyframes mp-logout-throbber-spin{
    0%{transform:rotate(360deg) scale(1);opacity:.96;}
    100%{transform:rotate(-88deg) scale(.94);opacity:.62;}
}

@media (max-width:700px){
    .mp-logout-throbber-overlay{
        --mp-logout-orb-size:clamp(240px, 64vw, 340px);
        --mp-logout-ring-width:7.5px;
    }
}

@media (prefers-reduced-motion: reduce){
    .mp-logout-throbber-overlay,
    .mp-logout-throbber-mark,
    .mp-logout-throbber-mark::before,
    .mp-logout-throbber-mark::after{
        animation-duration:.01ms!important;
        transition-duration:.01ms!important;
    }
}


/* ===== App bar (sticky page header) ======================================
   The page header is shell chrome, not page content: one bar, rendered once by
   core (PageHeader::render), sticky on every route.

       [ H1 / breadcrumb ]      [ search ]      [ actions ][ bell ]

   It is a SIBLING of <main>, carrying the column offset itself: margin-left is --mp-rail,
   the same token .content uses, so the two track the collapse together. Not sticky: the
   pane owns the scroll, the document never moves, and position:sticky would be a rule that
   can never fire. Its old bottom margin (--mp-appbar-gap) is the pane's top padding now:
   same rhythm, moved with the element that owns it. Its surface is var(--mp-sidebar) over a
   flat <html>, exactly as the rail is, which is what makes the two read as one piece.

   Height is derived, not guessed: --mp-appbar-h = the sidebar's top padding (20.5px)
   doubled + the brand block (32px), which puts the H1's optical centre on the logo's.
   Change the sidebar padding and this must follow. */
:root{
    --mp-appbar-h:72px;
    --mp-appbar-pad:var(--mp-content-pad,42px);
    --mp-appbar-gap:26px;
    --mp-appbar-title:26px;
    --mp-appbar-search-w:clamp(260px,32vw,520px);
}

.mp-appbar{
    position:relative;
    z-index:60;
    display:grid;
    grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);
    align-items:center;
    gap:18px;
    height:var(--mp-appbar-h);
    margin:0 0 0 var(--mp-rail);
    padding:0 var(--mp-appbar-pad);
    border-bottom:1px solid transparent;
    transition:border-color .18s ease;
}

/* The bar's frosted surface lives on a child layer, NOT on .mp-appbar itself.
   An element carrying backdrop-filter becomes a BACKDROP ROOT: every descendant's own
   backdrop-filter then samples an empty backdrop and blurs nothing. The bar hosts
   overlays — the notification flyout today (whose panel has no background colour at all:
   the blur IS its surface, so it went fully transparent), a global-search results panel
   next — so the bar must never be a backdrop root. A pseudo-element is not an ancestor of
   the bar's children, so the blur applies to the page behind the bar while the flyouts
   inside it keep frosting the page normally. */
.mp-appbar::before{
    content:'';
    position:absolute;
    inset:0;
    z-index:-1;
    /* SAME FINDING AS THE RAIL, same reasoning, restated below 1000px rather than removed.
       The bar is normal flow above .content, so on a desktop width its backdrop is the flat
       <html> and the blur is a no-op: measured at 1440 and 1024, dpr 2, max 1 of 255 on
       ~1.3% of pixels, against a control that moved 251 of 255 on 90%.

       BELOW 1000px THIS IS LEFT ALONE. The mobile shell rearranges what is behind the bar —
       a fixed 72px header, a pane whose height is cut for it — and the fixture used to take
       these measurements carries no .mobile-app-header, so its narrow rendering is not
       evidence about the real one. A no-op proven at desktop widths is not a no-op proven
       everywhere, so the change is scoped to where the proof reaches. */
    background:var(--mp-sidebar);
    /* This layer is what makes the bar and the rail read as one piece: same
       var(--mp-sidebar), same blur, and since v1.12.431 the same flat backdrop, so the two
       resolve to one colour by construction rather than by anyone matching a literal.
       The clip-path outlived the shadow it was written for (see the removed stuck rules
       above) and is kept deliberately: it also stops the blur's edge from bleeding upward
       past the bar, and the pseudo has no children so clipping it costs nothing. */
    clip-path:inset(0 0 -40px 0);
    pointer-events:none;
}

/* The bar only asserts an edge once there is content behind it. At scroll 0 it is
   indistinguishable from the page — the chrome earns its border by being used. */
/* body.mp-appbar-stuck: REMOVED at v1.12.432 =================================
   Two rules lived here, giving the bar a bottom border and a shadow once content had
   scrolled behind it. NOTHING EVER SET THE CLASS — zero writers in js or php across the
   whole tree, so the behaviour had never once fired. It is not revivable either: since
   v1.12.431 the pane owns the scroll and the bar sits outside it, so no content can pass
   behind the bar and there is no moment for a stuck state to mark. Reinstating the effect
   would mean a scroll listener on .content and a different question — 'has the pane
   scrolled', not 'is the bar stuck' — so it would not be these rules coming back. */

.mp-appbar-lead{
    display:flex;
    flex-direction:column;
    justify-content:center;
    gap:2px;
    min-width:0;
}

.mp-appbar-crumbs{
    display:flex;
    align-items:center;
    gap:6px;
    min-width:0;
    color:var(--mp-ink-muted);
    font-size:var(--mp-fs-xs,11px);
    font-weight:var(--mp-fw-bold,700);
    letter-spacing:var(--mp-ls-wide,0.04em);
    text-transform:uppercase;
    white-space:nowrap;
    overflow:hidden;
}

.mp-appbar-crumb{
    min-width:0;
    overflow:hidden;
    text-overflow:ellipsis;
}

.mp-appbar-crumb + .mp-appbar-crumb::before{
    content:'/';
    margin-right:6px;
    opacity:.5;
}

.mp-appbar-title{
    margin:0;
    min-width:0;
    color:var(--mp-ink-strong);
    font-size:var(--mp-appbar-title);
    /* snug, not tight: overflow:hidden ellipsis must not slice 900-weight descenders (Safari enforces the line box) */
    line-height:var(--mp-lh-snug,1.25);
    letter-spacing:var(--mp-ls-tighter,-0.03em);
    font-weight:var(--mp-fw-black,900);
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
}

.mp-appbar-mid{
    display:flex;
    align-items:center;
    justify-content:center;
    min-width:0;
}

.mp-appbar-tail{
    display:flex;
    align-items:center;
    justify-content:flex-end;
    gap:14px;
    min-width:0;
}

.mp-appbar-actions{
    display:flex;
    align-items:center;
    gap:8px;
}

/* The bell's mount point (mail-service renders into the appbar_right hook). The slot
   is the ONLY thing that positions it — the bell no longer pins itself to the viewport
   corner, and no page header has to reserve an inset for it. */
.mp-appbar-slot{
    display:flex;
    align-items:center;
    gap:8px;
}

/* Module-contributed widgets (mp_appbar_widget) share the slot with the bell. Each is a
   flex item whose inline CSS `order` (from the widget's declared order) fixes its place in
   the rail regardless of the order ModuleHooks emitted them in — the bell sits at the edge
   (high order), module widgets to its left. One wrapper, one rhythm, every module. */
.mp-appbar-widget{
    display:flex;
    align-items:center;
}

/* ----- Search --------------------------------------------------------------
   The bar renders the control; it does not implement search. A provider claims it at
   runtime (window.MPSearch.register) — today the page's mpdg grid, tomorrow a
   system-wide AJAX search. A view only renders this control when it DECLARES search
   (PageHeader), so it paints visible from first render — no is-idle pop-in. .is-idle
   applies only if the last provider releases at runtime, leaving nothing to type into.

   ONE BOX. .mp-appbar-search-field is the field's visual box on every page and at every
   width: border, fill, shadow, hover, focus, the active-search ring and the narrow-layout
   overlay are stated on it and nowhere else, and the input inside it is text entry only.
   Until v1.12.533 the plain input WAS the box while the token field built a second box
   around it at runtime, so each of those rules existed twice and the overlay was mirrored
   one-for-one. PageHeader renders the wrapper now; the token field only adds chips to its
   track. */
.mp-appbar-search{
    position:relative;
    display:flex;
    align-items:center;
    width:var(--mp-appbar-search-w);
    max-width:100%;
    /* The field's box, stated once. 40px and this shadow are what the plain input used to
       receive by ACCIDENT — forms-controls' global `input` rule reached in, and a height here
       could never beat its min-height — so the wrapper states them on purpose. */
    --mp-search-h:40px;
    --mp-search-sh:var(--mp-sh-14) 0 1px 2px, var(--mp-fill-035) 0 1px 0 inset;
    /* The active-search ring's weight. One number. */
    --mp-search-ring-w:3px;
}

.mp-appbar-search.is-idle{
    display:none;
}

.mp-appbar-search-field{
    position:relative;
    display:flex;
    align-items:center;
    flex:1 1 auto;
    width:100%;
    min-width:0;
    min-height:var(--mp-search-h);
    padding:0 34px 0 34px;
    border:1px solid var(--mp-fill-07);
    border-radius:9px;
    background:var(--mp-fill-035);
    box-shadow:var(--mp-search-sh);
    cursor:text;
    transition:border-color .16s ease,background .16s ease,box-shadow .16s ease;
}

/* The hover wash is forms-controls' `input:hover` fill, which the plain input picked up by
   accident and the token field never did — so Tasks hovered one way and every other search page
   another. Stated here on purpose, and kept, because it is what ten of the eleven pages showed. */
.mp-appbar-search-field:hover{
    border-color:var(--mp-fill-10);
    background:linear-gradient(var(--mp-fill-067),var(--mp-fill-03));
}

.mp-appbar-search-field:focus-within{
    border-color:var(--mp-hl-20);
    background:var(--mp-fill-06);
    box-shadow:0 0 0 3px var(--mp-hl-08);
}

/* Offsets are measured from the field's OUTER edge, as they were when the control's root was
   their containing block: the field's 1px border plus 11px is the 12px the glyph always sat at. */
.mp-appbar-search-ico{
    position:absolute;
    left:11px;
    width:15px;
    height:15px;
    background:var(--mp-ink-muted);
    mask:var(--mp-icon-search) center/contain no-repeat;
    -webkit-mask:var(--mp-icon-search) center/contain no-repeat;
    pointer-events:none;
    opacity:.75;
    transition:background .16s ease,opacity .16s ease;
}

.mp-appbar-search:focus-within .mp-appbar-search-ico{
    background:var(--highlight);
    opacity:1;
}

/* The row the text and any chips share. It clips, not the field, so the active-search ring
   (a pseudo-element on the field) is never cut off by the field's own overflow. */
.mp-appbar-search-track{
    display:flex;
    align-items:center;
    align-self:stretch;
    flex:1 1 auto;
    gap:5px;
    min-width:0;
    overflow:hidden;
}

/* Text entry only. The typography lives here; everything that made the input a box is reset
   below. A child selector, so the reset outranks both this rule and forms-controls' global
   `input` rule, which imposes a height, a min-height, padding, a border, a fill and a shadow
   that must each be undone explicitly — a height alone does not override a min-height. */
.mp-appbar-search-input{
    color:var(--mp-ink-strong);
    font:inherit;
    font-size:var(--mp-fs-base,13px);
    font-weight:var(--mp-fw-medium,500);
    outline:0;
}

.mp-appbar-search-track > .mp-appbar-search-input{
    flex:1 1 60px;
    width:auto;
    min-width:60px;
    height:34px;
    min-height:0;
    max-height:none;
    padding:0;
    margin:0;
    border:0;
    border-radius:0;
    background:none;
    box-shadow:none;
}

.mp-appbar-search-track > .mp-appbar-search-input:hover,
.mp-appbar-search-track > .mp-appbar-search-input:focus{
    border:0;
    background:none;
    box-shadow:none;
}

.mp-appbar-search-input::placeholder{
    color:var(--mp-ink-muted);
    opacity:.9;
}

/* Once a chip is present the placeholder would compete with it for the same short line. */
.mp-appbar-search-track.has-chips > .mp-appbar-search-input::placeholder{
    color:transparent;
}

.mp-appbar-search-input::-webkit-search-cancel-button{
    -webkit-appearance:none;
    appearance:none;
}

/* How many rows the applied term leaves, as the provider reports it (MPSearch setCount).
   Shown only while a term is applied AND the provider has counted it: an unknown count is
   absent, never a guessed 0. In the flow rather than absolute, so the text it sits beside
   gives way to it instead of running underneath. */
.mp-appbar-search-count{
    flex:0 0 auto;
    margin-left:8px;
    color:var(--mp-ink-muted);
    font-size:var(--mp-fs-xs,11px);
    font-weight:var(--mp-fw-semibold,600);
    font-variant-numeric:tabular-nums;
    white-space:nowrap;
}

.mp-appbar-search-clear{
    position:absolute;
    right:7px;
    display:grid;
    place-items:center;
    width:20px;
    height:20px;
    padding:0;
    border:0;
    border-radius:999px;
    background:var(--mp-fill-07);
    color:var(--mp-ink-muted);
    cursor:pointer;
    transition:background .16s ease,color .16s ease;
}

.mp-appbar-search-clear:hover{
    background:var(--mp-fill-10);
    color:var(--mp-ink-strong);
}

.mp-appbar-search-clear[hidden]{
    display:none;
}

.mp-appbar-search-clear span{
    width:9px;
    height:9px;
    background:currentColor;
    mask:var(--mp-icon-close) center/contain no-repeat;
    -webkit-mask:var(--mp-icon-close) center/contain no-repeat;
}

/* The collapsed (icon) affordance is a mobile/narrow control only. */
.mp-appbar-search-toggle{
    display:none;
}

/* ----- Active search ---------------------------------------------------------
   While a provider is filtering on a term, the field wears a brand-gradient ring — and at
   narrow widths, where the field is folded away, the toggle that opens it does. MPSearch
   sets .has-query from the term the provider actually applied (trimmed, as the grid trims
   it); PageHeader sets it at first paint when the server already applied one.

   A RING, NOT A BORDER, and not a gradient background either. border-image ignores
   border-radius, and the padding-box/border-box background pair needs an opaque interior —
   this field's fill is translucent, so the gradient would show through the whole box. So
   the ring is a pseudo-element masked down to its padding band: it lies over the field's own
   1px border, nothing inside the field moves, and the fill underneath is untouched. */
.mp-appbar-search-field::after,
.mp-appbar-search-toggle::after{
    content:'';
    position:absolute;
    inset:-1px;
    padding:var(--mp-search-ring-w);
    border-radius:inherit;
    background:var(--grad-brand);
    -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
    -webkit-mask-composite:xor;
    mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0);
    opacity:0;
    pointer-events:none;
    transition:opacity .16s ease;
}

.mp-appbar-search.has-query .mp-appbar-search-field::after,
.mp-appbar-search.has-query:not(.is-open) .mp-appbar-search-toggle::after{
    opacity:1;
}

/* ----- Fallback: a module that has not declared a PageHeader --------------------
   Core renders the bar anyway (titled from the nav menu), so the module's own
   in-content header would print the title twice. Suppress the shapes we ship; a
   third-party view with a deeper header keeps rendering as it always did. */
body[data-mp-appbar="fallback"] .content > h1:first-of-type,
body[data-mp-appbar="fallback"] .content > .page-header,
body[data-mp-appbar="fallback"] .content > .page-header-actions{
    display:none;
}

/* The legacy in-content header. Nothing core renders it any more (PageHeader owns the
   title + actions); it survives for views that have not been migrated. Owned here, with
   the rest of the page chrome — it was living in the dashboards component. */
.page-header-actions{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:20px;
    margin-bottom:26px;
}

.page-header-actions h1{margin:0}


/* ----- Narrow + mobile ------------------------------------------------------- */
@media(max-width:1240px){
    :root{
        --mp-appbar-title:22px;
    }
}

@media(max-width:1000px){
    :root{
        /* Narrow gutter for the whole column — .content and the bar move together. */
        --mp-content-pad:16px;
        --mp-appbar-gap:20px;
        --mp-appbar-h:60px;
        --mp-appbar-title:19px;
    }

    /* The rail is a full-screen drawer here, so there is nothing to lap over and nothing to
       round against: both tokens go to zero. Stated on the same selectors as the collapsed
       rule and later in the file, so it wins on source order, not specificity. */
    html.sidebar-collapsed-preload,
    body.sidebar-collapsed,
    body{
        --mp-rail:0px;
        --mp-pane-lap:0px;
    }

    /* The 72px clearance under the fixed mobile header is PADDING ON BODY, not a margin on
       the bar: a top margin on the first in-flow child collapses out of body and moves body
       itself, which leaves the stack measuring correctly while the document ends up 72px
       taller than the viewport. body is border-box, so the padding stays inside its 100dvh. */
    body{
        padding-top:72px!important;
    }

    body.mobile-menu-open .content,
    body.mobile-menu-open.sidebar-collapsed .content,
    body.sidebar-collapsed .content,
    .content{
        padding-top:var(--mp-appbar-gap)!important;
        height:calc(100dvh - var(--mp-appbar-h) - 72px)!important;
    }

    .mp-appbar{
        gap:10px;
        /* lead | search toggle | actions + bell. Three columns, so nothing can wrap onto a
           second row when the title runs long — the title ellipsises instead. */
        grid-template-columns:minmax(0,1fr) auto auto;
    }

    .mp-appbar-tail{
        gap:8px;
    }

    /* Collapsed search: a toggle in the tail row that expands the FIELD over the title,
       so the bar never wraps and the title never fights the control for width. The field
       is one element for the plain and the token control alike, so this is stated once. */
    .mp-appbar-search{
        position:static;
        width:auto;
    }

    .mp-appbar-search-toggle{
        position:relative;
        display:grid;
        place-items:center;
        width:32px;
        height:32px;
        padding:0;
        border:1px solid var(--mp-fill-07);
        border-radius:var(--radius,5px);
        background:var(--mp-fill-035);
        cursor:pointer;
    }

    .mp-appbar-search-toggle .mp-appbar-search-ico{
        position:static;
        opacity:.9;
    }

    /* The magnifier belongs to the toggle here; the opened field is text entry. */
    .mp-appbar-search-field > .mp-appbar-search-ico{
        display:none;
    }

    /* The field's own children (count, clear) travel with it, so nothing here has to
       re-derive their offsets from the bar's gutter. */
    .mp-appbar-search-field{
        position:absolute;
        left:var(--mp-appbar-pad);
        right:var(--mp-appbar-pad);
        top:50%;
        z-index:2;
        width:auto;
        transform:translateY(-50%);
        opacity:0;
        visibility:hidden;
        padding-left:12px;
        transition:opacity .16s ease,visibility 0s linear .16s;
    }

    /* Opaque while open: it lies over the title, which must not read through it. Only the
       delay changes on open, so a reduced-motion duration is never re-stated away here. */
    .mp-appbar-search.is-open .mp-appbar-search-field{
        opacity:1;
        visibility:visible;
        background:var(--panel-solid);
        transition-delay:0s;
    }
}

@media(max-width:560px){
    .mp-appbar-actions .btn .btn-label{
        display:none;
    }

    .mp-appbar-actions .btn{
        padding-left:9px;
        padding-right:9px;
    }
}

/* No !important: the field's open state changes only the DELAY, so nothing later or more
   specific re-states a duration this has to beat. */
@media (prefers-reduced-motion: reduce){
    .mp-appbar,
    .mp-appbar::before,
    .mp-appbar-search-field,
    .mp-appbar-search-field::after,
    .mp-appbar-search-toggle::after{
        transition-duration:.01ms;
    }
}

/* ---- Search token field --------------------------------------------------
   A provider that declares searchable dimensions (MPSearch.register opts.dims) gets a token
   field: <dim>:<value> clauses render as chips in front of the free text, inside the track
   PageHeader renders for every search. Progressive enhancement over the SAME box — the field
   rules above serve both, so a page whose provider declares no dims and one whose provider
   does cannot drift apart, and nothing below restates the box. */

/* The chip is foundation's .mp-chip primitive; the renderer adds it alongside
   .mp-search-chip, which stays as this component's behaviour hook (render() clears
   by it) and as the seam for search-only deltas. There are none today: everything
   the search chip looked like IS the primitive - it was the reference the Tags
   control was unified onto, so the definition moved down a layer rather than being
   copied sideways. --mp-chip-c is set inline per dim (component.js). */

.mp-search-menu{
    position:absolute;
    z-index:120;
    top:calc(100% + 6px);
    left:0;
    right:0;
    max-height:232px;
    overflow-y:auto;
    display:flex;
    flex-direction:column;
    padding:5px;
    border:1px solid var(--mp-fill-10);
    border-radius:10px;
    background:var(--mp-panel-98,var(--mp-surface,#0b1220));
    box-shadow:0 12px 28px var(--mp-sh-20,rgba(2,6,23,.28));
}
.mp-search-menu[hidden]{display:none;}
.mp-search-sugg{
    display:flex;
    align-items:center;
    gap:8px;
    width:100%;
    min-height:0;
    padding:7px 8px;
    border:0;
    border-radius:7px;
    background:none;
    color:var(--mp-ink-strong);
    font:inherit;
    font-size:var(--mp-fs-base,13px);
    font-weight:var(--mp-fw-semibold,600);
    text-align:left;
    cursor:pointer;
}
.mp-search-sugg:hover,.mp-search-sugg.is-active{background:var(--mp-fill-06);}
.mp-search-sugg-dim{
    flex:0 0 auto;
    color:var(--mp-ink-muted);
    font-size:var(--mp-fs-2xs,10px);
    font-weight:var(--mp-fw-heavy,800);
    text-transform:uppercase;
    letter-spacing:var(--mp-ls-wide,0.04em);
}
.mp-search-sugg-dot{flex:0 0 8px;width:8px;height:8px;border-radius:999px;background:var(--mp-fill-14,rgba(148,163,184,.28));}
.mp-search-sugg-text{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* ---- Narrow chrome: the bar's surface and the suggestion menu -------------------------
   The token field's own collapsed-overlay mirror is gone: the field is one element, and the
   narrow block above positions it for every search control. */
@media(max-width:1000px){
    /* The bar keeps its frosted surface at mobile widths; see the ::before rule for why the
       desktop half does not. */
    .mp-appbar::before{
        backdrop-filter:blur(22px);
        -webkit-backdrop-filter:blur(22px);
    }

    .mp-search-menu{
        left:var(--mp-appbar-pad);
        right:var(--mp-appbar-pad);
    }
}

/* ============================================================================
   PRINT — the app shell steps out of the way.

   Everything here is navigation: a fixed sidebar, an app bar of controls, a mobile
   header and a floating add button. None of it can be operated on paper, and the
   sidebar is position:fixed, which prints on the FIRST page only and leaves the
   content indented on every page after it.

   .content lives in foundation, but its margin-left exists for exactly one reason:
   to clear the fixed sidebar this component owns. Undoing that offset therefore
   belongs next to the rule that caused it, so "why does the shell print" has one
   place to look rather than two.

   The app bar's TITLE is kept. It is the only caption a printed page gets, and a
   plan that does not say which plan it is has to be labelled by hand.
   ============================================================================ */
@media print{
  .sidebar,
  .mobile-app-header,
  .mobile-menu-toggle,
  .mobile-menu-backdrop,
  .floating-add-btn,
  .sidebar-toggle,
  .mp-search-menu,
  .mp-logout-throbber-overlay,
  .mp-appbar-search,
  .mp-appbar-search-toggle,
  .mp-appbar-actions,
  .page-header-actions{
    display:none !important;
  }

  /* The column no longer has a fixed sidebar to clear. */
  /* On paper there is no viewport to bound the pane to: a scroll container would print one
     screenful and drop the rest. */
  .content{margin-left:0 !important;padding:0 !important;background:none !important;
           box-shadow:none !important;border-radius:0 !important;
           height:auto !important;overflow:visible !important;}
  html,body{height:auto !important;overflow:visible !important;}

  /* .content transitions margin-left over 220ms, and several surfaces animate on entry.
     A print snapshot taken while any of that is in flight captures the halfway frame -
     a page indented by whatever the sidebar offset happened to be at that instant. There
     is nothing to animate on paper, so nothing animates. */
  *,*::before,*::after{
    transition:none !important;
    animation:none !important;
  }

  /* Sticky resolves against a scroll container the printed document does not have,
     so the bar would otherwise pin itself to the top of page one and float there. */
  .mp-appbar{position:static !important;background:none !important;border:0 !important;}
}

/* ── Logout as a POST ──────────────────────────────────────────────────────
   Logout changes state, so it submits a form rather than following a link: reachable by GET it
   could be triggered from any other page.

   The button is a DIRECT child of .user, exactly where the anchor was, and reaches its form through
   the HTML5 form attribute. An earlier version wrapped it in a <form> with display:contents; that
   kept the layout but WebKit removes such a form from the box tree and then does not submit it, so
   the button silently did nothing in Safari. No wrapper means the structural rules above apply
   unchanged and nothing has to be restated.

   The reset only neutralises what a <button> brings with it — user-agent background, border and
   font. Every visual property still comes from .user-logout above. */
button.user-logout{
    appearance:none;
    -webkit-appearance:none;
    background:none;
    border:0;
    border-top:1px solid var(--mp-fill-065);
    font-family:inherit;
    line-height:inherit;
    cursor:pointer;
}
