/* Side global navigation (#global-nav-side).
 *
 * Everything global-nav related lives in THIS file. Every selector is
 * namespaced: #global-nav-side, .gns-*, body[data-global-nav],
 * body.gns-collapsed.
 *
 * The rail is built to work exactly like the workspaces app's
 * .workspace-tabs column: a TRANSPARENT floating icon column,
 * SUPERIMPOSED on top of the app canvas (no panel, no border, no
 * content displacement). #app-space / #portal stay at left: 0; instead
 * the app content clears the rail the same way workspace content
 * clears its tabs column:
 *   - sidebars get the workspace-style left padding
 *     (--sidebar-padding with the 50px rail inset);
 *   - document bodies get --document-body-left-distance, consumed by
 *     the stock .document-body rules in all.css.
 *
 * Mode is a body state: <body data-global-nav="top|side">, rendered
 * server-side so there is no flash of the wrong nav. The rail is
 * expanded (--workspace-tabs-home-width) on Home only: whenever an app
 * is on screen (#app-space.state-on, matched via :has, plus the
 * body.gns-collapsed class KikaronGlobalNav mirrors) it collapses to
 * the 50px icon column — desktop only; under 768px the bottom tab bar
 * takes over.
 */

/* Registered as a real <color> so app-to-app switches interpolate —
 * the collapsed rail's wash then fades between app colours instead of
 * snapping (the var is stamped on <body> by KikaronGlobalNav). */
@property --gns-active-app-colour {
    syntax: '<color>';
    inherits: true;
    initial-value: transparent;
}

:root {
    /* Favourites runs as a rail app but has no App registry row, so its
     * colour isn't in the generated apps.css — give it the accent. */
    --app-colour-favourites: var(--colour-accent, #ea6c1a);
    /* 20px narrower than the workspace-tabs column: the expanded rail's
     * labels don't need the full 250px, and the dashboard feed (which
     * clears the rail via this var) gets the reclaimed width. */
    --gns-width: calc(var(--workspace-tabs-home-width, 250px) - 20px);
    --gns-width-collapsed: 50px;
    --gns-mobile-height: 64px;
    /* Corner radius of the active-app tile and the Home brandmark tile on
     * the mobile rail — shared so the two stay visually identical. */
    --gns-mobile-active-tile-radius: 13px;
    /* same x/y inset the workspace-tabs column uses */
    --gns-left: calc(var(--sidebar-margin-left, 15px) + 0.5 * var(--sidebar-padding-right, 25px));
    --gns-top: calc(var(--sidebar-margin-top, 15px) + 0.5 * var(--sidebar-padding-top, 25px));
    --gns-background: #fff;
    --gns-hairline: var(--colour-separation-lines, #eae2d6);
    --gns-ink: var(--colour-text, #1a1a1a);
    --gns-muted: var(--colour-discreet, #6b6560);
    --gns-accent: var(--colour-accent, #ea6c1a);
    /* One speed/curve for every rail transition (width morph, logo, labels,
     * fades, colour washes) so expand/collapse and all their parts move
     * together. */
    --gns-transition: var(--base-animation-duration, 0.3s);

    /* ONE SQUARE, ONE CORNER for every icon on the global nav — the launcher
     * tile, a pinned app's tile, the active app's filled tile, the search
     * launcher and the hover wash that lands behind any of them. They were
     * each carrying their own 36px/6px pair, so the hover square came out a
     * different size and a different corner from the tile it sat under
     * (Daniel, 2026-08-19). The corner follows the brand's middle radius,
     * like every other tile-shaped thing on the header row
     * (--kal-corner-radius, themes/usugami/style/all.css). The rail's own
     * icons are 38px — a rail measure, not a header one — but they take the
     * same corner. */
    --kik-global-nav-icon-size: 36px;
    /* The CORNER moved to :root (libraries/quaive/settings/artefacts/
       variables.css, 2026-08-22): it is a shape the whole product shares,
       and deriving it from --kal-corner-radius — which only usugami
       declares — let it drift between themes. Only the size is a rail
       measurement and stays here. */
}

/* ------------------------------------------------------------------ */
/* The rail itself — workspace-tabs look: transparent floating column  */
/* ------------------------------------------------------------------ */

#global-nav-side {
    display: none;
    position: fixed;
    top: var(--gns-top);
    bottom: calc(var(--sidebar-margin-bottom, 15px) + 0.5 * var(--sidebar-padding-bottom, 25px));
    left: var(--gns-left);
    width: var(--gns-width);
    /* transparent while expanded; the collapsed state paints it white */
    border: 1px transparent solid;
    border-radius: calc(0.7 * var(--border-radii, 8px));
    padding-top: 22px;
    box-sizing: border-box;
    flex-direction: column;
    background: transparent;
    /* zero-size, zero-alpha shadow so the collapsed state's
     * --default-box-shadow fades in instead of popping */
    box-shadow: 0 0 0 rgba(0, 0, 0, 0);
    color: var(--gns-ink);
    z-index: 999; /* above #portal (10) / #app-space (11) and any sidebar chrome, below modals/tooltips */
    transition: all var(--gns-transition);
    will-change: width;
    overflow-x: hidden;
    /* No scrollbar in the vertical rail: anything that doesn't fit moves into
     * the "More apps" candy box instead (global-nav-side.js
     * syncAppsOverflow / syncSpacesOverflow measure and mark). A scrolling
     * rail hides its own contents behind a gesture — the overflow menu is the
     * affordance, so clip rather than scroll. */
    overflow-y: hidden;
}

/* One and Pro get the SAME bottom bar on phones (Daniel, 2026-08-11).
 *
 * Compact mode's own rail (cloud_nav_rail.html) is desktop-only, so below
 * 769px those faces had no rail at all — the app carousel used to stand in
 * for it, and that is gone. The markup needs nothing: global_nav_side.html is
 * already included on every authenticated full page regardless of mode (it is
 * this `display` rule, and nothing else, that keeps it off the other faces).
 * So show it here, and the ≤768px bottom-bar section at the foot of this file
 * matches [data-global-nav="compact"] alongside "side" for the styling.
 *
 * Above the breakpoint compact keeps its own rail and this stays hidden. */
@media only screen and (max-width: 768px) {
    body[data-global-nav="compact"] #global-nav-side {
        display: flex;
    }
}

/* Header: platform identity + the user (personal-menu trigger).
 * interpolate-size lets the auto height animate to the collapsed 31px. */
#global-nav-side .gns-header {
    flex: 0 0 auto;
    height: 120px;
    padding: 0px 0 6px 10px;
    interpolate-size: allow-keywords;
    transition: all var(--gns-transition);
}
/* Platform logo: full wordmark expanded, brandmark (swirl) collapsed —
 * the two images sit stacked and crossfade with the width animation */
#global-nav-side .gns-platform-logo {
    display: block;
    position: relative;
    height: 67px;
    cursor: default;
    /* The logo tile box morphs between the full and collapsed states —
     * transition everything on the same rail speed so it eases instead of
     * snapping while the wordmark img morphs. */
    transition: all var(--gns-transition);
}
#global-nav-side .gns-platform-logo img {
    display: block;
    /* On the base (not just the collapsed state) so the morph uses the same
     * origin collapsing and expanding — keeps the two directions symmetric. */
    transform-origin: left;
    transition: all var(--gns-transition);
}
#global-nav-side .gns-logo-full {
    /* Rail width minus the header's 10px left padding, so the wordmark
     * tracks --gns-width instead of baking in the old 250px rail. */
    width: calc(var(--gns-width) - 10px);
}
#global-nav-side .gns-logo-mark {
    position: absolute;
    top: 0;
    left: 0;
    height: 28px;
    width: 28px;
    opacity: 0;
}
#global-nav-side .gns-platform-name {
    display: block;
    font-weight: 700;
    font-size: 1.1em;
    color: var(--gns-ink);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: all var(--gns-transition);
}
#global-nav-side .gns-hub {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    margin-top: 6px;
    margin-left: -8px;
    padding: 6px 8px;
    border-radius: 12px;
    box-sizing: border-box;
    max-height: 56px;
    overflow: hidden;
    min-width: 0;
    text-decoration: none;
    cursor: default;
    /* Collapse via opacity (linear fade) + max-height (linear clip) rather
     * than zoom: scaling to ~0 reads as "gone" almost instantly one way but
     * grows in gradually the other, so collapse and expand looked
     * lopsided. A fade + clip looks the same in both directions. */
    transition: all var(--gns-transition);
}

/* Hover wash + rounded corners matching the app rows (.gns-link:hover). */
@media (hover: hover) {
  #global-nav-side .gns-hub:hover {
      background: color-mix(in srgb, var(--gns-ink) 5%, transparent);
  }
}
body[dir="rtl"] #global-nav-side .gns-hub {
    margin-left: 0;
    margin-right: -8px;
}
/* The small round avatar left of the name — stock .pat-avatar shape
 * (50% radius, initials fallback), just sized down */
#global-nav-side .gns-user-avatar {
    flex: 0 0 auto;
    width: 29px;
    height: 29px;
    margin-left: -2px;
    margin-right: 1px;
    padding: 0;
    font-size: 0.68em;
    font-weight: 700;
}
/* Name stacked above the current-hub byline, to the right of the avatar. */
#global-nav-side .gns-hub-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    transition: all var(--gns-transition);
}
/* Name + down-arrow on one line; the arrow (the personal-menu dropdown
 * affordance) sits right after the name and never gets clipped. */
#global-nav-side .gns-hub-name {
    display: flex;
    align-items: center;
    min-width: 0;
    font-size: 0.85em;
    line-height: 1.2;
    color: var(--gns-ink);
    transition: all var(--gns-transition);
}
#global-nav-side .gns-hub-name-text {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#global-nav-side .gns-hub-name::after {
    flex: 0 0 auto;
    font-family: fontello;
    content: var(--glyph-down-open);
    font-size: 0.8em;
    color: var(--gns-muted);
    margin-left: 4px;
    transition: all var(--gns-transition);
}
/* The hub the user is currently in — a small muted byline under the name. */
#global-nav-side .gns-hub-byline {
    font-size: 0.7em;
    line-height: 1.2;
    color: var(--gns-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: all var(--gns-transition);
}

/* Item lists */
#global-nav-side ul {
    list-style: none;
    margin: 0;
    padding: 0;
}
#global-nav-side .gns-apps {
    flex: 0 0 auto;
}
#global-nav-side .gns-spaces {
    /* Directly BELOW the apps, not parked at the bottom: the spaces are the
     * apps' near neighbours — you move between an app and a space constantly —
     * and a column-length gap between them made them read as unrelated
     * furniture. A dim hairline above keeps the two groups distinct. */
    /* 0 0 auto, NOT 0 1 auto: this section is never silently squeezed. It
     * either shows its spaces at natural height or collapses wholesale to the
     * single Team spaces icon (.gns-spaces-overflow) — shrinking just clipped
     * the list against the rail's hidden overflow, hiding spaces with no
     * affordance to reach them. The collapse order lives in
     * global-nav-side.js refresh(): spaces first, then apps into the candy
     * box. */
    flex: 0 0 auto;
    margin-top: 0;
    /* No padding-top: the rule below owns the air above the first space, so
     * the open-apps group has the same 8px under it as the separator above it
     * gives it. */
    padding: 0 0 8px;
    min-height: 0;
    /* Clipped, not scrolled — see the rail's own overflow note above. Too many
     * spaces collapse to the single Team spaces icon (.gns-spaces-overflow). */
    overflow-y: hidden;
}
/* The rule between the apps and the spaces — the SAME line the apps list
 * draws between its pinned and its open rows (.gns-apps-separator): 2px at
 * 10% ink, inset 5px to the tile edge, 8px of air either side. Drawn here
 * rather than as a border on the section so the inset matches; hidden when
 * there are no spaces, since a rule under the last app with nothing below it
 * reads as a mistake (same reasoning as the separator's own :has gate). */
#global-nav-side .gns-spaces::before {
    content: "";
    display: block;
    height: 0;
    margin: 8px 5px;
    border-top: 2px solid color-mix(in srgb, var(--gns-ink) 10%, transparent);
}
#global-nav-side .gns-spaces:not(:has(.gns-item.gns-space))::before {
    display: none;
}
/* The app-launcher (candy-box) entry, between Team spaces and the personal
 * menu. It carries the margin-top:auto that .gns-spaces used to — the spaces
 * now sit straight under the apps, so this row and the personal menu are what
 * hold the bottom of the column; never squeezed, for the same reason
 * .gns-spaces isn't. (The list is .gns-app-launcher; the unqualified
 * .gns-launcher is the launcher CONTROL inside every app row — see the close
 * swap below.) */
#global-nav-side .gns-app-launcher {
    flex: 0 0 auto;
    margin-top: auto;
}
/* Mobile bottom bar: the launcher already lives there as the "More apps"
 * popover, so this desktop row stays out of the bar (same treatment the
 * personal menu gets). */
@media only screen and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-app-launcher {
        /* Both faces: the bar has its own launcher control at the right rim,
           and the rail's separate app-launcher entry is a SECOND waffle
           beside it (seen on Pro, 2026-08-12 — One's app set happens not to
           carry the entry, which is why it only showed there). */
        display: none;
    }
}
/* Personal cloud — the row between the app launcher and the personal menu.
 * No margin-top:auto of its own: the launcher above already holds the bottom
 * group down, and this list rides along under it. Never squeezed, same as its
 * two neighbours. */
#global-nav-side .gns-personal-cloud {
    flex: 0 0 auto;
}
/* Mobile bottom bar: out of the bar, like the launcher and the personal menu —
 * the More popover carries this entry there. */
@media only screen and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-personal-cloud {
        display: none;
    }
}

/* Close-on-hover for the ACTIVE app — the pro/One rail's behaviour
 * (cloud-nav-rail.css). Every app row carries a third control alongside its
 * two launcher flavours; it hides until the generated :has() rules
 * (global_nav_side.html) swap it in for the launcher while its app is the
 * on-screen one. The compound .gns-link.gns-close is deliberate: the plain
 * .gns-close form ties with .gns-link's own display and would lose on source
 * order, so the control would never hide. */
/* The candy-box entry (global_nav_side.html) is the COMPACT MOBILE BAR's
 * alone — the desktop rail has the Apps launcher row, and hub's bar has its
 * More popover. Revealed in the ≤768px block at the foot of this file. */
#global-nav-side .gns-candy-item {
    display: none;
}

#global-nav-side .gns-link.gns-close {
    display: none;
}
/* The close control carries TWO glyphs: the app icon (default) and the ×
 * (.gns-close-glyph, a fontello icon-cancel span). Hover swaps them, so the
 * active app's row reads "this closes the app" before the click. No hover
 * media query around the base hide: on touch a tap on the active row closes
 * too, and the × simply never previews. */
#global-nav-side .gns-close .gns-close-glyph {
    display: none;
    font-size: 15px;
    color: #fff;
}
#global-nav-side .gns-close .gns-close-glyph::before {
    margin: 0;
    width: auto;
}
/* The × is white, so the tile under it must be the app's colour whenever the
 * close control is on screen. The .current painting further up can't do it: it
 * keys off a SERVER snapshot (prefs_active_app), while this control is swapped
 * in by live :has() state — after a pane switch the two disagreed and the ×
 * painted white on the rail's own background, i.e. invisible. The control only
 * ever displays while its app is the active one, so painting it unconditionally
 * here is the same statement, made where it can't drift. --app-colour is
 * stamped inline per row. */
#global-nav-side .gns-close .gns-icon.app-icon {
    background-color: var(--app-colour, var(--gns-accent));
}
#global-nav-side .gns-close .gns-icon.app-icon * {
    fill: #fff !important;
}
#global-nav-side .gns-close .gns-icon.rail-icon * {
    fill: none !important;
    stroke: #fff !important;
}

@media (hover: hover) {
    #global-nav-side .gns-close:hover .gns-icon:not(.gns-close-glyph) {
        display: none;
    }
    #global-nav-side .gns-close:hover .gns-close-glyph {
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}
#global-nav-side .gns-section-title {
    display: none;
    margin: 4px 4px 6px;
    font-size: 0.7em;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gns-muted);
    transition: all var(--gns-transition);
}

/* One nav entry — workspace-tabs row metrics: 50px rows, 42px icon
 * block, bold label */
#global-nav-side .gns-link {
    display: flex;
    align-items: center;
    gap: 4px;
    /* 2px on the left in BOTH rail states, which is what the collapsed rail
     * needs to centre a 44px slot in its 48px content box. The expanded rail
     * doesn't need it for anything — it is here so the icon does not move at
     * all between the two states. Carried on the row rather than the icon so
     * the hover wash, which paints the padding box, keeps the full row width. */
    padding: 0 10px 0 2px;
    height: 44px;
    border-radius: 12px;
    box-sizing: border-box;
    color: #6B6560;
    font-weight: bold;
    text-decoration: none;
    white-space: nowrap;
    cursor: default;
    position: relative;
    transition: all var(--gns-transition);
}
@media (hover: hover) {
  #global-nav-side .gns-link:hover {
      background: color-mix(in srgb, var(--gns-ink) 5%, transparent);
  }
}

/* The active item's link has no background at all — its state shows
 * through the coloured icon tile and accent label, not a row wash */
@media (hover: hover) {
  #global-nav-side .gns-item.current > .gns-link:hover,
  #global-nav-side .gns-link[aria-current="page"]:hover {
      background: transparent;
  }
  /* …except a SPACE row, which keeps the wash even when it is the open one.
   * An app's active state is carried by a filled tile across the whole row's
   * height, so a wash on top adds nothing; a space's is a 38px filled circle
   * with a long row beside it, and dropping the wash left the open space as
   * the one row on the rail that didn't react to the pointer at all. In the
   * collapsed rail the rail-wide "no wash" rule still wins (2 ids) and the
   * hover square takes over. */
  #global-nav-side .gns-item.gns-space.current > .gns-link:hover {
      background: color-mix(in srgb, var(--gns-ink) 5%, transparent);
  }
}
#global-nav-side .gns-item.current > .gns-link,
#global-nav-side .gns-link[aria-current="page"] {
    color: var(--gns-accent);
    font-weight: 600;
}
#global-nav-side .gns-icon {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    margin: 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
#global-nav-side .gns-icon svg {
    width: 100%;
    height: 100%;
}
/* App icons: borderless transparent tiles, every SVG shape painted in
 * the muted ink (#6B6560), matching the label colour. !important beats
 * fill attributes baked into the source SVGs. */
#global-nav-side .gns-icon.app-icon {
    border: none;
    box-shadow: none;
    border-radius: var(--kik-global-nav-icon-radius);
    background-color: transparent;
    box-sizing: border-box;
    width: 38px;
    height: 38px;
    padding: 7px;
    overflow: hidden;
    transition: all var(--gns-transition);
}
#global-nav-side .gns-icon.app-icon * {
    fill: #6B6560 !important;
}
#global-nav-side .gns-icon.app-icon svg #dots {
    fill: transparent !important;
}
/* Active app: icon tile fills with the app's own colour, glyph in
 * white — the 42px rounded square the workspace tabs paint behind
 * their active icon */
#global-nav-side .gns-item.current .gns-icon.app-icon,
#global-nav-side .gns-link[aria-current="page"] .gns-icon.app-icon {
    background-color: var(--app-colour, var(--gns-accent));
}
#global-nav-side .gns-item.current .gns-icon.app-icon *,
#global-nav-side .gns-link[aria-current="page"] .gns-icon.app-icon * {
    fill: #fff !important;
}
#global-nav-side .gns-item.current .gns-icon.app-icon svg #dots,
#global-nav-side .gns-link[aria-current="page"] .gns-icon.app-icon svg #dots {
    fill: transparent !important;
}
/* Rail line icons (.rail-icon — an app icon the is_stroke_icon filter
 * sniffed as an outline; there is no separate rail-icons set): the
 * glyph is an outline, so the lines are COLOURED via stroke and the fill
 * stays empty. Each rule mirrors the filled-icon rule above at equal
 * specificity, later in source, so it wins for .rail-icon elements while
 * leaving fallback (filled) app icons on their fill rules. */
#global-nav-side .gns-icon.rail-icon * {
    fill: none !important;
    stroke: #6B6560 !important;
}
#global-nav-side .gns-icon.rail-icon svg #dots {
    fill: transparent !important;
    stroke: transparent !important;
}
/* Active app, line icons: the glyph rides the app-colour tile (from the
 * .app-icon rule above) with a white outline — the coloured-outline
 * treatment is reserved for the tooltip menu. */
#global-nav-side .gns-item.current .gns-icon.rail-icon *,
#global-nav-side .gns-link[aria-current="page"] .gns-icon.rail-icon * {
    fill: none !important;
    stroke: #fff !important;
}
#global-nav-side .gns-label {
    flex: 1 1 auto;
    max-width: 12rem;
    overflow: hidden;
    text-overflow: ellipsis;
    /* flex-grow is NOT transitioned — animating it forced a layout pass
     * per frame on every row; it snaps while the fade carries. The label
     * collapses via max-width (a cheap clip) rather than zoom — zoom
     * re-rasterised the label text every frame and was the main collapse
     * stutter on a rail full of rows. */
    transition: all var(--gns-transition);
}

/* Team-space avatar — the space's first letter on its own colour
 * (--gns-space-colour, set inline per entry; accent fallback).
 * Inactive: transparent, a small outlined circle (see below).
 * Active workspace: solid colour, white letter, full 38px. */
#global-nav-side .gns-space-avatar {
    /* The ink themes pin --app-icon-contrast-colour (black) so space
     * avatars join the ink palette; other brands fall through to the
     * per-space colour. */
    background: color-mix(in srgb, var(--app-icon-contrast-colour, var(--gns-space-colour, var(--gns-accent))) 10%, white);
    color: var(--app-icon-contrast-colour, var(--gns-space-colour, var(--gns-accent)));
    border: 1px solid var(--app-icon-contrast-colour, var(--gns-space-colour, var(--gns-accent)));
    box-sizing: border-box;
    /* The space's letter mark stays a perfect circle — the CONTAINER around
     * it is what matches the apps (the hover tile is the app tile's 38px /
     * 10px square), not the mark itself. */
    border-radius: 50%;
    font-size: 0.75em;
    font-weight: 700;
    line-height: 1;
}

/* A space you are NOT in reads as a small outlined circle: the ring shrinks
 * and empties out, while the LETTER keeps its size — that is what you scan
 * the list by, and shrinking it too would just make the rail harder to read.
 * The open space grows back into the filled 38px avatar below, so the one
 * space you're in is the only weight in the section.
 *
 * The slot is unchanged in both states — box + margin is 44px either way —
 * so the labels never shift as the circle grows and shrinks, and the two
 * states can simply animate into each other. */
#global-nav-side .gns-item.gns-space .gns-space-avatar {
    width: 26px;
    height: 26px;
    margin: 9px;
    background: transparent;
    /* The hover tile is an ::after at z-index -1 (it has to sit behind the
     * letter). Without a stacking context of its own here, that negative
     * layer sinks past every ancestor to the rail's own background and never
     * shows — which is exactly what happened. isolate keeps it in this box. */
    position: relative;
    isolation: isolate;
    transition:
        width var(--gns-transition),
        height var(--gns-transition),
        margin var(--gns-transition),
        background-color var(--gns-transition),
        color var(--gns-transition);
}
/* The current workspace stays remembered (li.current) but only reads
 * as highlighted while the Workspaces pane is in the foreground — on
 * Home or in any other app it renders like the rest. */
#global-nav-side .gns-item.gns-space.current > .gns-link {
    color: #6B6560;
    font-weight: bold;
}
body:has(#app-space.state-on:is(.active-app-workspaces, .active-app-team-spaces)) #global-nav-side .gns-item.gns-space.current > .gns-link {
    color: var(--gns-accent);
    font-weight: 600;
}

/* The OPEN space: the full-size filled avatar — the state every space used
 * to render in, now reserved for the one you're actually looking at. */
body:has(#app-space.state-on:is(.active-app-workspaces, .active-app-team-spaces)) #global-nav-side .gns-item.gns-space.current .gns-space-avatar {
    width: 38px;
    height: 38px;
    margin: 3px;
    background: var(--app-icon-contrast-colour, var(--gns-space-colour, var(--gns-accent)));
    color: #fff;
}

/* The "+" entry above the space list (opens the New team space modal):
 * a dashed neutral tile so it reads as an action, not another space. */
#global-nav-side .gns-space-add .gns-space-avatar {
    background: transparent;
    border-style: dashed;
    border-color: #6B6560;
    color: #6B6560;
    font-size: 1.1em;
    font-weight: 600;
}
@media (hover: hover) {
  #global-nav-side .gns-space-add .gns-link:hover .gns-space-avatar {
      border-color: var(--gns-ink);
      color: var(--gns-ink);
  }
}

/* Too many spaces to fit without scrolling: KikaronGlobalNav flags the
 * rail with .gns-spaces-overflow and the per-space list collapses into
 * the single Team spaces app icon (the .gns-spaces-all entry).
 *
 * UNLESS Team spaces is PINNED. A pinned Team spaces already has its own
 * icon up among the apps (global-nav-mobile-order.css keys that off the same
 * data-gns-pinned marker), so collapsing would trade every direct space entry
 * for a SECOND copy of an icon the rail is already showing — losing the
 * spaces and gaining nothing. Pinned, the list stays and scrolls instead. */
#global-nav-side .gns-spaces .gns-spaces-all {
    display: none;
}
#global-nav-side.gns-spaces-overflow:not(:has(.gns-apps .gns-app-team-spaces [data-gns-pinned])) .gns-spaces .gns-spaces-all {
    display: block;
}
#global-nav-side.gns-spaces-overflow:not(:has(.gns-apps .gns-app-team-spaces [data-gns-pinned])) .gns-spaces > ul:not(.gns-spaces-all),
#global-nav-side.gns-spaces-overflow:not(:has(.gns-apps .gns-app-team-spaces [data-gns-pinned])) .gns-spaces .gns-section-title {
    display: none;
}

/* Unread badge — keeps working in the collapsed rail (rides the icon) */
#global-nav-side .gns-badge {
    flex: 0 0 auto;
    background: var(--gns-accent);
    color: #fff;
    border-radius: 999px;
    font-size: 0.7em;
    line-height: 1;
    padding: 3px 6px;
    top: 0;
}

/* Favourites-as-app pane visibility — no App registry row means no
 * generated show-rule in automations/apps.css, so it lives here. */
#app-space.active-app-favourites.state-on #application-body-favourites {
    display: block;
}

/* RTL (Hebrew/Arabic): rail rides the right edge; the var-driven
 * margins/distances are consumed by all.css's own RTL swaps, but the
 * physical insets declared above (padding shorthands, left/right
 * coordinates) need explicit mirrors. */
body[dir="rtl"] #global-nav-side {
    left: auto;
    right: var(--gns-left);
}
/* Header/logo: the 10px inline inset moves to the right edge (the
 * wordmark <img> right-aligns by itself — block replaced element under
 * direction: rtl) and the stacked brandmark anchors right too. */
body[dir="rtl"] #global-nav-side .gns-header {
    padding: 0 10px 10px 0;
}
body[dir="rtl"] #global-nav-side .gns-logo-mark {
    left: auto;
    right: 0;
}
/* Row insets: the 10px end-padding sits before the label, not the icon,
 * so the icons line up under the logo like they do in LTR. */
body[dir="rtl"] #global-nav-side .gns-link {
    padding: 0 0 0 10px;
}

/* The mobile bottom bar's leading Home button (the brandmark) — the
 * desktop rail uses the .gns-header logo instead, so hide it here. */
#global-nav-side .gns-mobile-home {
    display: none;
}

/* "More apps" overflow entry — hidden by default, and on the DESKTOP rail it
 * now stays that way: the Apps launcher has its own rail entry and lists every
 * app, so global-nav.js no longer sets .gns-apps-overflow there — it just
 * marks what didn't fit .gns-overflowed and lets it drop off. Only the mobile
 * bar still reveals a More entry, via the breakpoint rules further down. */
#global-nav-side .gns-more-item {
    display: none;
}
#global-nav-side.gns-apps-overflow .gns-more-item {
    display: block;
}
#global-nav-side .gns-apps .gns-item.gns-overflowed {
    display: none;
}
/* The trigger is a <button popovertarget> — strip native chrome so it
 * reads as a plain rail icon; default cursor (it's a control). */
#global-nav-side .gns-more-trigger {
    background: none;
    border: none;
    /* Strip the UA button chrome WITHOUT overriding the rail's own link
     * treatment. `font: inherit` is a shorthand — it reset font-weight to the
     * inherited 400 — and `color: inherit` took the row's colour, so "More"
     * rendered lighter and darker than every app name beside it. Family and
     * size still need inheriting (a UA button defaults to a small system
     * font); colour and weight are left to the .gns-link rules this button
     * also matches, so the entry reads exactly like an app name. */
    font-family: inherit;
    font-size: inherit;
    cursor: default;
    width: 100%;
}
/* While the overflow popover is open, the trigger reads as active: the tile
 * stays transparent and the glyph is painted in the accent colour (fill for
 * filled icons, stroke for line icons). The popover is a DOM descendant of
 * the rail, so :has() matches from #global-nav-side; the ID prefix outranks
 * the rail's base `.gns-icon.app-icon`/`.rail-icon *` rules. */
#global-nav-side:has(.gns-more-popover:popover-open) .gns-more-trigger .gns-icon {
    background-color: transparent;
}
#global-nav-side:has(.gns-more-popover:popover-open) .gns-more-trigger .gns-icon.app-icon * {
    fill: var(--colour-accent) !important;
}
#global-nav-side:has(.gns-more-popover:popover-open) .gns-more-trigger .gns-icon.rail-icon * {
    fill: none !important;
    stroke: var(--colour-accent) !important;
}

/* The overflow popover (HTML Popover API) — a floating app launcher above
 * the bar. UA styles centre popovers in the viewport; override the inset/
 * margin to dock it just over the bar, spanning the bar's width. */
.gns-more-popover {
    position: fixed;
    inset: auto;
    margin: 0;
    bottom: calc(
        var(--workspace-tabs-bar-height, var(--gns-mobile-height))
        + 0.5 * var(--small-screen-padding, 10px)
        + env(safe-area-inset-bottom, 0px)
        + 10px
    );
    left: calc(0.5 * var(--small-screen-padding, 10px));
    right: calc(0.5 * var(--small-screen-padding, 10px));
    width: auto;
    max-width: none;
    box-sizing: border-box;
    padding: 16px;
    border: none;
    border-radius: var(--border-radii, 8px);
    background-color: var(--pat-surface-background-colour, rgba(255, 255, 255, 0.9));
    -webkit-backdrop-filter: blur(10px) saturate(4) brightness(1.2);
    backdrop-filter: blur(10px) saturate(4) brightness(1.2);
    box-shadow: var(--pat-toolbar-box-shadow, var(--default-box-shadow));
    color: var(--gns-ink);
    /* Fade + slide in; allow-discrete animates the display/overlay flips */
    opacity: 0;
    transform: translateY(8px);
    transition:
        opacity var(--gns-transition),
        transform var(--gns-transition),
        overlay var(--base-animation-duration, 0.2s) allow-discrete,
        display var(--base-animation-duration, 0.2s) allow-discrete;
}
.gns-more-popover:popover-open {
    opacity: 1;
    transform: translateY(0);
}
@starting-style {
    .gns-more-popover:popover-open {
        opacity: 0;
        transform: translateY(8px);
    }
}
/* The mirrored personal menus stack above the app list; space each block
 * off from the next (and the app list below). */
#global-nav-side .gns-more-popover .pat-menu-list {
    margin-bottom: 15px;
}
.gns-more-popover .gns-more-list {
    list-style: none;
    margin: 8px 0px;
    padding: 0;
    display: grid;
    /* minmax(0, 1fr) — not the default minmax(auto, 1fr) — so the nowrap app
     * labels can't blow the tracks wider than their half of the popover and
     * trigger a horizontal scrollbar; the labels ellipsize instead. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2px;
    max-height: 60vh;
    overflow-x: hidden;
    overflow-y: auto;
}
.gns-more-popover .gns-more-link {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 5px;
    border-radius: 8px;
    text-decoration: none;
    color: var(--gns-ink);
    white-space: nowrap;
    overflow: hidden;
}
@media (hover: hover) {
  .gns-more-popover .gns-more-link:hover {
      background: color-mix(in srgb, var(--gns-ink) 6%, transparent);
  }
}
/* #global-nav-side prefix so these outrank the rail's ID-scoped
 * `#global-nav-side .gns-icon.app-icon` base rules — the popover lives
 * inside the rail, so a class-only selector loses to them. */
#global-nav-side .gns-more-popover .gns-more-app .gns-icon {
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 28px;
    height: 28px;
    padding: 5px;
    aspect-ratio: 1 / 1;
    border-radius: 6px;
}
/* Popover icons: line icons keep their stroke, filled icons their fill —
 * both painted in the muted ink, accent for the current app. */
.gns-more-popover .gns-more-app .gns-icon.app-icon * {
    fill: var(--gns-muted) !important;
}
.gns-more-popover .gns-more-app .gns-icon.rail-icon * {
    fill: none !important;
    stroke: var(--gns-muted) !important;
}
.gns-more-popover .gns-more-app.current .gns-more-link {
    color: var(--app-colour, var(--gns-accent));
    font-weight: 600;
}
/* Active app: icon tile fills with the app's colour and the glyph turns
 * white (fill for filled icons, stroke for line icons) — the same
 * white-on-app-colour treatment the rail uses for its current item. */
#global-nav-side .gns-more-popover .gns-more-app.current .gns-icon {
    background-color: var(--app-colour, var(--gns-accent));
}
#global-nav-side .gns-more-popover .gns-more-app.current .gns-icon.app-icon * {
    fill: #fff !important;
}
#global-nav-side .gns-more-popover .gns-more-app.current .gns-icon.rail-icon * {
    fill: none !important;
    stroke: #fff !important;
}
.gns-more-popover .gns-more-label {
    /* min-width: 0 lets the label shrink below its content width inside the
     * flex link, so overflow/ellipsis engages instead of pushing the row wide. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The pat-back-button leads its toolbar-section — it must sit before
 * any sibling view-option icons (e.g. the visibility toggle) regardless
 * of source order. flex order: -1 wins whether the back button is a
 * direct child of the view-options section (Library detail) or is itself
 * wrapped in its own nested view-options section (pat_ueber_back_button).
 * Applies to Side AND Compact (Cloud) — both drop the legacy top-nav
 * mobile back proxy, so in both the toolbar's own back button is the
 * mobile back affordance (re-shown in the ≤768px block below). */
body:is([data-global-nav="side"], [data-global-nav="compact"]) .pat-toolbar .toolbar-section .pat-back-button,
body:is([data-global-nav="side"], [data-global-nav="compact"]) .pat-toolbar .toolbar-section > .toolbar-section.view-options:has(> .pat-back-button) {
    order: -1;
}

/* A toolbar whose ONLY content is the reveal-the-sidebar back button
 * (button.icon.back) is hidden on non-mobile by _canvas-toolbar.scss —
 * the button has nothing to do there, leaving an empty strip. That rule
 * deliberately KEEPS the bar on mobile, where the button is the page's
 * back affordance. But in Top (non-Side) nav mode the legacy mobile
 * back-nav proxy already carries that affordance, so the bar is just an
 * empty strip on mobile too — hide it. Side and Compact modes are
 * excluded: there the proxy is dropped and this toolbar back button IS
 * the mobile back. Selector mirrors the _canvas-toolbar.scss rule
 * (flattened :not(:has()) — nested :has() is invalid). */
@media only screen and (max-width: 768px) {
    body:not([data-global-nav="side"]):not([data-global-nav="compact"]) .pat-toolbar:has(.toolbar-section.view-options > button.icon.back):not(:has(.toolbar-section.quick-functions)):not(:has(.toolbar-section.editor)):not(:has(.toolbar-section.navigation)):not(:has(.toolbar-section.view-options > :not(button.icon.back))) {
        display: none;
    }
}

/* The contacts profile/detail back toolbar (#contact-nav-toolbar) is hidden
 * on small screens by the contacts App.css (its ``--screen-small`` rule).
 * That fits Top nav — the legacy mobile nav-proxy carries "back" there and
 * the toolbar's pat-back-button is hidden anyway. In Side and Compact
 * modes the proxy is gone and that pat-back-button IS the back
 * affordance (restyled as a leading icon above), so keep the toolbar
 * visible. !important + later source beats the App.css container-query
 * rule. */
@media only screen and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #contact-nav-toolbar {
        display: block !important;
    }
}

/* ------------------------------------------------------------------ */
/* ≤768px: the rail becomes a bottom tab bar                           */
/* ------------------------------------------------------------------ */

@media only screen and (max-width: 768px) {
    /* Side and Compact (Cloud) modes drop the legacy mobile back-nav proxy
     * (quaive-mobile-navigation renders only for global_nav == 'top' — see
     * global_header.html), so the app toolbar's own .pat-back-button has
     * to carry the back affordance on small screens. _toolbar.scss hides it
     * there for the top-nav layout; re-show it while Side/Compact is active
     * and restyle it as a plain toolbar .icon (square, label hidden,
     * left-open glyph in :before). The restyle itself lives with the
     * component — see the ≤768px Side/Compact block in
     * components/pat-back-button.css. Only the toolbar-grid placement
     * rules below stay here (they are nav-mode layout, not the button). */

    /* When a .navigation section is present the mobile toolbar grid
     * squeezes its column down to a single button width (see _toolbar.scss),
     * leaving no room for the title — it shows only as a clipped sliver
     * beside the back icon. Drop it; the back icon carries the context.
     * Toolbars with no navigation section give the title the full first
     * column, so those keep their title. Mirrors the team-spaces/registry
     * small-screen "title shrinks away where there's no room" rule. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #portal .pat-toolbar:has(.toolbar-section.navigation) .toolbar-title,
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body .pat-toolbar:has(.toolbar-section.navigation) .toolbar-title {
        display: none;
    }

    /* The pat-back-button lives in a .view-options section, not the
     * .navigation one. On desktop that section is already the toolbar's
     * leading (first) grid column, but _toolbar.scss bumps view-options to
     * the MIDDLE column on small screens whenever an app is open with a
     * navigation section — pushing the back button off the leading edge.
     * Hand the leading slot back to the section that carries the back
     * button so it's always the first icon, like the native .icon.back.
     * (The title that normally sits first is hidden above, so the column
     * is free.) Other view-options sections keep the middle placement. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #portal .pat-toolbar .toolbar-section.view-options:has(.pat-back-button),
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body .pat-toolbar .toolbar-section.view-options:has(.pat-back-button) {
        grid-area: first !important;
        justify-self: start !important;
    }

    /* Floating bottom bar — same recipe as the workspace-tabs bar on
     * small screens: inset pill, translucent blur background, shadow,
     * superimposed over the content. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side {
        top: auto;
        bottom: 10px;
        left: 10px;
        right: 10px;
        width: auto;
        height: var(--workspace-tabs-bar-height, var(--gns-mobile-height));
        flex-direction: row;
        align-items: stretch;
        /* Items size to content and spread across the bar; this pins the
         * first and last icon to the two ends at a constant distance from
         * the sides, regardless of how many icons are shown. The varying
         * count is absorbed by the gaps between the inner icons. */
        justify-content: space-between;
        /* The same surface recipe the chat composer wears (its own rule in
           messages/styling reads the first of these two tokens), so the two
           islands sharing the bottom of the screen are made of one material
           and a theme tunes them together (Daniel, 2026-08-12). The blur was
           a hand-rolled blur/saturate/brightness stack that no theme could
           reach. */
        background-color: var(--pat-toolbar-button-background-colour);
        -webkit-backdrop-filter: var(--pat-surface-backdrop-filter);
        backdrop-filter: var(--pat-surface-backdrop-filter);
        box-shadow: var(--pat-toolbar-box-shadow, var(--default-box-shadow));
        border-radius: var(--border-radii, 8px);
        border-top: none;
        overflow: hidden;
        padding: 0 8px;
        /* Auto-hide on scroll: slide down out of sight (see the
         * scroll-down rule below) and glide back on scroll-up.
         *
         * Declared ONCE, here on the element, and deliberately not repeated in
         * the states that move it. A transition is read from the state being
         * transitioned TO, so three rules each naming their own duration meant
         * the two directions ran on different clocks and an interrupted slide
         * could land in a state whose rule said something else — which is what
         * "the second scroll jumps without transition" looks like (reported
         * 2026-08-12). One declaration cannot disagree with itself. */
        transition: transform calc(var(--base-animation-duration, 0.25s) * 2) ease;
    }
    /* Scrolling down pushes the bar fully below the viewport edge (its own
     * height + a buffer that clears the bottom inset and drop shadow);
     * scrolling back up, or reaching the top, slides it into place.
     *
     * THE STOCK PATTERN SAYS SO, not a listener of ours.
     *
     * WHAT SCROLLS is ``.document-body`` (and a sidebar's ``.sidebar-content``)
     * — ``#app-space .document-body`` is ``position: absolute; overflow-y:
     * auto`` inside a ``#main`` that is ``position: fixed; inset: 0``, so the
     * window never scrolls at all and neither does the body. Both of those
     * boxes already carry ``pat-scroll-box``, so both already publish
     * ``scroll-up`` / ``scroll-down`` / ``scroll-position-top``, live. There
     * was never anything to add.
     *
     * KEYED ON THE DIRECTION, not the position, and that is what makes the
     * ``:has()`` safe. More than one application body can carry ``state-on``,
     * and one nobody is looking at sits at its scroll top for ever — so a rule
     * asking "is any of them at the top?" is answered yes by a parked app and
     * pins the dock up permanently, which is exactly how it froze earlier
     * today. A parked body has never scrolled and so carries no direction
     * class at all: only a box somebody has actually pushed down can match
     * this, and the absence of a match means the dock is up, which is the safe
     * way round.
     *
     * AND THE PANE IS THE FOCUSED ONE, which is what the app body's focus mode
     * says. An application body holds BOTH a .sidebar-content and a
     * .document-body, so a sidebar left scrolled down went on answering for a
     * document you had since opened at its top: the dock stayed away from a
     * page with nothing scrolled. Only the pane being read is asked.
     *
     * POSITION OVERRULES DIRECTION AT BOTH ENDS, because at both ends the
     * direction is a lie the rubber band tells.
     *
     * At the TOP, the bounce reads as a downward scroll and would take the
     * dock away from a list that has not moved — hence
     * ``:not(.scroll-position-top)``.
     *
     * At the BOTTOM it reads as an upward one, and brought the dock back over
     * the end of the very thing being read (Daniel, 2026-08-24). So
     * ``.scroll-position-bottom`` hides it outright: at the foot of a list
     * there is nothing below to reveal, and the last thing wanted there is
     * chrome climbing over the last line. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(.application-body.state-on.focus-document .document-body:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) #global-nav-side,
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(.application-body.state-on.focus-sidebar .sidebar-content:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) #global-nav-side {
        transform: translateY(calc(100% + 40px));
    }
    /* Collabora full-screen editing: the floating bar is superimposed over
     * the content, so on small screens it overlaps the office editor's own
     * toolbars/handles. Drop the rail entirely while a Collabora iframe is
     * open in the browser — same for the OnlyOffice (EUO) editor shell —
     * the editor (with its built-in close button)
     * gets the whole viewport. :has() is live, so the bar returns the moment
     * the editor shell leaves the DOM. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(.collabora-editor-shell) #global-nav-side,
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(.onlyoffice-editor-shell) #global-nav-side {
        display: none;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-spaces {
        display: none;
    }
    /* The header (logo + user avatar) is dropped on mobile — Home lives
     * in the brandmark button; the personal menu stays in the apps grid. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-header {
        display: none;
    }
    /* No global header on the phone, exactly as in Side mode — where the rail
     * replaces it outright (the `display: none !important` block further up).
     * Compact's header used to be the ONLY chrome a phone had, so base.html
     * kept it: the candy box, the avatar and the app labels were the whole
     * navigation. The bottom bar carries that now, and two chrome strips for
     * one small screen is one too many (Daniel, 2026-08-11).
     *
     * Zero the height variable with it: half the compact rules position
     * against --global-header-height (the top shadow, the sticky offsets, the
     * mobile shell's document min-height), and leaving it at 54px reserves a
     * band for a header that is no longer painted. */
    body[data-global-nav="compact"] {
        --global-header-height: 0px;
    }
    body[data-global-nav="compact"] #global-header {
        display: none !important;
    }

    /* One/Pro: no More popover on the bar. The candy box replaces it — it
     * brings the portal forward and swipes the dashboard carousel to the Apps
     * portlet, so the launcher is a page of the bento rather than a popover
     * listing whichever apps happened not to fit (Daniel, 2026-08-11). Hub
     * keeps its More entry.
     *
     * !important because the per-width icon-cap blocks further down are LATER
     * media queries that set .gns-more-item's display themselves; without it
     * they would put the popover trigger back at every breakpoint above the
     * narrowest. */
    body[data-global-nav="compact"] #global-nav-side .gns-more-item,
    body[data-global-nav="compact"] #global-nav-side .gns-more-popover {
        display: none !important;
    }
    body[data-global-nav="compact"] #global-nav-side .gns-candy-item {
        display: block;
        /* Same slot the More entry held: last, at the bar's trailing end
         * (see global-nav-mobile-order.css for the order scale). */
        order: 2;
    }

    /* No user avatar on the bar either (Daniel, 2026-08-11). The personal
     * menu trigger is the rail's last child and sits OUTSIDE .gns-header, so
     * the rule above never reached it: on a 375px bar it was taking 219 of
     * the 355px — the whole row's worth of app icons — for one avatar and a
     * hub name. The personal menu is reachable from the header's own avatar
     * and from the apps grid; the bar is for apps. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-user {
        display: none;
    }
    /* Brandmark Home button is the bar's leading item (order 0), pinned to
     * the left end by the bar's space-between. Matches an app tab's box
     * width (44px icon + 2px link padding each side) so the brandmark
     * occupies the same slot as the icons; its 32px image stays centred. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-mobile-home {
        order: 0;
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        flex: 0 0 auto;
        width: 48px;
        min-width: 0;
        padding: 0;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-mobile-home img {
        width: 32px;
        height: 32px;
        object-fit: contain;
    }
    /* display:contents dissolves the .gns-apps box so its .gns-item rows
     * become direct flex children of the bar, spread across the row by the
     * bar's space-between alongside the home button. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps {
        display: contents;
    }
    /* Cap the app icons on the bar; the rest are reached via the "More
     * apps" popover. Three by default — the cap rises with width below,
     * so narrower screens move more icons into the popover.
     * :not(.gns-more-item):not(.gns-candy-item) keeps the trailing More entry on the bar. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(n+4) {
        display: none;
    }
    /* The More entry is the bar's trailing item, mobile only — pinned to
     * the right end by the bar's space-between. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-more-item {
        display: block;
        flex: 0 0 auto;
        min-width: 0;
    }
    /* …but drop it when nothing overflows (no app past the cap), so it
     * never opens an empty popover. The threshold rises with the cap in
     * the breakpoints below. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps:not(:has(.gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(4))) .gns-more-item {
        display: none;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-item {
        flex: 0 0 auto;
        min-width: 0;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-link {
        flex-direction: column;
        justify-content: center;
        gap: 2px;
        padding: 2px;
        height: 100%;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-icon {
        width: 44px;
        height: 44px;
        margin: 0;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-icon.app-icon {
        padding: 7px;
    }
    /* More-apps popover: tighten the app-icon tile padding on mobile.
     * Class-count beats the base popover rule (padding: 5px) above. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-more-popover .gns-more-app .gns-icon.app-icon {
        padding: 4px;
    }
    /* Active app: rounder tile square on the mobile bar. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-item.current .gns-icon.app-icon,
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-link[aria-current="page"] .gns-icon.app-icon {
        border-radius: var(--gns-mobile-active-tile-radius, 13px);
    }
    /* Mobile bar iconography is monochrome black — app glyphs and the
     * brandmark Home button alike. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-icon.app-icon * {
        fill: #000 !important;
    }
    /* Rail line icons: colour the stroke black, fill stays empty */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-icon.rail-icon * {
        fill: none !important;
        stroke: #000 !important;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-mobile-home img {
        filter: brightness(0);
    }
    /* Home screen current (no app on screen): the brandmark sits on an
     * accent tile, mirroring the active app icon. The tile is a ::before
     * (a background on the img would be whitened by the invert filter);
     * the brandmark itself turns white via brightness(0) invert(1). */
    body:is([data-global-nav="side"], [data-global-nav="compact"]):not(:has(#app-space.state-on)) #global-nav-side .gns-mobile-home::before {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
        border-radius: var(--gns-mobile-active-tile-radius, 13px);
        background: var(--colour-accent, var(--gns-accent));
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]):not(:has(#app-space.state-on)) #global-nav-side .gns-mobile-home img {
        position: relative;
        z-index: 1;
        filter: brightness(0) invert(1);
    }
    /* Icons only on the bar — no tab text (like the workspace tabs) */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-label {
        display: none;
    }
    
    /* No hover tooltips on touch — labels are inline anyway */
    @media (hover: hover) {
      body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-link[data-gns-label]:hover::after {
          content: none;
      }
    }
    
    /* No hover effects on the touch bar — a tap shouldn't leave a wash */
    @media (hover: hover) {
      body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-link:hover,
      body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-mobile-home:hover {
          background: transparent;
      }
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-badge {
        position: absolute;
        top: 4px;
        right: 50%;
        margin-right: -20px;
    }
    /* The bar floats OVER the content (like the workspace tabs) — no
     * bottom offset on #app-space/#portal; content scrolls under it. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #app-space,
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #portal {
        margin-left: 0;
        margin-right: 0;
    }
    /* …but a scrollable sidebar would otherwise end UNDER the floating
     * bar, hiding its last items. Pad its scroll content by the bar's
     * full footprint (height + its bottom inset + the safe area) plus a
     * little breathing room, so everything can scroll into view. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #app-space .sidebar-content,
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #app-space #sidebar-content {
        box-sizing: border-box;
        padding-bottom: calc(
            var(--workspace-tabs-bar-height, var(--gns-mobile-height))
            + 0.5 * var(--small-screen-padding, 10px)
            + env(safe-area-inset-bottom, 0px)
            + 20px
        );
    }
    /* Same story for document bodies: pad the bottom by the bar's full
     * footprint so a page's last lines clear the floating bar instead of
     * hiding behind it. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body {
        padding-bottom: calc(
            var(--workspace-tabs-bar-height, var(--gns-mobile-height))
            + 0.5 * var(--small-screen-padding, 10px)
            + env(safe-area-inset-bottom, 0px)
            + 20px
        );
    }
    /* …except where a sticky COMPOSER already owns the bottom of the scroller
     * (Messages, Kiki). Two things about that padding go wrong there: the
     * composer is the scroller's last flow child, so nothing can hide behind
     * the bar anyway, and — measured 2026-08-12 — a sticky element's inset is
     * resolved against the scrollport's CONTENT box, so the padding is added
     * to the composer's own `bottom` and it floated 178px up instead of the 85
     * it asks for. Let the composer's inset be the only thing holding it off
     * the dock. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body:has(.kik-chat-composer) {
        padding-bottom: 0;
    }
    /* …and where the content is a FIXED-HEIGHT canvas rather than a page
     * (the Agenda's FullCalendar pane — it scrolls inside itself, nothing
     * of it can hide behind the bar). There the breathing room above turns
     * into a white band between the grid and the dock, so the reserve is
     * pared to the bar's own footprint: the calendar's bottom edge lands
     * exactly on the dock (Daniel, 2026-08-26). */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body:has(.events-calendar-pane) {
        padding-bottom: calc(
            var(--workspace-tabs-bar-height, var(--gns-mobile-height))
            + env(safe-area-inset-bottom, 0px)
        );
    }
    /* Portal / dashboard content pane: same story — pad its scroll bottom
     * by the floating bar's full footprint (height + bottom inset + safe
     * area) plus breathing room so the last portlets clear the bar. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #portal-content-pane {
        padding-bottom: calc(
            var(--workspace-tabs-bar-height, var(--gns-mobile-height))
            + 0.5 * var(--small-screen-padding, 10px)
            + env(safe-area-inset-bottom, 0px)
            + 20px
        );
    }
    /* Calendar canvas: small uniform insets, with the bottom raised clear
     * of the floating bar. (position:absolute lives in the desktop media
     * query, so re-assert it here for the insets to take effect.) */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #app-space .application-calendar .pat-calendar#calendar-app {
        position: absolute;
        top: 8px;
        right: 8px;
        bottom: 83px;
        left: 8px;
    }
    /* Meetings (Jitsi) canvas: same insets as the calendar above, so it
     * sits clear of the floating bar. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #app-space .application-body.application-meetings .pat-jitsi {
        position: absolute;
        top: 8px;
        right: 8px;
        bottom: 83px;
        left: 8px;
    }
    /* NB: the dashboard's bare-feed styling (single full-width column, no
     * title, no panel chrome) is NOT here — it's driven by the Size 1
     * container query (gns-dash width < 660px) above, so it tracks the
     * actual column width rather than the viewport. */
}

/* Wider phones have room for more icons on the bar before spilling into
 * the More popover: raise the cap a notch per breakpoint. Each re-shows
 * the next app (overriding the base nth-child(n+6) hide) and pushes the
 * bar's hide threshold out by one.
 *
 * The popover lists ONLY the apps that didn't fit: it's the same list in
 * the same order, so hiding the first N (= the bar cap) leaves exactly the
 * overflow. Each breakpoint hides one more, mirroring the bar. */
@media only screen and (max-width: 768px) {
    .gns-more-popover .gns-more-app:nth-child(-n+3) {
        display: none;
    }
}
/* 360px covers the narrowest mainstream phones (iPhone 13 mini / SE = 375px,
 * 360px-class Androids): the trimmed icon footprint above leaves room for a
 * 4th app on the bar at that width, where the base band fit only 3. */
@media only screen and (min-width: 360px) and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(-n+4) {
        display: block;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(n+5) {
        display: none;
    }
    .gns-more-popover .gns-more-app:nth-child(4) {
        display: none;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps:not(:has(.gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(5))) .gns-more-item {
        display: none;
    }
}
@media only screen and (min-width: 490px) and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(-n+5) {
        display: block;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(n+6) {
        display: none;
    }
    .gns-more-popover .gns-more-app:nth-child(5) {
        display: none;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps:not(:has(.gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(6))) .gns-more-item {
        display: none;
    }
}
@media only screen and (min-width: 580px) and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(-n+6) {
        display: block;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(n+7) {
        display: none;
    }
    .gns-more-popover .gns-more-app:nth-child(6) {
        display: none;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps:not(:has(.gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(7))) .gns-more-item {
        display: none;
    }
}
@media only screen and (min-width: 670px) and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(-n+7) {
        display: block;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(n+8) {
        display: none;
    }
    .gns-more-popover .gns-more-app:nth-child(7) {
        display: none;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps:not(:has(.gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(8))) .gns-more-item {
        display: none;
    }
}
@media only screen and (min-width: 760px) and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(-n+8) {
        display: block;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(n+9) {
        display: none;
    }
    .gns-more-popover .gns-more-app:nth-child(8) {
        display: none;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps:not(:has(.gns-item:not(.gns-more-item):not(.gns-candy-item):nth-child(9))) .gns-more-item {
        display: none;
    }
}
/* Preferences gets its own trailing tab on the mobile bottom bar. It's kept
 * off the desktop rail (see the desktop utility-apps block above) and is
 * normally an overflow-only entry, so on mobile we force it back onto the bar
 * — past the per-width app cap (hence !important, which beats the nth-child
 * hide bands above whatever Preferences' dynamic position is), pin it last
 * with flex order so it reads as the final option, and drop its now-duplicate
 * entry from the more-apps popover. Mobile only — the desktop rail is
 * untouched. */
@media only screen and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-apps .gns-item.gns-app-preferences {
        display: block !important;
        order: 1;
        flex: 0 0 auto;
        min-width: 0;
    }
    .gns-more-popover .gns-more-app.gns-app-preferences {
        display: none;
    }
}

/* Installed as a PWA (standalone): float the bar a little further from the
 * screen edges than in a normal browser tab (10px base above). */
@media only screen and (max-width: 768px) and (display-mode: standalone) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side {
        bottom: 23px;
        left: 23px;
        right: 23px;
    }
}

@keyframes gns-sidebar-slide-in {
    from {
        transform: translateX(-48px);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* RTL twin — sidebars live on the right, so they arrive from there */
@keyframes gns-sidebar-slide-in-rtl {
    from {
        transform: translateX(48px);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* ================================================================== */
/* Compact mode (body[data-global-nav="compact"])                      */
/* ------------------------------------------------------------------ */
/* Based on Tabs mode: keeps the global header + portal, but drops the  */
/* open-app tabs bar and the portal quick-nav. A candy-box launcher in  */
/* the header (next to the personal menu) opens a pat-tooltip whose     */
/* content is the apps grid — the apps inject into the SPA shell like    */
/* the rail, so no tab/pane switching is needed.                        */

body[data-global-nav="compact"] #tabs-bar,
body[data-global-nav="compact"] .portal-quick-nav {
    display: none !important;
}

/* Kill the two legacy top-chrome decorations #app-space paints for the classic
 * header: the drop-shadow gradient (::before — forced visible by
 * body:not(:has(#portal-tabs)), and Compact mode has no #portal-tabs) and the
 * separation line above the app area (state-on::after). Compact/Cloud mode's
 * own header supplies its edge, so both read as stray artefacts here — same
 * cleanup Side mode does for the ::before shadow above. */
body[data-global-nav="compact"] #app-space:not(.injecting)::before,
body[data-global-nav="compact"] #app-space.state-on::after {
    opacity: 0 !important;
}

/* With the header shadow gone (above), paint a faint 4px shadow just under the
 * cloud header, spanning the FULL viewport width, that reads as content sliding
 * beneath it. It hangs off #main::after rather than the content panes: a
 * pane-level ::after is trapped in that pane's stacking context —
 * .document-body sits at z-index 0 beside the z-index 1 sidebar, so its shadow
 * was clipped to the content area and never reached over the sidebar. #main is
 * the app shell — a position:fixed box pinned to all four viewport edges — so
 * its ::after spans the whole viewport and, sitting a level above #app-space
 * (z-index 11) with z-index 12, clears the sidebar and every app pane
 * edge-to-edge. position:fixed (offset by the header height) holds it in place
 * as content scrolls beneath.
 *
 * Both the fill and the thickness are read from vars with the default (a faint
 * 4px gradient) supplied as the var() *fallback* rather than declared here — so
 * the only place that ever sets these vars is a theme that wants to override
 * them. The ink theme (assets/themes/ink/style/all.css) sets them at :root to a
 * crisp 1px black line; because this file loads after the brand sheet, an
 * override declared on a selector here would beat ink — the fallback keeps ink
 * (:root) the sole declaration, so it wins. */
body[data-global-nav="compact"] #main::after {
    content: "";
    position: fixed;
    top: var(--global-header-height);
    right: 0;
    left: 0;
    height: var(--kik-cloud-top-shadow-height, 4px);
    background: var(--kik-cloud-top-shadow,
        linear-gradient(
            to bottom,
            color-mix(in srgb, black 8%, transparent) 0%,
            transparent 100%));
    z-index: 12;
    pointer-events: none;
}

/* Mobile (phones ≤680px): hide the cloud-header top shadow entirely. */
@media (max-width: 680px) {
    body[data-global-nav="compact"] #main::after {
        display: none;
    }
}

/* No tabs bar in Compact mode, so pull the app sidebar flush to the top/left
 * of the app area (reclaiming the space the tabs bar used to occupy). */
body[data-global-nav="compact"] .sidebar {
    --sidebar-margin-top: 0;
    --sidebar-margin-bottom: 0;
    --sidebar-margin-left: 0;
}
/* White sidebars in Compact mode. Set the variable (not background-color
 * directly) so the sidebar toggle — which also reads
 * --sidebar-background-colour — picks up the same white.
 * Also zero the sidebar's left margin HERE (on the document-body's ancestor,
 * not just on .sidebar): the document body computes its open-sidebar left
 * offset as calc(--sidebar-width + --sidebar-margin-left) in its own scope, so
 * without this it kept the default 15px margin and sat slightly too far right.
 * Now its left equals the sidebar's effective width. */
body[data-global-nav="compact"] .application-body {
    --sidebar-background-colour: white;
    --sidebar-margin-left: 0;
    /* Override the document body's open-sidebar left offset directly: the base
     * value is calc(--sidebar-width + --sidebar-margin-left), and the inner
     * --sidebar-margin-left doesn't pick up the 0 above at the document body's
     * scope (it keeps the 15px default), so the document body sat 15px too far
     * right. Drop the margin term entirely — left == the sidebar width. */
    --document-body-left-distance-sidebar-open: var(--sidebar-width);
}
/* Events app: white application-body background in Compact mode. */
body[data-global-nav="compact"] .application-body.application-events {
    --application-body-background-colour: white;
}
body[data-global-nav="compact"] aside.sidebar.left {
    height: 100%;
    border-radius: 0;
}
body[data-global-nav="compact"] aside.sidebar.left .sidebar-content {
    border-radius: 0;
}
/* A large sidebar (e.g. the Tasks list) spans the full viewport width in
 * Compact mode. Its base width is calc(100% + …) of the app body, which is
 * narrower than the viewport and left a strip on the right; pin it to the
 * viewport instead. This also pushes the document body fully off-screen, since
 * its open-sidebar left offset tracks --sidebar-width. */
body[data-global-nav="compact"] .application-body.sidebar-large aside.sidebar.left {
    --sidebar-width: 100vw;
}
/* The 100vw above only retargets --sidebar-width, but the OPEN large sidebar's
 * actual `width` is set by a separate, higher-specificity base rule:
 *   @container main (min-width: 769px) {
 *     .sidebar-left-open.sidebar-large aside.sidebar.left {
 *       width: calc(100% - 2 * var(--sidebar-margin-left));
 *     }
 *   }
 * The 2 × --sidebar-margin-left inset is the right thing in Tabs/Rail mode, but
 * in Compact it stops the sidebar short and leaves a strip on the right. The
 * sidebar is absolutely positioned and its containing block is #app-space
 * (left: 0; right: 0 — always the full viewport), so 100% with no inset spans
 * the viewport exactly (and, unlike 100vw, never adds a scrollbar-gutter
 * overflow). The extra body[data-global-nav]/.application-body terms out-rank
 * the base selector. */
body[data-global-nav="compact"] .application-body.sidebar-left-open.sidebar-large aside.sidebar.left {
    width: 100%;
}
/* Pin the document body beside the sidebar from the narrow-desktop breakpoint
 * (769px) in Compact mode. The base only shifted it right at >=1280px; between
 * 769-1280px it fell back to left:0 (full width, sidebar overlapping it) —
 * which read as "document body too wide". Compact has minimal chrome, so the
 * side-by-side layout should kick in earlier.
 *
 * Asks the CONTAINER, not the viewport: as a @media rule this measured the
 * screen, so a 400px-wide window on a 1400px desktop still got the full
 * desktop sidebar offset — the sidebar inside it was in phone mode, and the
 * content was shoved sideways by a gutter nothing occupied. (The base rule in
 * document-body.css has meanwhile moved from 1280px to this same >768px
 * container gate, so this block now only adds the Compact specificity.)
 *
 * Carries the same ``:has(aside.sidebar)`` gate as that base rule: the blanket
 * ``sidebar-left-open`` on every app body declares a sidebar's landing state,
 * not its existence, so only a body that HOLDS one reserves the gutter. */
@container main (width > 768px) {
    body[data-global-nav="compact"] .sidebar-left-open:not(.sidebar-left-closed):has(aside.sidebar) .document-body,
    body[data-global-nav="compact"] .sidebar-left-open:not(.sidebar-left-closed):has(aside.sidebar) #document-body {
        left: var(--document-body-left-distance-sidebar-open);
    }
    body[dir="rtl"][data-global-nav="compact"] .sidebar-left-open:not(.sidebar-left-closed):has(aside.sidebar) .document-body,
    body[dir="rtl"][data-global-nav="compact"] .sidebar-left-open:not(.sidebar-left-closed):has(aside.sidebar) #document-body {
        left: var(--document-body-right-distance);
        right: var(--document-body-left-distance-sidebar-open);
    }
}

/* Messages (Element) app fills its app body in Compact mode — pin all four
 * insets to 0 (the base rule offsets by the --sidebar-margin-* vars). */
body[data-global-nav="compact"] .application-body.application-messages .pat-element {
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
}
body[data-global-nav="compact"] .pat-element iframe {
    border-radius: 0;
}
/* Collabora office editor fills its app body flush in Compact mode. */
body[data-global-nav="compact"] .document-content .collabora-editor-frame {
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    border-radius: 0;
    box-shadow: none;
}

/* Compact-mode app labels (multi-tasking): one label per OPEN app, laid out
 * left-to-right in #kik-app-labels. Each label is an app-colour-bordered pill of
 * [icon · name · close]. Show/hide + active highlighting are driven entirely by
 * the per-app :has() rules in the kik_app_label partial's inline <style> (no
 * JS); here we set the row layout, the box and the appearance.
 *
 * #kik-app-labels fills the header between the home brand and the right-floated
 * controls (avatar / candy box / favourites). It's the sole in-flow child of
 * #global-header and establishes a flex formatting context, so it shrinks to
 * clear those right floats automatically — no hard-coded width reserve. Its
 * margin-left clears the absolutely-positioned home tile (15px inset + 36px tile
 * + gap). overflow:hidden clips any labels that still don't fit once shrunk. */
/* #kik-app-labels is a BLOCK box (overflow:hidden establishes its own formatting
 * context) so its auto width FILLS the gap left of the right-floated controls in
 * every engine. The flex row lives on the inner .kik-app-labels-row. Flexing
 * #kik-app-labels directly breaks WebKit: a flex formatting context beside a
 * float is sized shrink-to-fit, collapsing to min-content — and with the names'
 * min-width:0 + ellipsis that is just the icons, so on iPad every name ellipsised
 * to nothing even on an empty header. A block box fills, giving the inner flex a
 * real width so names only ellipsise when genuinely crowded. */
#global-header .kik-app-labels {
    display: block;
    height: var(--global-header-height);
    /* No left margin: the absolutely-positioned home tile it used to clear is
     * gone (2026-08-16), so the row starts at the header's own padding. */
    overflow: hidden;
    /* The row fills the full free width, but its empty stretch must not eat
     * taps meant for the content scrolled under the (transparent, pointer-events
     * :none) header on small screens — so only the labels themselves are live. */
    pointer-events: none;
}
#global-header .kik-app-labels .kik-app-labels-row {
    display: flex;
    align-items: center;
    gap: 6px;
    height: 100%;
    overflow: hidden;
}
#global-header .kik-app-labels kik-app-label {
    pointer-events: auto;
}
body[dir="rtl"] #global-header .kik-app-labels {
    margin-left: 0;
    margin-right: 44px;
}
kik-app-label {
    position: relative;
    align-items: center;
    gap: 0;
    /* Idle: icon glyph + name in the muted base header ink (#6B6560, the candy
     * box's colour), and a transparent border. The per-app active rule
     * (kik_app_label.html) overrides --kal-text / --kal-border to the app colour. */
    color: var(--kal-text, #6B6560);
    /* Same overall height as the global-header icons (36px), the 1.5px border
     * included (box-sizing). The inner .kal-icon is sized down to fit inside. */
    box-sizing: border-box;
    height: 36px;
    padding: 0;
    border: 1.5px solid var(--kal-border, transparent);
    /* The header's corner. Shared, because everything that rides this bar has
       to round the same way — the media-player chip's album art takes it too
       (base.css). Change it here and the row follows. */
    border-radius: var(--kal-corner-radius, 8px);
    transition: border-color 0.2s;
    /* Flex item: shrink when the row is crowded (min-width:0 lets the name
     * ellipsis), while the icon + close inside it never shrink. */
    flex: 0 1 auto;
    min-width: 0;
}

/* Tab hover keeps only the background wash and the border line (Daniel,
 * 2026-08-15) — the icon/name darkening is retired. */
/* The icon tile. Idle: transparent background, glyph in the app colour (the
 * label's border already carries the app identity). Active: the per-app rule
 * fills the tile with the app colour, flips the glyph white and scales it to 0.8
 * so a ring of the label's (white) background shows around it — all via the
 * --kal-icon-* custom properties, with a transition on the transform. */
kik-app-label .kal-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    /* Square tile, DEFINITE 33px (the label's 36px height minus its 1.5px
     * border top+bottom). The app-icon SVGs are viewBox-only (no intrinsic px
     * size). A definite width matters in TWO WebKit places:
     *   1. Visual: width:auto + the flex default min-width:auto lets the
     *      indeterminate SVG width balloon the tile to full width (old iOS bug).
     *   2. Layout: WebKit resolves an auto/aspect-ratio flex item's FLEX BASIS
     *      from the SVG's ~300px fallback intrinsic, NOT the aspect-ratio-derived
     *      width, so each tab is measured ~300px wide → the row over-subscribes →
     *      flex-shrink ellipsises every .kal-name even with a near-empty header
     *      (tiny icons + acres of space + truncated names, Safari/iPad only).
     * A definite width fixes both: real 33px basis, names ellipsise only when the
     * row is genuinely crowded. (Mirrored inline in kik_app_label.html so it ships
     * without a SASS recompile — the assets submodule is volatile.) */
    width: 33px;
    min-width: 33px;
    height: 33px;
    flex: 0 0 33px;
    /* contain:size makes WebKit measure the tile purely from its CSS size and
     * IGNORE the viewBox-only SVG's phantom intrinsic — the definite width above
     * is meant to do this alone, but some WebKit builds still leak a flex item's
     * content intrinsic into the ancestor (tab row) sizing pass, re-inflating the
     * row and re-ellipsising the names. contain:size + overflow:hidden slams that
     * door shut. */
    contain: size;
    overflow: hidden;
    padding: 4px;
    border-radius: var(--kik-global-nav-icon-radius);
    background-color: var(--kal-icon-bg, transparent);
    color: var(--kal-icon-fg, #6B6560);
    transform: scale(var(--kal-icon-scale, 1));
    transition: all 0.2s;
    /* The icon is the app-label menu trigger; disable it on inactive tabs so a
     * click there can't open the menu. The active tab re-enables it via
     * --kal-icon-pointer (set in the per-app active rule in kik_app_label.html). */
    pointer-events: var(--kal-icon-pointer, none);
}
/* Roughly half the app icons are authored as solid white silhouettes
 * (fill="white"); tie their fill to currentColor so they follow .kal-icon's
 * colour exactly like the outline (stroke: currentColor) icons do. Outline icons
 * (fill: none) are intentionally NOT matched, so they stay outlines. */
kik-app-label .kal-icon svg[fill="white"] *,
kik-app-label .kal-icon svg [fill="white"] {
    /* Ink flips this to a transparent fill + a NON-scaling stroke so the
     * solid-silhouette icons read as black outlines (like the app grid) at the
     * tiny tab size; without non-scaling-stroke a 1px stroke in the 512 viewBox
     * collapses to sub-pixel and the glyph vanishes. Defaults (fill:currentColor,
     * no stroke) keep every other brand's filled rendering. Target the shapes,
     * not the <svg>, so vector-effect (not inherited) actually applies. */
    fill: var(--kal-icon-fill, currentColor);
    stroke: var(--kal-icon-stroke, none);
    stroke-width: var(--kal-icon-stroke-width, 0);
    vector-effect: non-scaling-stroke;
}
kik-app-label .kal-icon svg {
    width: 100%;
    height: 100%;
    transform: scale(var(--kal-icon-svg-scale, 0.8));
}
kik-app-label .kal-icon svg * {
    transition: all 0.2s;
}
/* The tab body: a pat-switch <button> that activates this app, reset to read as
 * plain text (it inherits the label's app colour). */
kik-app-label .kal-name {
    appearance: none;
    border: 0;
    background: transparent;
    padding: 0;
    margin: 0;
    color: inherit;
    font-family: inherit;
    text-align: left;
    cursor: default;
    font-weight: 300;
    font-size: 22px;
    white-space: nowrap;
    /* Only the text is sacrificed when the row is crowded (req: close + icon
     * stay intact). */
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 0 1 auto;
}
/* Stretched-link hit-layer: the name button's switch covers the WHOLE tab, so a
 * click anywhere on it activates the app. It sits BEHIND the icon and × (z-index
 * 1 vs their 2): an inactive icon (pointer-events:none) lets the click fall
 * through to it, while the active icon (pointer-events:auto) and the × stay on
 * top with their own actions. overflow:hidden on the button doesn't clip it —
 * the ::before's containing block is the (position:relative) kik-app-label. */
kik-app-label .kal-name::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}
kik-app-label .kal-icon,
kik-app-label .kal-close {
    position: relative;
    z-index: 2;
}
/* Close button (an <a> → close_app, so the close is recorded in the session):
 * the mark in the muted base header ink for inactive tabs (the active tab's
 * rule flips --kal-close-colour to the app colour). No background in any
 * state: the mark is --glyph-cancel-circle (kik_app_label.html), which draws
 * its OWN circle at the button's full size, so a plate behind it was a second
 * circle 1.6px outside the first. Hover is carried by the colour alone. */
kik-app-label .kal-close {
    /* The ring. It lives on the BUTTON, not on the ::before that draws the ×
       — the pseudo is a 14px box centred in this 20px one, so a border there
       drew a second, smaller circle inside this one. Colour follows the active
       state through --kal-close-border (the app colour on the active tab, 50%
       black off it), the same way --kal-close-glyph-bg drives the fill. */
    border: 1px solid var(--kal-close-border, rgba(0, 0, 0, 0.5));
    background: transparent;
    text-decoration: none;
    cursor: default;
    font-size: 14px;
    line-height: 1;
    /* No optical padding here. This described the text glyph U+00D7, which the
     * mark no longer is — kik_app_label.html collapses the anchor's own text
     * and draws --glyph-cancel on ::before, correcting the glyph there. Two
     * corrections stacked would fight; the id-specificity rule in the template
     * already zeroed this one, so it only survived as a misleading comment. */
    padding: 0;
    border-radius: 50%;
    color: var(--kal-close-colour, #6B6560);
    /* Hidden + collapsed on inactive tabs (no × taking space); the active tab's
     * rule sets --kal-close-w / --kal-close-mx / --kal-close-opacity to reveal it.
     *
     * Collapse by WIDTH, not `zoom`. `zoom` is a WebKit layout property, and zoom
     * on a flex item corrupts the tab row's intrinsic sizing: WebKit reserved each
     * inactive ×'s footprint at its UNZOOMED ~18px+margins (not the zoomed-away
     * size it painted), so every inactive tab carried a phantom close that
     * over-subscribed the row → flex-shrink ellipsised the names even with room to
     * spare (Safari/iPad only — the active tab's honest zoom:1 × never triggered
     * it). Width/margin are sized honestly by every engine: 0 when collapsed, 18px
     * on the active tab, eased. Mirrored inline in kik_app_label.html (which also
     * neutralises the stale built-CSS zoom until the submodule recompiles). */
    opacity: var(--kal-close-opacity, 0);
    transition: opacity 0.2s ease, width 0.2s ease, margin 0.2s ease;
    display: flex;
    flex: 0 0 auto;
    box-sizing: border-box;
    width: var(--kal-close-w, 0);
    min-width: var(--kal-close-w, 0);
    /* 20px, per design. NOTE the tab is 36px with a 1.5px border, so the box
     * the disc centres in is 33px — ODD — and an even disc leaves 6.5px above
     * and below, which the browser lands on a half pixel. (19px divided the 33
     * exactly, 7 and 7; 21px would too, at 6 and 6.) Width follows from
     * --kal-close-w in kik_app_label.html, which carries the same 20 so the
     * disc stays a circle. */
    height: var(--kal-close-h, 20px);
    /* visible, so the ::after hit area below can extend past this box.
     * The collapsed × no longer needs clipping: opacity 0 keeps it unseen and
     * pointer-events: none keeps it unclickable. */
    overflow: visible;
    pointer-events: none;
    align-items: center;
    justify-content: center;
    margin: 0 var(--kal-close-mx, 0);
}
/* Thumb-sized hit area without a bigger ×: an invisible pseudo extends the
 * clickable surface past the 18px visual button — square, and the full height
 * of the tab (36px). Hit-testing only, nothing painted; a click on a pseudo is
 * dispatched to its originating element, so the <a> stays the event target and
 * the pat-switch/pat-inject close wiring is untouched.
 *
 * Sized from --kal-close-hit rather than fixed, so it is literally 0×0 on
 * inactive tabs. That matters now that the button is overflow: visible — a
 * fixed 36px box would sit over every collapsed × as an invisible close target
 * and swallow clicks meant for the tab's own switch surface. The active tab's
 * rule sets it alongside --kal-close-w (see kik_app_label.html).
 *
 * (This replaced a ::before with negative insets. That never worked: the
 * button was overflow: hidden, which clips a pseudo out of the hit test as
 * surely as it clips it out of the paint, so the enlarged area it described
 * did not exist.) */
kik-app-label .kal-close::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: var(--kal-close-hit, 0);
    height: var(--kal-close-hit, 0);
    /* Re-enable hits inside the pseudo only; the button itself is
     * pointer-events: none so the × glyph, which overflows a 0-width box when
     * collapsed, cannot be clicked on an inactive tab. */
    pointer-events: auto;
}

/* No candy-box trigger rules here any more. In compact mode the launcher is a
 * <kik-app-label class="kal-candy"> holding a .kal-icon anchor — the same
 * markup as a pinned app — so the tab rules above give it its box, colour and
 * hover. (The side rail's own launcher is .cnr-candy, in cloud-nav-rail.css.) */

/* The candy-box grid is styled ONLY by .kik-apps-grid — see
 * static/libraries/kikaron/apps-grid/apps-grid.css. It used to override the
 * column count, gap, padding, label size, icon colour, stroke width and
 * border-radius here (and its colour inversion from the brand sheet), so the
 * launcher and the dashboard drifted apart and a change to 'the apps grid'
 * had to be made twice. The popover's own width lives in
 * libraries/patterns/context-menu/pat-context-menu.css, which is the shell
 * now — the .tippy-box rule that used to sit here was dead. */

/* Cloud/compact mode: the global search FIELD (base.html) is swapped for a
 * launcher button for the Search app, sitting in the field's old header slot
 * (floated right, next to the candy-box). The Search rail icon is a stroked
 * (outline) magnifier painted with currentColor, so the launcher only sets
 * `color`: the app colour by default (visible on the light header). It's pure
 * pat-switch — clicking just brings the always-present #application-body-search
 * pane forward (its pat-lazy-load anchor autoloads the search home the first
 * time it's revealed). */
#global-header .kik-search-launcher {
    float: right;
    /* Match the kik-app-label height (36px, border included), so size the
     * launcher the same and centre it vertically in the header. */
    margin-top: calc((var(--global-header-height) - var(--kik-global-nav-icon-size)) / 2);
    margin-left: var(--application-label-white-space);
    box-sizing: border-box;
    width: var(--kik-global-nav-icon-size);
    height: var(--kik-global-nav-icon-size);
    padding: 5px;
    border: 0;
    border-radius: var(--kik-global-nav-icon-radius);
    background-color: transparent;
    color: var(--app-colour);
    transition: background-color var(--base-animation-duration),
                color var(--base-animation-duration);
}
body[dir="rtl"] #global-header .kik-search-launcher {
    float: left;
    margin-left: 0;
    margin-right: var(--application-label-white-space);
}
#global-header .kik-search-launcher svg {
    display: block;
    width: 100%;
    height: 100%;
    stroke: currentColor;
}
@media (hover: hover) {
  #global-header .kik-search-launcher:hover {
      background-color: rgba(0, 0, 0, 0.06);
  }
}

/* "current": Search is the active, on-screen app — fill the tile with the app
 * colour and flip the outline glyph to white so it reads against it, mirroring
 * the side-rail .gns-item.current treatment. The :has() reuses kik-app-label's
 * active-app detection (#app-space.state-on.active-app-search). This file is
 * cache-busted (asset_version), so the :has() rule can live here rather than
 * inline in the template. */
body[data-global-nav="compact"]:has(#app-space.state-on.active-app-search) #global-header .kik-search-launcher {
    background-color: var(--app-colour);
    color: #fff;
}
@media (hover: hover) {
  body[data-global-nav="compact"]:has(#app-space.state-on.active-app-search) #global-header .kik-search-launcher:hover {
      background-color: var(--app-colour);
  }
}

/* ------------------------------------------------------------------ */
/* One/Pro phones: the bar carries the candy box, and nothing else      */
/* ------------------------------------------------------------------ */
/*
 * Daniel, 2026-08-12. The apps carousel is the app switcher on these faces —
 * swipe between open apps, launcher page on the left — so a row of app icons
 * in the bar was a second switcher for the same job, and the brandmark Home
 * and the pinned Preferences were third and fourth doors to what the launcher
 * already shows. One control remains: the candy box, which jumps to the
 * launcher page.
 *
 * Hub keeps its full bar — there the icons ARE the navigation.
 *
 * Last in the file, and !important, because the per-width icon-cap blocks and
 * the pinned-Preferences rule are later media queries that set display
 * themselves (Preferences with !important of its own).
 */
@media only screen and (max-width: 768px) {
    body[data-global-nav="compact"] #global-nav-side .gns-mobile-home,
    body[data-global-nav="compact"] #global-nav-side .gns-apps .gns-item:not(.gns-candy-item) {
        display: none !important;
    }

    /* The bar spreads its children edge to edge (justify-content:
     * space-between), which parks a lone control against the left rim. The
     * candy box goes to the RIGHT rim instead (Daniel, 2026-08-12) — the
     * middle of the bar is taken: the apps carousel's rail of balls docks
     * there, centred, so this face reads left-to-right as "where you are"
     * (the balls) then "everywhere you could go" (the launcher).
     *
     * The rail seats itself absolutely against the bar's midpoint rather than
     * riding this flex line (phone_app_carousel.html, .kik-pac-dots--docked),
     * so it stays dead centre no matter how wide the candy box is — the bar's
     * own `position: fixed` (the base #global-nav-side rule) is the
     * positioning context that seating hangs off. */
    body[data-global-nav="compact"] #global-nav-side {
        justify-content: flex-end;
    }
}

/* ------------------------------------------------------------------ */
/* The dock strip: one clearance, and it yields to a focused composer   */
/* ------------------------------------------------------------------ */
/*
 * The bar is the DOCK on a phone. Two things hang off that (Daniel,
 * 2026-08-12), and both are CSS only — nothing measures a composer or a
 * keyboard in JavaScript any anymore:
 *
 *   --kik-dock-clearance is how much room the dock takes at the bottom of the
 *   screen: its 64px height, its 10px offset, and 8px of air. Anything that
 *   would otherwise sit in that strip reads this and rests above it — the
 *   carousel's dot rail, and the sticky chat composer (messages/styling).
 *
 *   While a composer HAS FOCUS the dock steps down out of the way, because
 *   that is the moment the strip is worth more to the person typing (and on a
 *   phone it is also when the keyboard has taken everything below). The travel
 *   is the scroll auto-hide's, so the two never fight. :has() on <body>: the
 *   composer is inside an app page and the bar is a sibling of #app-space.
 */
@media only screen and (max-width: 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) {
        /* What anything resting ON TOP of the dock has to clear: the bar's
           10px offset from the screen edge, its 64px height, the 1px hairline
           it draws, and 10px of air — Daniel wants the chat composer exactly
           ten pixels above the dock (2026-08-12). */
        --kik-dock-clearance: calc(85px + env(safe-area-inset-bottom, 0px));
        /* The dock's icon box, stated once so the two things wearing it — the
           candy box at the right rim and the active-app icon at the left
           (phone_app_carousel.html, .kpa-icon) — cannot drift apart
           (Daniel, 2026-08-12). */
        --kik-dock-icon-size: 46px;
        --kik-dock-icon-radius: 10px;
    }

    /* The media-player chip rides IN the dock, immediately left of the candy
       box (Daniel, 2026-08-12). Its default perch is the top-left corner —
       which belonged to the icon rail, and on these faces there is no longer
       any chrome up there at all, so it floated over the app's own toolbar.

       media-player.js parents it INTO the bar on these faces (dockChip), the
       way the rail and the active-app label are; it stays outside #app-space,
       so no pat-inject can swap it out mid playback. It keeps position: fixed
       — and the bar's backdrop-filter makes the bar its containing block, so
       these offsets read against the BAR: 8px of bar padding + the candy
       box's 50px hit area + 8px of air from the right rim, and a vertical
       centre in the 64px control band above the safe area.

       Nothing here has to hide it with the dock, either: it is inside the bar
       now, so it slides away with it. */
    body[data-global-nav="compact"] .kik-media-player-chip {
        top: auto;
        left: auto;
        right: 66px;
        /* The dock's icon box, like the candy box and the active app's icon:
           one size and one radius for everything seated in this strip
           (Daniel, 2026-08-12). */
        width: var(--kik-dock-icon-size);
        height: var(--kik-dock-icon-size);
        border-radius: var(--kik-dock-icon-radius);
        bottom: calc(
            (var(--workspace-tabs-bar-height, var(--gns-mobile-height))
             - var(--kik-dock-icon-size)) / 2
            + env(safe-area-inset-bottom, 0px));
    }

    /* The candy box's own button paints WHITE — it is a .gns-link, and those
       inherit a button surface (measured rgb(255,255,255) behind a bar that is
       itself a translucent white pill, so it read as a solid chip around the
       glyph). The bar IS the surface here; the trigger is just a hit area
       (Daniel, 2026-08-12). */
    body[data-global-nav="compact"] #global-nav-side .gns-candy-trigger {
        background: transparent;
    }

    /* The candy box takes that box. The bar's generic .gns-icon is 44px, a
       size inherited from the days when the bar was a row of app tiles; the
       one control left on this face does not need to be the biggest thing in
       it. The 48px .gns-link around it is untouched, so the TAP target keeps
       its size. */
    body[data-global-nav="compact"] #global-nav-side .gns-candy-item .gns-icon {
        width: var(--kik-dock-icon-size);
        height: var(--kik-dock-icon-size);
        border-radius: var(--kik-dock-icon-radius);
    }

    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(.kik-chat-composer:focus-within) #global-nav-side {
        transform: translateY(calc(100% + 40px));
    }
}

/* ------------------------------------------------------------------ */
/* A composer follows the dock down                                     */
/* ------------------------------------------------------------------ */
/*
 * The bar hides itself on a downward scroll (pat-scroll-box's scroll-down on
 * <body>, above), and a
 * composer left floating 85px up over the space it vacated reads as a bar with
 * a hole beneath it. Drop it to the plain inset — the same place the focused
 * composer sits, because it is the same situation from the other end: nothing
 * is in the dock strip.
 *
 * Here rather than in the messages stylesheet because the registry build
 * prefixes every selector in that sheet with `.application-body
 * .application-messages`, which both mangles a `body.…` selector and gives the
 * resting rule a specificity of (0,3,0) — hence the :is() here, which buys the
 * one class needed to out-rank it.
 */
@media only screen and (max-width: 768px) {
    body:has(.application-body.state-on.focus-document .document-body:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) :is(.application-body, .kiki-dock) .kik-chat-composer,
    body:has(.application-body.state-on.focus-sidebar .sidebar-content:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) :is(.application-body, .kiki-dock) .kik-chat-composer {
        --kik-composer-inset: 15px;
    }
}

/* ------------------------------------------------------------------ */
/* One and Pro: the dock is a bar, not an island                       */
/* ------------------------------------------------------------------ */
/*
 * On the compact faces the dock runs edge to edge and sits ON the bottom of
 * the screen, square-cornered, with the home-indicator safe area added BELOW
 * its 64px of controls rather than eaten out of them (Daniel, 2026-08-12).
 * Hub keeps the floating pill.
 *
 * `compact` IS One and Pro — the two share that chrome value, which is why one
 * attribute covers both.
 *
 * Last in the file because it overrides the ≤768px bar block above AND the
 * standalone-PWA insets that follow it — all three carry the same specificity,
 * so source order is what decides.
 */
@media only screen and (max-width: 768px) {
    body[data-global-nav="compact"] {
        /* No 10px gap under the bar any more, so what rests on top of the
           dock clears its height, its hairline, the safe area it now covers,
           and the same 10px of air. */
        --kik-dock-clearance: calc(75px + env(safe-area-inset-bottom, 0px));
    }

    body[data-global-nav="compact"] #global-nav-side {
        left: 0;
        right: 0;
        bottom: 0;
        border-radius: 0;
        /* box-sizing is border-box here, so the height has to grow by the
           safe area for the padding to add space UNDER the controls instead
           of squeezing them. */
        height: calc(var(--workspace-tabs-bar-height, var(--gns-mobile-height))
                     + env(safe-area-inset-bottom, 0px));
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }
}
