/* THE HUB'S SIDE-RAIL CHROME — every rule here is scoped to
 * body[data-global-nav="side"].
 *
 * Split out of global-nav-side.css on 2026-08-26. That file is linked from
 * partials/quaive_head.html on EVERY face, but `data-global-nav` is only ever
 * "side" (hub) or "compact" (One/Pro) — core.platform_modes.MODE_GLOBAL_NAV —
 * so on the personal cloud, which is always compact, these 53KB could never
 * match a single element. They were pure cold-start weight on the exact face
 * we care most about.
 *
 * What did NOT move: the bare `#global-nav-side` / `.gns-*` rules. Those ARE
 * live on compact — global-nav-side.css turns the same element into the phone's
 * bottom tab bar. Only the "side"-attributed rules are provably dead there.
 *
 * The blocks keep their original relative order. They now all sit AFTER the
 * shared file rather than interleaved with it, which can only matter for a
 * side-scoped rule that used to LOSE to an equal-specificity shared rule — and
 * the attribute selector these carry makes equal specificity the rare case.
 */

body[data-global-nav="side"] {
    transition: --gns-active-app-colour var(--gns-transition);
}

body[data-global-nav="side"] #global-nav-side {
    display: flex;
}

@media (width > 768px) {
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side {
        width: var(--gns-width-collapsed);
        /* Fixed semi-transparent white, the same regardless of which app
         * is active (previously a 10% wash of the active app/space colour). */
        background: var(--kik-gns-background, rgba(255, 255, 255, 0.5));
        box-shadow: var(--default-box-shadow);
        /* The rail's own edge. White is a hairline against a light page and
         * the brightest line on the screen against a dark one, so the dark
         * theme states a faint light instead. */
        border-color: var(--kik-gns-collapsed-border, white);
    }
}
@media (width > 768px) {
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-platform-name {
        zoom: 0.001;
        opacity: 0;
    }
    /* The user line shrinks away rather than popping out */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-hub {
        margin-top: 0;
        opacity: 0;
    }
    /* Morph the full wordmark into the brandmark: scale down + slide left so
     * the orange swirl (on the right of the wordmark) arrives roughly where
     * the black mark sits, fading out as the mark fades in — reads as one
     * mark transforming rather than a hard crossfade. */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-logo-full {
        opacity: 0;
        transform: scale(0.30) translate(-125px, -22px);
    }
    /* RTL: the rail (and mark) sit on the right, so slide the wordmark the
     * other way. */
    body[dir="rtl"]:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-logo-full {
        transform: scale(0.9) translateX(82px);
    }
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-logo-mark {
        opacity: 1;
        top: 5px;
        left: 6px;
        /* ink-coloured brandmark, slightly inset like the app glyphs (the
         * dark theme paints the same mark in its own light ink) */
        filter: var(--kik-gns-collapsed-mark-filter, grayscale(1) brightness(0.08));
        transform: scale(0.92);
    }
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-header {
        padding-left: 0;
        padding-right: 0;
        height: 35px;
    }
    /* Collapsed: the logo behaves like an icon tile — same size and
     * radius as the app icons, with the same light hover wash */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-platform-logo {
        width: 38px;
        height: 38px;
        margin-left: 6px;
        border-radius: var(--kik-global-nav-icon-radius);
    }
    @media (hover: hover) {
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-platform-logo:hover {
          background-color: var(--kik-gns-hover-background, rgba(255, 255, 255, 0.5));
      }
    }
    
    /* Home is the active screen while the rail is collapsed (only the
     * tablet-portrait case — on desktop the rail collapses only once an
     * app is on screen). The logo tile then reads as the "current"
     * destination, exactly like a current app icon: solid accent fill,
     * brandmark inverted to white. */
    body[data-global-nav="side"].gns-collapsed:not(:has(#app-space.state-on)) #global-nav-side .gns-platform-logo {
        background-color: var(--colour-accent, var(--gns-accent));
    }

    @media (hover: hover) {
      body[data-global-nav="side"].gns-collapsed:not(:has(#app-space.state-on)) #global-nav-side .gns-platform-logo:hover {
          background-color: var(--colour-accent, var(--gns-accent));
      }
    }
    body[data-global-nav="side"].gns-collapsed:not(:has(#app-space.state-on)) #global-nav-side .gns-logo-mark {
        /* invert the brandmark to solid white (vs the dark-ink filter
         * the inactive collapsed state uses above) */
        filter: brightness(0) invert(1);
    }
}
@media (width > 768px) {
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-section-title {
        zoom: 0.001;
        opacity: 0;
    }
    /* Updates is the dashboard's home surface on desktop — only the
     * mobile bottom bar gets its own Updates tab */
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-item.gns-app-updates {
        display: none;
    }
    /* The utility apps (preferences / administrator / password / help) ride
     * the avatar/personal menu, not the desktop rail — they stay available on
     * the mobile bottom bar's more-apps popover.
     *
     * …unless the user PINNED one. Pinning is an explicit "keep this one
     * click away", and an admin who stars Administrator has said exactly that;
     * silently dropping it made the star a lie. Same data-gns-pinned marker
     * and same reasoning as Team spaces (global-nav-mobile-order.css) —
     * default-hidden, pinned-visible. Unpinned still never shows here, not
     * even while the app is open, since these slugs stay in
     * quaive_tags._GNS_DESKTOP_HIDDEN_SLUGS and so get no open-reveal rule. */
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps > .gns-item:is(
        .gns-app-preferences,
        .gns-app-administrator,
        .gns-app-password,
        .gns-app-help
    ):not(:has([data-gns-pinned])) {
        display: none;
    }
    /* Open-or-pinned rail (the pro/One rail's behaviour): an app that is
     * neither a favourite nor open earns no desktop rail row. The template
     * flags those entries .gns-open-item, and the [data-kik-open] stamp
     * (app-label-state.js) reveals one the moment its app body is on. Both live behind this desktop breakpoint: the
     * mobile bottom bar keeps listing every app, capped by its own
     * nth-child rules. */
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps > .gns-item.gns-open-item {
        display: none;
    }

    /* Two halves: the apps you PINNED, then the ones that are merely open.
     *
     * Done with `order`, not DOM order: the mobile bar's icon cap is
     * nth-child-based, so it counts DOM position and the list must keep the
     * order quaive_tags.global_nav_apps hands over. Flex is what makes `order`
     * work on the rows, and a column flex box lays them out exactly as the
     * block list did. Desktop only — the mobile bar dissolves this same ul
     * with display: contents. */
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps {
        display: flex;
        flex-direction: column;
    }
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps > .gns-item {
        order: 1;
    }
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps > .gns-apps-separator {
        order: 2;
    }
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps > .gns-item.gns-open-item {
        order: 3;
    }
    /* The rule itself: a dim hairline with air either side, hidden until the
     * list's [data-kik-open-items] stamp says something is actually below it (an empty
     * rule floating under the pinned apps reads as a mistake). It is a bare
     * <li> with no row of its own, so it can't be tabbed to or hovered. */
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps > .gns-apps-separator {
        display: none;
        height: 0;
        /* 5px, not 3px: the rule is inset to the ICON TILE's edge, and the
         * tile now starts 5px in (the row's 2px padding + the icon's 3px
         * margin) in both rail states. */
        margin: 8px 5px;
        padding: 0;
        border-top: 2px solid color-mix(in srgb, var(--gns-ink) 10%, transparent);
        pointer-events: none;
    }
    /* …and both come back by what app-label-state.js stamps: [data-kik-open]
     * on a row whose app body is on, [data-kik-open-items] on the list while
     * any open-only row is. These were per-app body:has(#application-body-x
     * .state-on) rules, and Safari did not re-evaluate them when a window
     * closed: the closed app kept its icon (Daniel, 2026-10-29). */
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps > .gns-item.gns-open-item[data-kik-open] {
        display: block;
    }
    body:is([data-global-nav="side"], [data-kik-rail="docked"]) #global-nav-side .gns-apps[data-kik-open-items] > .gns-apps-separator {
        display: block;
    }

    /* Close an open, unpinned app from the EXPANDED rail.
     *
     * Those rows are on the rail only because their app is open, so closing is
     * the one thing you might want from them that the row itself doesn't do —
     * and it takes the row away, which is what makes the × honest here (a
     * pinned row stays put, so it gets no × at all).
     *
     * Different shape from the collapsed rail's close, which swaps the whole
     * tile: here the row keeps its icon and its label, and the × sits at the
     * right end as its own control. The HIT AREA is a square the height of the
     * row, flush to its right edge; the PLATE that appears under the × on
     * hover is inset well inside that — 32 square against the row's 44, more
     * tightly rounded — so it reads as nested inside rather than boxed against
     * it. It appears on row hover, taking the unread counter's place —
     * they share that end of the row. */
    body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item {
        position: relative;
    }
    body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item .gns-link.gns-close {
        display: flex;
        align-items: center;
        justify-content: center;
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        width: 44px;
        height: auto;
        padding: 0;
        background: none;
        opacity: 0;
        transition: opacity var(--gns-transition);
    }
    /* The × and nothing else. The control renders the shared app-icon partial,
     * which carries the app ICON and its LABEL as well — left up, the label
     * sat under the × clipped to its first letters ("Be✕" for Berichten). */
    body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item .gns-close .gns-icon:not(.gns-close-glyph),
    body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item .gns-close .gns-label {
        display: none;
    }
    body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item .gns-close .gns-close-glyph {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 32px;
        height: 32px;
        margin: 0;
        padding: 0;
        border-radius: var(--kik-global-nav-icon-radius);
        background-color: transparent;
        /* Dim through the COLOUR, not opacity — opacity would fade the plate
         * behind it along with the glyph. */
        /* The dim strength is a token: 45% of a dark ink reads on a light
           rail, but 45% of a LIGHT ink sinks into a dark one — a dark theme
           turns this up. */
        color: color-mix(in srgb, var(--gns-ink) var(--kik-gns-dim, 45%), transparent);
        opacity: 1;
        transition: background-color var(--gns-transition), color var(--gns-transition);
    }
    @media (hover: hover) {
        body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item:hover .gns-link.gns-close {
            opacity: 1;
        }
        /* Keep the ROW washed while the pointer is on the ×. The wash lives on
         * .gns-link:hover, and the close control is a SIBLING anchor laid over
         * the launcher — so reaching for the × took the pointer off the
         * launcher and the row went cold underneath the very control it was
         * offering. Paint it from the row's own hover instead. (Not for the
         * active row: its state shows through the coloured icon tile, never a
         * wash — same exception the rule below this one makes.) */
        body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item:not(.current):hover .gns-link.gns-launcher {
            background: var(--kik-gns-hover-background, rgba(255, 255, 255, 0.5));
        }
        /* The × takes the COUNTER's place — they occupy the same end of the
         * row, so with the badge left up the two overlapped into an unreadable
         * knot (an orange 1 with a × through it). Hiding the badge for the
         * duration of the hover also says the right thing: while you are
         * reaching for close, how many unread there are is not the point. */
        body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item:hover .gns-link.gns-launcher .gns-badge {
            display: none;
        }
        /* A long label runs the full width of the row, so the × that fades in
         * over its right end lands ON the last letters ("Videovergadering✕").
         * The label fades out under the control instead of being clipped at a
         * hard edge — a gradient mask over the width the × occupies, so the
         * word trails off into it rather than colliding with it. Only where
         * there IS a × (.gns-open-item) and only on the expanded rail; the
         * collapsed rail has no label to fade. The mask is physical-right in
         * both directions because the control it hides behind is: .gns-close
         * is pinned with right: 0 and is not flipped for RTL. */
        body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item:hover .gns-link.gns-launcher .gns-label {
            mask-image: linear-gradient(to right, #000 calc(100% - 3rem), transparent 100%);
        }
        body:is([data-global-nav="side"]:not(.gns-collapsed):not(:has(#app-space.state-on)), [data-kik-rail="docked"].kik-rail-home) #global-nav-side .gns-apps > .gns-item.gns-open-item .gns-link.gns-close:hover .gns-close-glyph {
            background-color: var(--kik-gns-hover-background, rgba(255, 255, 255, 0.5));
            color: color-mix(in srgb, var(--gns-ink) 85%, transparent);
        }
    }
}
@media (width > 768px) {
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-label {
        flex-grow: 0;
        max-width: 0;
        opacity: 0;
    }
    /* The icon stays LEFT-aligned and the base row's 2px left padding does the
     * centring — it used to be justify-content: center, and that is not an
     * animatable property. It applied on the first frame of the collapse,
     * while the row was still 228px wide, so every icon was flung towards the
     * middle of a row that was about to disappear; the label's max-width then
     * snapped to 0 halfway through and threw them somewhere else again, and
     * the width animation walked them back. That three-step detour is the
     * wobble.
     *
     * Left-aligned there is no detour, and nothing here changes the icon's
     * position at all: every rail slot (38px icon + 3px margins, 26px space
     * letter + 9px margins) is 44px wide, so the row's own 2px + 44px + 2px
     * fills the collapsed rail's 48px content box and centres the slot to the
     * pixel — the same 2px the expanded rail already carries. Only the label's
     * trailing 10px goes, and there is no label here to push. */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-link {
        padding: 0 0 0 2px;
        gap: 0;
    }
    
    /* THE COLLAPSED RAIL'S WASH IS INK, NOT WHITE (Daniel, 2026-11-02: "hover
     * state is missing for all icons on the collapsed rail"). The white wash
     * suits the expanded rail on the desk's glass; on the collapsed rail's
     * own light ground it painted nothing. Every hover and open-menu square
     * below reads the one token, so retinting it here covers them all — the
     * candy box and the hub hopper while their menus are up included. A theme
     * tunes it with --kik-gns-collapsed-hover-background (usugami-dark). */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side {
        --kik-gns-hover-background: var(--kik-gns-collapsed-hover-background, rgba(0, 0, 0, 0.07));
    }

    /* Collapsed rail: hover feedback moves from the row to the icon
     * tile itself (active icons keep their app-colour fill) */
    @media (hover: hover) {
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-link:hover {
          background: transparent;
      }
    }
    @media (hover: hover) {
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-item:not(.current) .gns-link:hover .gns-icon.app-icon {
          background-color: var(--kik-gns-hover-background, rgba(255, 255, 255, 0.5));
      }
    }
    /* An open menu holds the icon square; the row stays bare, as on hover. */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-link[aria-expanded="true"] {
        background: transparent;
    }
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-item:not(.current) .gns-link[aria-expanded="true"] .gns-icon.app-icon {
        background-color: var(--kik-gns-hover-background, rgba(255, 255, 255, 0.5));
    }
    /* Collapsed rail: glyphs go full ink — black on a light rail, the light
     * ink on a dark one (the active app keeps its white-on-app-colour tile,
     * untouched by the :not(.current) scope) */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-item:not(.current) .gns-link:not([aria-current="page"]) .gns-icon.app-icon * {
        fill: var(--kik-gns-collapsed-glyph, #000) !important;
    }
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-item:not(.current) .gns-link:not([aria-current="page"]) .gns-icon.app-icon svg #dots {
        fill: transparent !important;
    }
    /* Rail line icons: colour the stroke instead of the fill */
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-item:not(.current) .gns-link:not([aria-current="page"]) .gns-icon.rail-icon * {
        fill: color-mix(in srgb, var(--kik-gns-collapsed-glyph, #000) var(--app-icon-fill-opacity, 0%), transparent) !important;
        stroke: var(--kik-gns-collapsed-glyph, #000) !important;
    }
}
/* HOVER: exactly what an app row does, in both rail states.
 *
 * EXPANDED — the whole row highlights, from the shared ``.gns-link:hover``
 * wash; nothing here overrides it.
 *
 * COLLAPSED (or any app open) — the row wash is off rail-wide and the feedback
 * moves to the icon: a rounded-corner square behind the mark at the app tile's
 * own geometry (38px, 10px radius, 7% ink). Painted as an ::after rather than
 * a background on the avatar, because the mark is a smaller circle sitting in
 * the middle of that slot; ``inset: -6px`` on the 26px circle lands exactly on
 * the app tile's footprint. */
@media (hover: hover) {
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-item.gns-space .gns-link:hover .gns-space-avatar::after {
        content: "";
        position: absolute;
        inset: -6px;
        border-radius: var(--kik-global-nav-icon-radius);
        background-color: var(--kik-gns-hover-background, rgba(255, 255, 255, 0.5));
        z-index: -1;
    }
    /* The open space is the ONE row that skips the square — its avatar is
     * already filled, and a wash behind a filled tile just muddies it. But
     * only while the space is actually in the foreground: an open space with
     * another app on top keeps its ``current`` class and renders like any
     * other row (same gate the accent-label rule uses), so with the row wash
     * off in this state it had no hover feedback left at all. */
    body:has(#app-space.state-on:is(.active-app-workspaces, .active-app-team-spaces)) #global-nav-side .gns-item.gns-space.current .gns-link:hover .gns-space-avatar::after {
        display: none;
    }
}
@media (width > 768px) {
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-badge {
        position: absolute;
        top: 4px;
        right: 2px;
    }
}

/* Collapsed rail: labels become tooltips (CSS-only, from data-gns-label) */
@media (width > 768px) {
    @media (hover: hover) {
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-link[data-gns-label]:hover::after,
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-hub[data-gns-label]:hover::after,
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-platform-logo[data-gns-label]:hover::after {
          content: attr(data-gns-label);
          position: fixed;
          left: calc(var(--gns-left) + var(--gns-width-collapsed) + 6px);
          background: var(--gns-background);
          color: var(--gns-ink);
          font-size: 0.8em;
          font-weight: 400;
          padding: 4px 10px;
          border-radius: 4px;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
          white-space: nowrap;
          z-index: 1000;
          pointer-events: none;
      }
      /* A PINNED app says so in its tooltip — the row itself keeps showing the
       * app icon at every state, hover included. (An earlier take swapped the
       * icon for a pin glyph on hover; it cost you the icon for a fact that
       * isn't about what the click does.) The marker rides the tooltip's
       * ``content`` rather than data-gns-label so the attribute stays the plain
       * app name. A literal pushpin character, not the fontello glyph: the
       * pseudo-element renders the label in the UI font, and one content string
       * can only have one font-family. */
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-link[data-gns-label][data-gns-pinned]:hover::after {
          content: "\1F4CC\00a0" attr(data-gns-label);
      }
    }

    /* The platform logo is a tall 67px block, so the fixed pseudo's static
     * vertical position falls to the block's bottom — well below the small
     * 38px collapsed brandmark tile. Anchor it to the rail top instead so it
     * centres on the swirl tile (rail padding-top 22px + half the 38px tile). */
    @media (hover: hover) {
      body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-platform-logo[data-gns-label]:hover::after {
          top: calc(var(--gns-top) + 30px);
      }
    }
}

/* ------------------------------------------------------------------ */
/* Chrome swap: what Side mode does to the rest of the page            */
/* ------------------------------------------------------------------ */

/* The header is gone in Side mode, so every layout offset derived
 * from its height collapses to zero (0px, not 0 — unitless zero is
 * invalid inside calc() additions). The dashboard's top margin made
 * room under that header, so it goes too. */
body[data-global-nav="side"] {
    --global-header-height: 0px;
    --dashboard-margin-top: 0px;
}
/* The dashboard sits flush to the top in Side mode — no header to
 * clear. dashboard.css declares --dashboard-margin-top ON
 * .quaive-dashboard itself at large widths (80px, or 40px via
 * #portal:not(:has(#portal-tabs))), which out-ranks the inherited
 * body-level reset, so re-zero it on the element with matching/higher
 * specificity. The second selector beats the (2,1,0) 40px rule;
 * together they cover every width. */
body[data-global-nav="side"] #portal .quaive-dashboard,
body[data-global-nav="side"] #portal:not(:has(#portal-tabs)) .quaive-dashboard {
    --dashboard-margin-top: 0px;
}

/* Side-mode dashboard goes full-bleed: drop the 1280px container cap so
 * the portlet grid uses the whole canvas (the apps/favourites portal
 * sections keep their cap — this is scoped to .section-dashboard). The
 * content box also becomes a size container (gns-dash) so the rules below
 * react to the REAL available width, not the viewport — the rail inset
 * varies (expanded on Home, the 50px strip in tablet-portrait), so a
 * viewport breakpoint would mis-judge how many columns actually fit. */
body[data-global-nav="side"] #portal.section-dashboard #portal-content {
    max-width: none;
    --pat-container-width: none;
    container: gns-dash / inline-size;
}
/* Stop capping the column count: instead of a fixed 2fr/3-track template,
 * auto-fill spawns one more ~320px lane every time another fits, so the
 * portlet grid keeps gaining columns as the canvas widens. */
body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard {
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    grid-auto-flow: row dense;
    /* AN IMPLICIT LANE IS A FULL LANE. Zmanim and Apps are pinned to
     * ``grid-column: 3`` from container width 1000px on, but auto-fill only
     * spawns a third 320px lane once 3*320 + 2*30 = 1020px is available — so
     * in the 20px band between the two, column 3 does not exist in the
     * explicit grid and every portlet placed there landed in an IMPLICIT
     * track sized by ``grid-auto-columns: auto``, i.e. min-content: a 42px
     * ribbon of clipped text down the right edge (Daniel, screenshot,
     * 2026-08-23). Sizing implicit tracks like the explicit ones makes that
     * band a normal three-lane dashboard, and keeps any future drift between
     * a pin and the lane arithmetic from collapsing a column again. */
    grid-auto-columns: minmax(0, 1fr);
}
/* The activity stream always lives in the first column — pin its column
 * line to 1 so the masonry/auto-flow algorithm can't drift it into
 * another lane (it would otherwise seek the shortest one). The base
 * span-every-row pin is dropped (no fixed track count to span). */
body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-portlet#portlet-updates {
    grid-column: 1;
    grid-row: auto;
    align-self: start;
}
/* Native masonry where the browser has it: portlets pack up into the
 * shortest lane rather than aligning to row tracks. The widgets' own box
 * (.quaive-dashboard-widgets, portal/dashboard.html) steps out of the way —
 * display: contents makes every widget a lane item of the board itself.
 * Without grid-lanes the box stays, and lays the widgets out as columns
 * (the @supports not block at the end of this section). */
@supports (display: grid-lanes) {
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard {
        display: grid-lanes;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-dashboard-widgets {
        display: contents;
    }
}
/* The lane width (320px) + gutter (20px) sets the breakpoints:
 *   1 lane  : <740px    (size 1, "mobile") — the stream alone
 *   2 lanes : 740–999   (size 2) — the stream, then the widgets' narrower lane
 *   3 lanes : 1000–1339 (size 3) …and +340px per lane after that.
 * Two lanes would fit from 660px, but not usefully (Daniel, 2026-10-06): the
 * feed then got no more than the widgets beside it. From 740px it keeps at
 * least 420px next to a 300px widget lane.
 *
 * From three lanes the activity stream is the prominent wide column, spanning
 * the first two lanes with the widgets to its right. It is first in the
 * markup, so it always starts at lane 1 and at the top: no widget is ever put
 * above it, at any width. */
@container gns-dash (width >= 1000px) {
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-portlet#portlet-updates {
        grid-column: 1 / span 2;
    }
}
/* Two lanes (740–999): not two EQUAL lanes. Two 1fr lanes gave a widget as
 * much room as the feed — at 670px a 325px Zmanim card beside a 325px stream
 * (Daniel, 2026-10-06: the board went to "very wide portlets" too soon, at
 * the feed's cost). The widget lane takes the shared aside width
 * (--kik-dash-aside, dashboard.css: a third, 300–400px) and the stream the
 * rest, as the three-lane tier already shares it out. */
@container gns-dash (740px <= width < 1000px) {
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard {
        grid-template-columns: minmax(0, 1fr) var(--kik-dash-aside);
    }
}

/* Size 1 (one lane, "mobile"): only the activity stream. Everything else,
 * Zmanim and News included, is hidden — and the lone activities portlet
 * sheds its card chrome to read as the bare feed (full-width column, no
 * title, no panel background/shadow/border/padding). Container-query
 * based, so it holds whenever the dashboard column is this narrow — the
 * mobile bottom-bar layout AND a desktop side-nav with the rail expanded. */
@container gns-dash (width < 740px) {
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-dashboard-widgets {
        display: none;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard {
        grid-template-columns: 1fr;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-portlet {
        grid-column: 1 / -1;
        background: transparent;
        box-shadow: none;
    }
    body[data-global-nav="side"] #portlet-updates .well-title-group,
    body[data-global-nav="side"] #portlet-activity-stream .well-title-group {
        display: none;
    }
    /* No card chrome and NO padding, on the portlet or its body: with nothing
     * above it the stream IS the page, so it starts at the top edge.
     *
     * Qualified through the dashboard (#portal.section-dashboard
     * .quaive-dashboard) only to outrank the ``padding-top: 20px`` in the
     * ≥769px MEDIA block further down. That one is a VIEWPORT query and this
     * is a CONTAINER query, so on a narrow tablet portrait — viewport ≥769,
     * dashboard column <740 because the expanded rail eats the width — both
     * matched at equal specificity and the later one won, dropping a 20px gap
     * above a feed that has nothing to be spaced from. */
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-updates,
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-activity-stream {
        padding: 0;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-updates .panel-content,
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-activity-stream .panel-content {
        background: transparent;
        box-shadow: none;
        border: none;
        padding: 0;
    }
    /* The standalone compose + comment boxes carry ``margin: 2rem auto`` from
     * base.css so they get breathing room on a roomy board. Here the card
     * padding around them is gone, so that fixed 2rem is measured from the
     * screen edge instead of from a card, and the `auto` centring has nothing
     * left to centre against. Percentages instead: the inset scales with the
     * column, which at this width IS the screen, so the box keeps the same
     * proportion of air around it from a narrow phone to a tablet portrait —
     * where a fixed rem is generous on one and cramped on the other. */
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard .update-social.post-box:not(.post *),
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard .update-social.comment-box:not(.post *) {
        margin: 5% 5% 3%;
        max-width: 100%;
    }
    /* No air under the post box of its own (Daniel, 2026-09-29: "less
       whitespace below the post box"): the stream under it opens with its own
       15px, and the two added up to 25. */
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard .update-social.post-box:not(.post *) {
        margin-block-end: 0;
    }
}

/* Mobile (the bottom-bar breakpoint): the stream and nothing above it.
 *
 * A VIEWPORT query, deliberately, where the tiers below are container queries.
 * With the rail dissolved into a bottom bar the dashboard column gets the whole
 * screen, so a phone in landscape measures wide enough to land in the two-lane
 * tier and grew a card above the feed. On a phone the feed is the page whatever
 * the column happens to measure. */
@media (width <= 768px) {
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-dashboard-widgets {
        display: none;
    }
    /* …and the same chrome the one-lane tier sheds. Deliberately repeated
     * rather than shared: the tier is a CONTAINER query and this is a VIEWPORT
     * one, and CSS has no way to say "either of these". Without it a phone whose
     * dashboard column happens to measure ≥740 — the bar gives the board the
     * whole screen — got the mobile bottom bar wearing the tablet's card: a
     * well title, a panel background and padding around a feed that is the
     * whole page. Keep the two blocks in step. */
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard {
        grid-template-columns: 1fr;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-portlet {
        grid-column: 1 / -1;
        background: transparent;
        box-shadow: none;
    }
    body[data-global-nav="side"] #portlet-updates .well-title-group,
    body[data-global-nav="side"] #portlet-activity-stream .well-title-group {
        display: none;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-updates,
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-activity-stream {
        padding: 0;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-updates .panel-content,
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard #portlet-activity-stream .panel-content {
        background: transparent;
        box-shadow: none;
        border: none;
        padding: 0;
    }
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard .update-social.post-box:not(.post *),
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard .update-social.comment-box:not(.post *) {
        margin: 5% 5% 3%;
        max-width: 100%;
    }
    /* No air under the post box of its own (Daniel, 2026-09-29: "less
       whitespace below the post box"): the stream under it opens with its own
       15px, and the two added up to 25. */
    body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard .update-social.post-box:not(.post *) {
        margin-block-end: 0;
    }
}

/* A tablet (the rail beside the board, one lane): the post box sits at the
 * top of the column, level with the rail's header, not 5% down it. On the
 * phone that 5% is air under the status bar and the hub's name; next to the
 * rail it read as the box sliding down the page (Daniel, 2026-09-29). */
@media (width > 768px) {
    @container gns-dash (width < 740px) {
        body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard .update-social.post-box:not(.post *) {
            margin-block-start: 0;
        }
    }
}

/* WITHOUT grid-lanes, the widgets are laid out in CSS COLUMNS beside the
 * stream — the fallback the tiles and the activity stream already use.
 *
 * A plain row grid cannot do it: every lane shares the same row tracks, so a
 * short widget next to a tall one leaves a hole under it, and the old fix
 * (the stream spanning a hundred rows, the first widgets pinned into lane 3)
 * only held for the widgets it named. Here the widgets' own box takes every
 * lane right of the stream, in the first row, and flows the widgets down its
 * columns in board order: the browser balances them and ``break-inside``
 * keeps a card whole. Its column width is the lane width, so it holds as
 * many columns as there are lanes beside the stream. */
@supports not (display: grid-lanes) {
    @container gns-dash (width >= 740px) {
        body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-portlet#portlet-updates {
            grid-row: 1;
        }
        body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-dashboard-widgets {
            display: block;
            grid-row: 1;
            grid-column: 2 / -1;
            align-self: start;
            column-width: 320px;
            column-gap: var(--grid-gutter);

            > .quaive-portlet {
                break-inside: avoid;
                margin-block-end: var(--grid-gutter);
            }
        }
    }
    @container gns-dash (width >= 1000px) {
        body[data-global-nav="side"] #portal.section-dashboard .quaive-dashboard > .quaive-dashboard-widgets {
            grid-column: 3 / -1;
        }
    }
}

/* App sidebars span the full app-body height — the header is gone, so
 * the height calc that subtracted it no longer applies. */
body[data-global-nav="side"] #app-space .sidebar.left {
    height: 100%;
}

/* Rail mode: paint the app sidebars with the light app colour — a pale wash of
 * the active app's hue — so they read as part of the app, not the chrome.
 * Overriding the shared --sidebar-background-colour recolours both the sidebar
 * and its content (both consume that variable). */
body[data-global-nav="side"] .application-body {
    --sidebar-background-colour: color-mix(in srgb, var(--app-colour, white) 15%, white);
}

/* Legacy top chrome disappears while Side is active */
body[data-global-nav="side"] #global-header,
body[data-global-nav="side"] #portal-tabs,
body[data-global-nav="side"] #tabs-bar {
    display: none !important;
}

/* base.html's default #portal-content lazy-loads the APPS PORTAL (grid + its
 * launcher toolbar) as the fallback landing, so on any full-page app load —
 * e.g. the redirect after saving language prefs — that portal would otherwise
 * flash in #portal behind the open app. Home in Side mode is the dashboard,
 * reached from the rail, so suppress it.
 *
 * Scoped to the apps portal's OWN grid (#apps-listing), not to
 * `.kik-apps-grid` at large: the dashboard's Apps portlet is a grid in #portal
 * too, and the blanket rule silently blanked it — a card with a header and no
 * tiles, with nothing in the markup to explain why. */
body[data-global-nav="side"] #portal #apps-listing,
body[data-global-nav="side"] #portal #app-launcher-toolbar {
    display: none !important;
}
/* The header drop-shadow #app-space paints along its top edge belongs
 * to the hidden header — without this it floats as a stray gradient. */
body[data-global-nav="side"] #app-space:not(.injecting)::before {
    opacity: 0 !important;
}

/* Content stays at left: 0 — the rail is superimposed on top of it,
 * exactly like the workspace-tabs column floats over workspace pages.
 * top: 0 because the hidden header no longer needs headroom. */
body[data-global-nav="side"] #app-space,
body[data-global-nav="side"] #portal {
    top: 0;
    margin-left: 0;
}

@media (width > 768px) {
    /* The portal content clears the full rail */
    body[data-global-nav="side"] #portal {
        padding-left: calc(var(--gns-left) + var(--gns-width));
        box-sizing: border-box;
    }
    /* …but a collapsed rail (tablet-portrait Home, or any open app)
     * shrinks to the 50px icon strip — the portal reclaims that width
     * and only clears the strip, so it never sits narrow beside a gap
     * where the expanded rail used to be. Tracks the same trigger as the
     * rail-width rule above. */
    body[data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)) #portal {
        padding-left: calc(var(--gns-left) + var(--gns-width-collapsed));
    }
    /* Apps (collapsed rail): every app body adopts the workspaces-app
     * layout recipe — the sidebar's content padding and the document
     * body's left distance both reserve the 50px rail, exactly as
     * #application-body-workspaces does for its own tabs column. The
     * stock aside.sidebar/.document-body rules in all.css consume
     * these vars; nothing else is touched. */
    body[data-global-nav="side"] #app-space .application-body {
        /* same width the workspaces app gives its sidebar (475px vs the
         * 400px platform default) */
        --sidebar-width: 475px;
        /* re-declared HERE (like #application-body-workspaces does) so
         * the calc picks up the 475px width — the :root default already
         * resolved against 400px and inherits as that fixed value */
        --document-body-left-distance-sidebar-open: calc(var(--sidebar-width) + var(--sidebar-margin-left, 15px));
        --sidebar-padding:
            var(--sidebar-padding-top)
            var(--sidebar-padding-right)
            var(--sidebar-padding-bottom)
            calc(var(--gns-width-collapsed) + 1.5 * var(--sidebar-padding-left));
        --document-body-left-distance: calc(var(--gns-width-collapsed) + var(--sidebar-padding-left));
        /* Closed sidebars keep a visible strip under the rail, exactly
         * like the workspaces app: its -397px against a 475px sidebar
         * leaves ~78px = tabs column + inset. Here that strip is the
         * rail width + the rail's left inset, whatever the app's
         * sidebar width. */
        --sidebar-margin-left-closed: calc(var(--gns-width-collapsed) + var(--gns-left) - var(--sidebar-width, 400px));
    }
    /* Large sidebars get the bigger left padding the workspaces app
     * uses (#application-body-workspaces.sidebar-large:
     * --sidebar-padding-left-large = 85px + padding) so the content
     * clears the rail. */
    body[data-global-nav="side"] #app-space .application-body.sidebar-large {
        --sidebar-padding-left-large: calc(85px + var(--sidebar-padding-left, 25px));
    }
    /* (No arrival slide: an app's sidebar is simply there when the app is
     * — Daniel, 2026-10-03. It slid in over a sidebar already drawn.) */
    /* The visible strip of a closed sidebar is chrome, not content —
     * fade its contents out and let clicks fall through (the stock
     * .sidebar-content transition animates the fade). */

    body[data-global-nav="side"] aside.sidebar.left .sidebar-content {
        transition: all var(--gns-transition);
        
        > * {
            transition: all var(--gns-transition);
        }
    }

    body[data-global-nav="side"] #app-space .application-body.sidebar-left-closed aside.sidebar.left #sidebar-content,
    body[data-global-nav="side"] #app-space .application-body.sidebar-left-closed aside.sidebar.left .sidebar-content {
        /* opacity: 0; */
        pointer-events: none;

        > * {
            opacity: 0;
            /* opacity alone is NOT enough: a direct child with
             * display: contents (the workspace-host sidebars wrap their
             * toolbar + listing in a pat-form rendered that way) generates
             * no box, so opacity on it composites nothing and its children
             * stay fully visible — the sidebar toolbar's white pills kept
             * peeking out around the rail on every closed team-space page.
             * visibility is INHERITED, so it reaches through a boxless
             * wrapper; being discretely animatable it also rides the same
             * transition instead of snapping. */
            visibility: hidden;
        }
    }
    /* Dashboard updates portlet: no well header (the rail/logo is the
     * navigation), just the feed.
     * Covers the lean id (portlet-updates) and classic
     * (portlet-activity-stream). */
    body[data-global-nav="side"] #portlet-updates .well-title-group,
    body[data-global-nav="side"] #portlet-activity-stream .well-title-group {
        display: none;
    }
    /* Messages Element iframe — same insets as the calendar canvas
     * below (its stock rule uses the 15px sidebar margins). */
    body[data-global-nav="side"] #app-space .application-body.application-messages .pat-element {
        top: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        right: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        bottom: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        left: var(--sidebar-padding-left, 25px);
    }
    /* Mail webmail iframe — same inset "card" box as the Messages Element
     * iframe above so the two full-bleed iframe apps line up in hub mode.
     * (Mail has no base absolute rule, so set position here too; the default
     * is the full-bleed width/height:100% from apps.css.) */
    body[data-global-nav="side"] #app-space .application-body.application-mail .mail-webmail-frame {
        position: absolute;
        top: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        right: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        bottom: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        left: var(--sidebar-padding-left, 25px);
        width: -webkit-fill-available;
        height: -webkit-fill-available;
        /* Same card chrome as the generic `.pat-element iframe` (workspace
         * chat / office editors): rounded corners + the standard drop shadow. */
        border-radius: var(--border-radii);
        box-shadow: var(--default-box-shadow);
    }
    /* Calendar canvas — same inset recipe as the workspace-chat
     * Element iframe (#application-body-workspaces .pat-element
     * iframe): 27.5px top/right/bottom, 25px left, card chrome. */
    body[data-global-nav="side"] #app-space .application-calendar .pat-calendar#calendar-app {
        position: absolute;
        top: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        right: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        bottom: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        left: var(--sidebar-padding-left, 25px);
        border-radius: var(--border-radii, 8px);
        box-shadow: var(--default-box-shadow);
    }
    /* The workspaces host reserves the SAME 50px for its own tabs
     * column — push its tabs right of the global rail and widen the
     * insets so both columns fit side by side. */
    body[data-global-nav="side"] #app-space #application-body-workspaces {
        --workspace-tabs-left: calc(var(--gns-left) + var(--gns-width-collapsed));
        --workspace-tabs-left-closed: calc(var(--gns-left) + var(--gns-width-collapsed));
        --sidebar-padding:
            var(--sidebar-padding-top)
            var(--sidebar-padding-right)
            var(--sidebar-padding-bottom)
            calc(2 * var(--gns-width-collapsed) + 1.5 * var(--sidebar-padding-left));
        --document-body-left-distance: calc(2 * var(--gns-width-collapsed) + var(--sidebar-padding-left));
    }
}

@media (width > 768px) {
    
    /* Collapsed-rail label tooltips flip to the rail's left side (match
     * the LTR selector breadth incl. the :has() collapse variant). */
    @media (hover: hover) {
      body[dir="rtl"]:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-link[data-gns-label]:hover::after,
      body[dir="rtl"]:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-hub[data-gns-label]:hover::after,
      body[dir="rtl"]:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-platform-logo[data-gns-label]:hover::after {
          left: auto;
          right: calc(var(--gns-left) + var(--gns-width-collapsed) + 6px);
      }
    }
    /* Collapsed logo tile insets from the right edge; the brandmark
     * keeps its 5px inset measured from the right. */
    body[dir="rtl"]:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-platform-logo {
        margin-left: 0;
        margin-right: 6px;
    }
    body[dir="rtl"]:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side .gns-logo-mark {
        left: auto;
        right: 5px;
    }
    /* The portal clears the rail on the right instead */
    body[dir="rtl"][data-global-nav="side"] #portal {
        padding-left: 0;
        padding-right: calc(var(--gns-left) + var(--gns-width));
    }
    /* The --sidebar-padding shorthand is physical (top right bottom
     * left): the rail inset moves from the left to the right component,
     * mirroring the workspaces-app RTL recipe in _sidebar.scss. */
    body[dir="rtl"][data-global-nav="side"] #app-space .application-body {
        --sidebar-padding:
            var(--sidebar-padding-top)
            calc(var(--gns-width-collapsed) + 1.5 * var(--sidebar-padding-left))
            var(--sidebar-padding-bottom)
            var(--sidebar-padding-right);
    }
    body[dir="rtl"][data-global-nav="side"] #app-space #application-body-workspaces {
        --sidebar-padding:
            var(--sidebar-padding-top)
            calc(2 * var(--gns-width-collapsed) + 1.5 * var(--sidebar-padding-left))
            var(--sidebar-padding-bottom)
            var(--sidebar-padding-right);
    }
    /* Messages Element iframe + calendar canvas: mirror the wide rail
     * inset onto the right side. */
    body[dir="rtl"][data-global-nav="side"] #app-space .application-body.application-messages .pat-element,
    body[dir="rtl"][data-global-nav="side"] #app-space .application-calendar .pat-calendar#calendar-app {
        left: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
        right: var(--sidebar-padding-left, 25px);
    }
}
/* In the popover, show only what the rail dropped. The mobile bar drives the
 * same popover through its nth-child mirrors, so this desktop-only marker
 * applies above the mobile breakpoint. */
@media (width > 768px) {
    #global-nav-side .gns-more-popover .gns-more-app.gns-more-app-railed {
        display: none;
    }
    /* …and dock the popover beside the RAIL. The base rules park it over the
     * mobile bar (fixed to the bottom, spanning the viewport width), which is
     * nowhere near the desktop trigger. Cap the height and let the popover
     * itself scroll: it is the overflow surface, so it may legitimately be
     * longer than the rail that couldn't hold its contents. */
    body[data-global-nav="side"] .gns-more-popover {
        /* rail's right edge + a 15px gap */
        left: calc(var(--gns-width-collapsed, 64px) + var(--gns-left, 15px) + 15px);
        right: auto;
        bottom: calc(var(--gns-left, 15px) + 48px);
        width: min(280px, calc(100vw - 120px));
        max-height: 70vh;
        overflow-y: auto;
    }
    /* Apps only on tablet + desktop. The popover carries a mirror of the
     * personal menu (org switcher, personal cloud, Preferences, Log out)
     * because on PHONES it doubles as the user menu — the avatar trigger is
     * hidden at that width. Above it the avatar is back in the header, so
     * those entries would be a second copy; here the popover is purely the
     * rail's app overflow. */
    body[data-global-nav="side"] .gns-more-popover > .pat-menu-list {
        display: none;
    }
    /* One column, not two. The two-track grid is a phone affordance — the
     * popover spans the viewport there, so pairing the apps keeps it short.
     * Beside the rail it is a narrow panel, so a single column reads as a
     * plain list and the labels stop competing for half-width tracks. */
    body[data-global-nav="side"] .gns-more-popover .gns-more-list {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* The Home dashboard's back-to-landing button is a mobile-only affordance
 * in Side mode: on desktop the rail's Home button already returns there,
 * so hide it there. (On mobile the rule below re-shows it, restyled as a
 * toolbar icon.)
 *
 * The in-app .document-body back button is NOT hidden on desktop: the rail
 * only switches apps, it can't pop a detail view back to the list it lives
 * in (e.g. a Library document back to the Library), so that toolbar
 * pat-back-button has to stay visible at every width. */
@media (width > 768px) {
    body[data-global-nav="side"] #portal .pat-toolbar .pat-back-button {
        display: none !important;
    }
}

/* ------------------------------------------------------------------ */
/* Reduced motion                                                      */
/* ------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
    #global-nav-side,
    #global-nav-side .gns-platform-logo img,
    #global-nav-side .gns-hub,
    #global-nav-side .gns-hub-name,
    #global-nav-side .gns-platform-name,
    #global-nav-side .gns-section-title,
    #global-nav-side .gns-label,
    #global-nav-side .gns-link,
    #global-nav-side .gns-icon.app-icon,
    body[data-global-nav="side"] #app-space,
    body[data-global-nav="side"] #portal {
        transition: none;
    }
    body[data-global-nav="side"] #app-space .application-body.sidebar-left-open aside.sidebar.left {
        animation: none;
    }
}

/* ------------------------------------------------------------------ */
/* Phones: HUB uses its own sidebar insets; One/Pro fill the screen        */
/* ------------------------------------------------------------------ */
/*
 * The mobile bar, the panes and the portal are shared by both faces (the
 * ≤768px section above matches "compact" alongside "side"), so the app shell
 * measures the same in either mode — with ONE exception, here.
 *
 * Compact zeroes the sidebar insets: a DESKTOP decision that reclaims the
 * space the tabs bar used to occupy. Hub's bar FLOATS 10px off the screen
 * edge, so a sidebar running the full height would pass behind it — hub keeps
 * its 15px inset.
 *
 * One and Pro do not: their dock is flush to the bottom edge now, and the
 * sidebar's own content already reserves the bar's footprint as padding (the
 * .sidebar-content rule above). An inset there only left a strip of backdrop
 * under the list — Daniel, 2026-08-12: the sidebar's height IS the viewport.
 *
 * Last in the file on purpose: the rules it overrides carry the same
 * specificity, so source order is what decides.
 */
@media (width <= 768px) {
    body[data-global-nav="side"] .sidebar,
    body[data-global-nav="side"] .application-body {
        --sidebar-margin-top: var(--sidebar-margin);
        --sidebar-margin-bottom: var(--sidebar-margin);
        --sidebar-margin-left: var(--sidebar-margin);
    }
}
/* A TABLET'S SIDEBAR IS NARROWER (Daniel, 2026-09-29: "reduce the standard
 * sidebar width on tablet size"). 475px of a 1000px screen left the document
 * beside it barely wider than the column; from the phone boundary up to a
 * large screen the standard sidebar is 360px instead ("even a bit narrower"
 * than the platform's 400px). Only
 * the width is restated: the document body's distance and the closed
 * sidebar's parking are calcs of it on the same element and follow, and
 * ``--sidebar-width-normal`` is what a closed LARGE sidebar parks by
 * (libraries/kikaron/sidebar). Below 769 the phone's own sidebar rules. */
@media (min-width: 769px) and (max-width: 1279px) {
    body[data-global-nav="side"] #app-space .application-body {
        --sidebar-width: 360px;
        --sidebar-width-normal: 360px;
    }
}
/* THE HUB'S PARKED COLUMN FITS ROUND THE RAIL WITH EQUAL AIR (Daniel,
   2026-10-21: "doing the sidebar is closed, there should be equal space all
   around"). Parked at the rail's width plus its inset, the card ended flush
   with the rail's right edge; it parks on --kik-hub-parked-edge instead
   (global-nav-side.css), which leaves the same air to the right of the rail
   as above, below and to the left of it. */
@media (width > 768px) {
    body[data-platform-mode="hub"][data-global-nav="side"] #app-space .application-body {
        --sidebar-margin-left-closed: calc(var(--kik-hub-parked-edge) - var(--sidebar-width, 400px));
    }
}

/* ------------------------------------------------------------------ */
/* THE DOCKED RAIL - Pro, and One's "Navigation: Side"                 */
/* ------------------------------------------------------------------ */
/* body[data-kik-rail="docked"] (body_mode_attrs.html) is this rail, markup and
 * states alike: expanded while the desk is in front (body.kik-rail-home,
 * app-label-state.js), collapsed while an app is - every rule above names both
 * states. What follows are only the parameters: flush to the screen edge, a
 * hairline instead of a floating card, the app colour on the line icons, the
 * content beside the rail instead of under it. Desktop only: on the
 * phone the same element is the compact faces' bottom bar. */

/* The bar never counted Mail and Monitor (!important: element_overlay.js
 * writes an inline display). */
@media (width <= 768px) {
    #global-nav-side :is(.kik-mail-unread-badge, .kik-monitor-problem-badge) {
        display: none !important;
    }
}

@media (width > 768px) {
    body[data-kik-rail="docked"] {
        --gns-left: 0px;
        --gns-top: 0px;
        --gns-bottom: 0px;
        --gns-width-collapsed: 60px;
        --global-header-height: 0px;
    }
    /* 6px a side keeps every icon on one line in both states: the row's 2px
     * and the tile's 3px put it 11px in, dead centre of the collapsed 60. */
    body[data-kik-rail="docked"][data-global-nav] #global-nav-side {
        display: flex;
        padding: 20px 6px;
        border: 0;
        border-radius: 0;
        box-shadow: 0 0 0 1px color-mix(in srgb, var(--gns-ink) 10%, transparent);
        transition: width var(--gns-transition);
    }
    /* Coloured icons: an app row's line glyph in the app's own colour. */
    body[data-kik-rail="docked"] #global-nav-side .gns-apps > .gns-item {
        --kik-gns-glyph: var(--app-colour);
        --kik-gns-collapsed-glyph: var(--app-colour);
    }

    /* The content stands beside the rail, at whichever width it has. */
    body[data-kik-rail="docked"][data-global-nav] :is(#app-space, #portal) {
        left: var(--gns-width-collapsed);
        top: 0;
        transition: left var(--gns-transition), right var(--gns-transition);
    }
    body[data-kik-rail="docked"][data-global-nav].kik-rail-home :is(#app-space, #portal) {
        left: var(--gns-width);
    }
    body[dir="rtl"][data-kik-rail="docked"][data-global-nav] :is(#app-space, #portal) {
        left: 0;
        right: var(--gns-width-collapsed);
    }
    body[dir="rtl"][data-kik-rail="docked"][data-global-nav].kik-rail-home :is(#app-space, #portal) {
        right: var(--gns-width);
    }
    body[data-kik-rail="docked"] #global-header,
    body[data-kik-rail="docked"] #main::after {
        display: none !important;
    }
    /* App sidebars wear the rail's hairline, and lose it when closed. */
    body[data-kik-rail="docked"] #app-space aside.sidebar.left {
        --default-box-shadow: 0 0 0 1px color-mix(in srgb, var(--gns-ink) 10%, transparent);
    }
    body[data-kik-rail="docked"] #app-space .application-body.sidebar-left-closed aside.sidebar.left {
        box-shadow: 0 0 0 transparent;
    }
}

/* AIR BETWEEN THE RAIL'S ROWS (Daniel, 2026-11-02: "keep 2px or so space
   between items so that hover/current state items don't touch"): two hovered
   or current rows, or a row and the square of an open menu next to it, read
   as one slab when they meet. The side rail only; the phone's bar is a row. */
@media (width > 768px) {
    #global-nav-side ul > .gns-item + .gns-item {
        margin-top: 2px;
    }
}

/* THE RAIL'S INK IS PURE BLACK OR PURE WHITE (Daniel, 2026-11-02: "make the
   text label in the rail pure black or pure white, depending on what
   contrasts the best with the current background"). --kik-rail-ground is
   what the rail is seen against: its own ground, or — the docked rail on the
   desk, over a wallpaper — the stretch of picture under it
   (apps-group.js samples it) seen through the rail's glass. The ink takes
   the launcher names' switch (dashboard/styling/all.css): perceptual
   lightness, with a saturated ground carrying white sooner. The labels only
   (they were the muted grey, --gns-muted); the current app keeps its accent. */
#global-nav-side {
    --kik-rail-ground: var(--gns-background, var(--colour-header, #fff));
    --kik-rail-label-ink: oklch(from var(--kik-rail-ground) clamp(0, calc((0.64 + 0.5 * c - l) * 1000), 1) 0 0);
}
#global-nav-side .gns-item:not(.current) > .gns-link:not([aria-current="page"]) .gns-label {
    color: var(--kik-rail-label-ink);
}
body[data-kik-rail-ground-sample][data-kik-rail="docked"].kik-rail-home #global-nav-side {
    --kik-rail-ground: color-mix(in srgb, var(--kik-rail-glass-tint, white) var(--kik-rail-glass-share, 55%), var(--kik-rail-ground-sample));
}


/* THE COLLAPSED RAIL LETS ITS TOOLTIPS OUT (Daniel, 2026-11-02: "rail tooltips
   in hub are clipped"). The labels are position: fixed ::after boxes, which
   no overflow should cut; Safari cut them at the rail's edge all the same —
   the rail is overflow: hidden and promoted to its own layer by
   ``will-change: width``, and WebKit clipped the fixed children to that layer.
   The clip and the layer are for the expand/collapse animation, which does
   not run while the pointer rests on a collapsed rail, so they are lifted for
   as long as it does — the only time a tooltip shows. */
@media (width > 768px) and (hover: hover) {
    body:is([data-global-nav="side"]:is(.gns-collapsed, :has(#app-space.state-on)), [data-kik-rail="docked"]:not(.kik-rail-home)) #global-nav-side:hover {
        overflow: visible;
        will-change: auto;
    }
}
