/* 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));
    /* The rail's opaque ground — the theme's card tone (white on every
       light theme; this sheet loads after the theme, so the literal was
       unreachable from a dark theme's :root). */
    --gns-background: var(--colour-base-light, #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 (base/
       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 (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 {
    /* The platform wordmark, dark-lettered: a dark theme re-inks it (see
       --kik-logo-filter in the theme; the swirl keeps its orange). This
       slot only ever shows the PLATFORM logo, so no custom-logo guard is
       needed here. */
    filter: var(--kik-logo-filter, none);
    /* 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;
}
/* The arrow is the house down drawing, masked so it takes the text colour
 * (Kikaron loads no icon font — settings.ICON_SET). */
#global-nav-side .gns-hub-name::after {
    flex: 0 0 auto;
    content: '';
    width: 1em;
    height: 1em;
    background-color: currentColor;
    -webkit-mask: var(--kik-svg-down) center / contain no-repeat;
    mask: var(--kik-svg-down) center / contain no-repeat;
    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 (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 used to carry it with the hub list — the hub hopper in the
 * candy box, removed 2026-09-29.) */
@media (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;
    /* The rail's own muted token, not the literal it happens to hold: the
       token chains to --colour-discreet, which is the dark theme's way in
       (the phone bar's candy box was unreadably dark there). */
    color: var(--gns-muted, #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: var(--gns-muted, #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 * {
    /* The app icons' own wash (--app-icon-fill-opacity, 0% on a theme
       without the knob) in the line's colour — the duotone the tiles wear. */
    fill: color-mix(in srgb, var(--gns-muted, #6B6560) var(--app-icon-fill-opacity, 0%), transparent) !important;
    stroke: var(--gns-muted, #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: color-mix(in srgb, #fff var(--app-icon-fill-opacity, 0%), transparent) !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. That colour is stored per entry, so a dark theme
     * cannot restate it the way it restates the app palette — it lifts it
     * instead, through --kik-gns-space-lift (0% here: the stored colour).
     *
     * TWO LIFTS, because the avatar has two jobs. The unopened state is a
     * ring and a letter drawn IN the colour, so it has to stand off the
     * ground and wants the full lift. The open one is a filled plate with a
     * WHITE letter on it, where the same lift would leave white on near-white
     * — so it takes --kik-gns-space-fill-lift, its own, smaller step. */
    --gns-space-source: var(--app-icon-contrast-colour, var(--gns-space-colour, var(--gns-accent)));
    --gns-space-tint: color-mix(in srgb, white var(--kik-gns-space-lift, 0%), var(--gns-space-source));
    --gns-space-fill: color-mix(in srgb, white var(--kik-gns-space-fill-lift, 0%), var(--gns-space-source));
    background: color-mix(in srgb, var(--gns-space-tint) 10%, white);
    color: var(--gns-space-tint);
    border: 1px solid var(--gns-space-tint);
    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: var(--gns-muted, #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(--gns-space-fill);
    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: var(--gns-muted, #6B6560);
    color: var(--gns-muted, #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;
    /* Half the small-screen padding above the dock: the dock's own inset
     * (see the floating bar below) plus its height plus the gap (Daniel,
     * 2026-09-27 the whole padding; 2026-09-28 half of it). */
    bottom: calc(
        max(10px, var(--kik-safe-bottom))
        + var(--workspace-tabs-bar-height, var(--gns-mobile-height))
        + 0.5 * var(--small-screen-padding)
    );
    /* Exactly as wide as the dock under it (Daniel, 2026-09-29): the dock's
     * own side inset, from the variable the dock's geometry is stated in at
     * the foot of this file (10px, 23px installed as a PWA). */
    left: var(--kik-dock-inline, 10px);
    right: var(--kik-dock-inline, 10px);
    width: auto;
    max-width: none;
    box-sizing: border-box;
    /* A little tighter than the 16px it had (Daniel, 2026-09-29). */
    padding: 10px;
    border: none;
    border-radius: var(--border-radii, 8px);
    /* THE DOCK'S OWN MATERIAL (Daniel, 2026-09-29: "why don't the popover and
     * the dock have the same background colour"). It carried a hand-rolled
     * blur(10px) saturate(4) brightness(1.2): four times the saturation the
     * dark theme gives its surfaces and a 20% brightening, so over the same
     * page the popover came out a warm, lighter brown than the dock right
     * under it. It now reads the same two tokens the dock does
     * (#global-nav-side's ≤768px block), and so follows every theme with it. */
    background-color: var(--pat-toolbar-button-background-colour,
                          var(--pat-surface-background-colour, rgba(255, 255, 255, 0.9)));
    -webkit-backdrop-filter: var(--pat-surface-backdrop-filter, none);
    backdrop-filter: var(--pat-surface-backdrop-filter, none);
    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;
}
/* ONLY THE ACTIVE APP IS COLOURED (Daniel, 2026-09-29: "show the app icons
 * without the coloured box and as monochrome icons, except for the active
 * app"). Every app wore its colour tile from 2026-09-27; now the rest are the
 * muted line glyphs above, bare, and the one in front keeps the tile — the
 * same single coloured square the dock itself shows. The body:is() prefix
 * and !important outrank the mobile bar's ink rule, which reaches in here. */
body #global-nav-side .gns-more-popover .gns-more-app.current .gns-icon {
    background-color: var(--app-colour, var(--gns-accent));
}
body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-more-popover .gns-more-app.current .gns-icon.app-icon *,
#global-nav-side .gns-more-popover .gns-more-app.current .gns-icon.app-icon * {
    fill: #fff !important;
}
body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-more-popover .gns-more-app.current .gns-icon.rail-icon *,
#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 {
    /* A step up from the body size the list inherited (Daniel, 2026-09-29:
     * "a bit larger"). */
    font-size: 1.1em;
    /* 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 micro-navigation section (Library detail) or is itself
 * wrapped in its own nested micro-navigation 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.micro-navigation:has(> .pat-back-button) {
    order: -1;
}

/* 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.
 *
 * GRID, NOT BLOCK (2026-09-03). This rule only ever had to undo a
 * `display: none`, and `block` is not what it was undoing it TO: a
 * .pat-toolbar is `display: grid`, its two sections placed in named areas
 * (`first` / `last`) by the toolbar component. Made a block, the areas still
 * computed and the sections became ordinary block siblings — so on a
 * contact's page the back control sat on one line and the whole
 * quick-functions cluster (call, star, More, Edit) on a second one under it,
 * a bar two rows tall (Daniel, measured: micro at y=15, quick-functions at
 * y=50). Restating the component's own value is the whole fix; nothing else
 * about the override changes.
 *
 * ASKED OF THE CONTAINER, NOT THE VIEWPORT (2026-09-04). The hide this undoes
 * is a container style query on ``--screen-small`` — true whenever the box the
 * page sits in is 680px or narrower, which is not the same thing as a narrow
 * WINDOW: an open sidebar or a modal panel makes that box small on a desktop
 * too. Written as a ≤768px media query, the undo simply did not fire there, so
 * a contact opened in a panel (or beside an open sidebar) lost its whole bar —
 * no way back, and no call, star, More or Edit either (Daniel, screenshot).
 * The two now ask the same question, so the undo reaches every case the hide
 * does. */
@container screen style(--screen-small: true) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #contact-nav-toolbar {
        display: grid !important;
    }
}

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

@media (width <= 768px) {
    /* On small screens the app toolbar's own .pat-back-button carries the
     * back affordance (the header-mounted mobile back proxy is gone — no
     * platform mode renders top nav). _toolbar.scss hides the button there;
     * it is re-shown and restyled as a plain toolbar .icon by
     * libraries/kikaron/back-button-nav-modes. 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;
    }
    /* …except an app landing's own name (Daniel, 2026-09-29: the Library
     * landing says "Library" on a phone). A bar may carry a second title,
     * .kik-phone-app-title, for exactly this: hidden at every other width
     * (below), shown here in the slot the section's title vacated. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body .pat-toolbar:has(.toolbar-section.navigation) .toolbar-title.kik-phone-app-title {
        display: inline-block;
    }
    /* The landing bar's centring (sidebar-handle.css) gives the section a
     * negative start margin of one slot, so its grid column is sized one slot
     * narrower than the words and "Library" came out "Bib…". At its own
     * max-content the section still centres on the bar: the overflow spills
     * evenly to both sides of the column. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body .pat-toolbar .toolbar-section.navigation:has(> .kik-phone-app-title) {
        min-width: max-content;
    }

    /* The pat-back-button lives in a .micro-navigation section, not the
     * .navigation one. On desktop that section is already the toolbar's
     * leading (first) grid column, but _toolbar.scss bumps micro-navigation 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 micro-navigation sections keep the middle placement. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #portal .pat-toolbar .toolbar-section.micro-navigation:has(.pat-back-button),
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .document-body .pat-toolbar .toolbar-section.micro-navigation: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;
        /* Above the iPhone's home indicator (the safe area — the app's own
           inset rides in as --kik-native-nav), 10px off the edge where there
           is none (Daniel, 2026-09-27). The page's bottom padding below
           already counts the safe area. */
        bottom: max(10px, var(--kik-safe-bottom));
        left: var(--kik-dock-inline, 10px);
        right: var(--kik-dock-inline, 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.
     *
     * BOTH RULES ASK FOR ``.kik-scrollable`` — the box has more content than it
     * can show (libraries/kikaron/scroll-box-scrollable). Without it the phone
     * could be left with no dock and no way back: pat-scroll-box calls a box
     * "at the bottom" whenever its scroll position reaches its own end, and on
     * iOS a rubber-band drag reports exactly that for a box that cannot scroll
     * at all — after which nothing scrolls, so no later event ever corrects the
     * class (Daniel, 2026-09-02: an empty Mail folder with the dock gone and
     * nothing he could do to get it back). The foot of a list is a place only a
     * scrollable box has.
     *
     * AND ``.kik-active-pane`` — the pane IN FRONT (libraries/kikaron/active-pane),
     * because ``state-on`` only says an app is open, and on the phone every
     * open app is. The direction key above narrowed this to "a box somebody
     * has pushed down", but a box somebody pushed down in ANOTHER app stays
     * pushed down while it sits parked beside this one, and it went on
     * hiding the dock over every app opened after it — with nothing in those
     * apps able to change a class living in that one (Daniel, 2026-09-09: the
     * dock gone on both screens of Messages, and scrolling did nothing). The
     * mark mirrors ``#app-space.active-app-<slug>`` onto the body it names,
     * which is the fact this rule always meant. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(.application-body.state-on.kik-active-pane.focus-document .document-body.kik-scrollable:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) :is(#global-nav-side, .kik-dock-veil),
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(.application-body.state-on.kik-active-pane.focus-sidebar .sidebar-content.kik-scrollable:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) :is(#global-nav-side, .kik-dock-veil),
    /* The home screen (the dashboard, and its activity stream) is no app
     * pane: on a phone it scrolls #portal-content-pane, which is a
     * pat-scroll-box for exactly this (base.html). Asked only while no app is
     * in front — #app-space without state-on — so a home screen left scrolled
     * down behind an open app does not hide the dock over that app. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(#portal-content-pane.kik-scrollable:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))):not(:has(#app-space.state-on)) :is(#global-nav-side, .kik-dock-veil) {
        /* The soft edge under the dock travels with it, the same distance on
           the same clock — see --kik-dock-travel at the foot of the file. */
        transform: translateY(var(--kik-dock-travel));
    }
    /* 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 on every light theme
     * (the token's fallback), the theme's full-strength light ink on a dark
     * one. This block outranks the muted-ink rules above, which is exactly
     * how the phone bar's candy box stayed pure black in dark mode after
     * those went theme-aware. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-icon.app-icon * {
        fill: var(--kik-gns-mobile-ink, #000) !important;
    }
    /* Rail line icons: colour the stroke, fill stays empty */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-icon.rail-icon * {
        fill: color-mix(in srgb, var(--kik-gns-mobile-ink, #000) var(--app-icon-fill-opacity, 0%), transparent) !important;
        stroke: var(--kik-gns-mobile-ink, #000) !important;
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side .gns-mobile-home img {
        /* brightness(0) is "force the brandmark to the bar's monochrome
           ink" for a black ink; a dark theme states the filter that lands
           on ITS ink instead. */
        filter: var(--kik-gns-mobile-brand-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)
            + var(--kik-safe-bottom)
            + 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)
            + var(--kik-safe-bottom)
            + 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))
            + var(--kik-safe-bottom)
        );
    }
    /* 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.
     *
     * ``#portal #portal-content-pane``, two ids: the small-screen shell
     * (quaive/portal) pads this pane with a ``padding:`` SHORTHAND under
     * exactly that selector, and with one id here the shorthand won and
     * set the bottom back to the phone's 15px — the hub home scrolled to
     * its last post and left it under the bar (Daniel, 2026-09-07: "the
     * stream has not enough bottom padding to scroll to above the dock").
     * The registry's paged-dashboard rule records the same fight. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #portal #portal-content-pane {
        padding-bottom: calc(
            var(--workspace-tabs-bar-height, var(--gns-mobile-height))
            + 0.5 * var(--small-screen-padding)
            + var(--kik-safe-bottom)
            + 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 (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 (360px <= 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 (490px <= 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 (580px <= 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 (670px <= 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 (760px <= 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 (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;
    }
    /* …only where the bar actually carries it. The Hub's bar has no
     * Preferences item, and there the popover's tile is the way in: a plain
     * app among the others, before Help (Daniel, 2026-09-29). */
    #global-nav-side:has(.gns-apps .gns-item.gns-app-preferences) .gns-more-popover .gns-more-app.gns-app-preferences {
        display: none;
    }
    .gns-more-popover .gns-more-app.gns-app-help {
        order: 1;
    }
}

/* THE HUB'S PILL IS FROSTED LIKE EVERY OTHER FLOATING CONTROL (Daniel,
 * 2026-09-29). It already took the surface wash and the surface blur; what set
 * it apart was the edge — a soft 3px drop shadow where the toolbar's round
 * buttons, the composer and the carousel discs draw the theme's hairline ring
 * (--pat-toolbar-button-box-shadow: usugami's 12% black, ink's solid black).
 * Side mode only: One and Pro's dock is an edge-to-edge bar with a top rule.
 * The candy-box popover above it is the same material, so it takes the same
 * edge. */
@media (width <= 768px) {
    body[data-global-nav="side"] #global-nav-side,
    body[data-global-nav="side"] #global-nav-side .gns-more-popover {
        box-shadow: var(--pat-toolbar-button-box-shadow,
                        var(--pat-button-box-shadow, 0 0 0 1px rgba(0, 0, 0, 0.12)));
    }
}

/* 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 (width <= 768px) and (display-mode: standalone) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) #global-nav-side {
        bottom: 23px;
        left: var(--kik-dock-inline, 23px);
        right: var(--kik-dock-inline, 23px);
    }
}

/* ================================================================== */
/* 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;
}

/* EXCEPT UNDER A MAXIMISED WINDOW, where it is not a stray artefact but the
 * edge it always was (Daniel, 2026-09-20). A maximised app meets the header
 * with its own content — there is no desk left between the two — so the
 * gradient reads as the page sliding under the chrome, the thing it was
 * drawn for. A FLOATING window leaves the desk showing, and a shadow across
 * the desk belongs to nothing, which is why the kill above stands for it.
 *
 * Maximised is stated the way the rest of windowed mode states it: no
 * `.kik-floating` (windowed-mode.css). It is asked of the pane IN FRONT
 * (`.kik-active-pane`, libraries/kikaron/active-pane) and not of any open
 * pane — the desk is always open and never floats, so "some pane is not
 * floating" is true even while the app you are looking at floats over it.
 * `#app-space.state-on` for the same reason from the other side: on the desk
 * itself there is no app meeting the header, and the edge stays off.
 *
 * Scoped to `html.kik-windowed` so the phone and any face with no windows
 * keep the compact header's own edge (#main::after, below). */
html.kik-one.kik-windowed body[data-global-nav="compact"]
    #app-space.state-on:not(.injecting):has(
        > .application-body.state-on.kik-active-pane:not(.kik-floating))::before {
    opacity: 1 !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;
    /* Shown only under an app that fills the screen (the rule below); on the
       desk — the dashboard in front, or a window floating over it — the
       header meets the wallpaper and the line is not drawn (Daniel,
       2026-09-22). */
    opacity: 0;
}

/* AN APP THAT FILLS THE SCREEN GETS THE LINE, IN THE SIDEBAR'S OWN INK. The
 * app space starts --separation-line-thickness (1px) below the header, and
 * this line is what fills that pixel. Left transparent — or left as the
 * translucent --kik-cloud-shadow-colour — the desk showed through it as a
 * coloured seam between the header and the app (Daniel, 2026-09-22). So under
 * an app that fills the screen it is drawn in --default-border-colour, the
 * opaque light grey the sidebar's own keyline uses, and the seam is a border
 * like any other. On the desk — the dashboard in front — there is no line at
 * all (opacity 0 above): the header meets the wallpaper.
 *
 * WHO IS IN FRONT is asked two ways, because `.kik-floating` only means
 * anything in windowed mode: with windows, a maximised window is the pane
 * that does NOT float (a floating one leaves the desk showing, and the line
 * would belong to nothing); without them — a narrow One, the fullscreen-only
 * preference off, the Android app — panes keep whatever floating class a
 * previous windowed session left on them, so the test there is simply "an app
 * other than the dashboard is in front". */
body[data-global-nav="compact"]:has(#app-space.state-on > .application-body.state-on.kik-active-pane:not(.kik-floating):not(#application-body-dashboard)) #main::after,
html:not(.kik-windowed) body[data-global-nav="compact"]:has(#app-space.state-on > .application-body.state-on.kik-active-pane:not(#application-body-dashboard)) #main::after {
    opacity: 1;
    background: var(--default-border-colour, #e4e4e7);
}

/* 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 {
    /* The opaque-card token, not literal white: this body-scoped
       declaration beats any theme's :root, so a literal here kept Compact
       mode's sidebars (and, below, the events body) white in dark mode.
       Every light theme resolves the token to the same white. */
    --sidebar-background-colour: var(--colour-base-light, 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: opaque card ground for the application body in Compact mode
   (white on light themes, the dark card tone on a dark one). */
body[data-global-nav="compact"] .application-body.application-events {
    --application-body-background-colour: var(--colour-base-light, 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 (width > 768px) {
 *     .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, var(--gns-muted, #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, var(--gns-muted, #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 hit-layer: a click anywhere on the tab activates the app. It sits
 * BEHIND the icon and × (z-index 1 vs their 2), so an inactive icon
 * (pointer-events:none) lets the click fall through to it while the active
 * icon and the × keep their own actions.
 *
 * A REAL ELEMENT, and it has to be. This was the name button's ::before, on
 * the reasoning written here for months — "overflow:hidden on the button
 * doesn't clip it, the containing block is the (position:relative)
 * kik-app-label". It does clip it. Sampled across a tab, eleven points edge to
 * edge, in Gecko and Blink alike: every one hit <kik-app-label> itself, which
 * has no handler, bar the few pixels of the button's own box. The pseudo
 * computes absolute, tab-wide, pointer-events auto — and takes no clicks at
 * all. A pinned tab (a bare glyph, its name button font-size:0) therefore had
 * a target a few pixels wide and read as an icon that does nothing, while a
 * tab with a long name worked (2026-09-01). */
kik-app-label .kal-hit {
    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, var(--gns-muted, #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 (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 (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 + var(--kik-safe-bottom));
        /* 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
            + var(--kik-safe-bottom));
    }

    /* 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) :is(#global-nav-side, .kik-dock-veil) {
        transform: translateY(var(--kik-dock-travel));
    }
    /* …and so does ANY text field with focus, which on a phone is the keyboard
       being up (Daniel, 2026-09-28: "the dock should not move up with the
       keyboard"). With interactive-widget=resizes-content the keyboard
       shrinks the layout viewport, #main with it, and the dock — pinned to
       #main's bottom — rode up to sit on the keys. It steps down behind them
       instead, the veil with it. Controls that open no keyboard (checkboxes,
       buttons, a select's picker) are left out.
       The field list is in :where(), so this stays one class lighter than the
       carousel switcher's composer pin (phone-app-carousel.css), which has to
       keep winning there. A field left focused after the keyboard is
       dismissed (Android's back gesture) keeps the dock down until it blurs. */
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(:where(textarea, [contenteditable]:not([contenteditable="false"]), input:not([type="checkbox"], [type="radio"], [type="button"], [type="submit"], [type="reset"], [type="range"], [type="color"], [type="file"], [type="image"], [type="hidden"])):focus) :is(#global-nav-side, .kik-dock-veil) {
        transform: translateY(var(--kik-dock-travel));
    }
}

/* ------------------------------------------------------------------ */
/* 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.
 *
 * The plain inset is measured from the top of the SAFE AREA, not the screen's
 * edge (Daniel, 2026-09-24: "respect the safe area"): with the dock gone the
 * home-indicator / gesture strip is what the pill would otherwise sit in.
 *
 * 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 (width <= 768px) {
    body:has(.application-body.state-on.kik-active-pane.focus-document .document-body.kik-scrollable:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) :is(.application-body, .kiki-dock) .kik-chat-composer,
    body:has(.application-body.state-on.kik-active-pane.focus-sidebar .sidebar-content.kik-scrollable:is(.scroll-position-bottom, .scroll-down:not(.scroll-position-top))) :is(.application-body, .kiki-dock) .kik-chat-composer {
        --kik-composer-inset: var(--kik-composer-rest, calc(15px + var(--kik-safe-bottom)));
    }
}

/* ------------------------------------------------------------------ */
/* 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 (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 + var(--kik-safe-bottom));
    }

    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))
                     + var(--kik-safe-bottom));
        padding-bottom: var(--kik-safe-bottom);
    }
}

/* The window carries its own close on the One/Pro faces, so the side nav —
   square icon tiles, like the cloud rail — keeps none of its own. (Moved here
   from windowed-mode.css on 2026-09-04; html.kik-one is rendered on those
   faces only.) */
html.kik-one .gns-close {
    display: none !important;
}

/* Single-app clients (data-kik-single-app from body_mode_attrs.html, or
   html.kik-single-app from partials/kik_single_app_mark.html for a page the
   service worker fetched without the app's User-Agent): the
   Kikaron Messages Android app IS one app on the phone, so the shell's own
   chrome goes — the global header, the dock (the bottom bar), and the cloud
   rail (Daniel, 2026-09-22). The general Kikaron app never carries the
   attribute and keeps all of it.

   Hidden, not left out of the markup: the dock and carousel scripts look
   these elements up, and compact mode already hides the phone header this
   same way. Last in the file and one type selector heavier than the
   body[data-global-nav] rules, so it wins at every width.

   The variables are what everything else reserves room with: the header
   band, the bar's footprint under document bodies and sidebars, the rail's
   width that wide screens (a Fold's inner display) offset content by, and the
   clearance the chat composer rests on. With no dock it rests on the SAFE
   AREA's edge: the Messages app draws under Android's swipe-up gesture strip
   on purpose (v0.1.4-alpha, 2026-09-22), but the composer is not to sit in it
   (Daniel, 2026-09-24: "respect the safe area"). */
html body[data-kik-single-app],
html.kik-single-app body {
    --global-header-height: 0px;
    --gns-mobile-height: 0px;
    /* …and the height the workspace tabs bar would take, which is what the
       calendar pane (and the portal) reserve their bottom room with: with no
       dock there is nothing to clear, and the Agenda ended on a band of empty
       white under its grid (Daniel, 2026-09-22). */
    --workspace-tabs-bar-height: 0px;
    --gns-width: 0px;
    --gns-width-collapsed: 0px;
    /* On the safe area's edge (Daniel, 2026-09-28, the iPhone's Messages:
       15px above it "sits too high up" — the Hub's call column settled on the
       same edge that day); the small-screen padding off the foot where there
       is no safe area. The scrolled-away rest above says the same. */
    --kik-composer-rest: max(var(--small-screen-padding, 15px), var(--kik-safe-bottom));
    --kik-dock-clearance: var(--kik-composer-rest);
    /* No header, so no line under it: the app space starts at the very top
       instead of a pixel down, where the desk showed through. */
    --separation-line-thickness: 0px;
}
html body[data-kik-single-app] #main::after,
html.kik-single-app body #main::after {
    display: none;
}
/* No window controls either (Daniel, 2026-09-22): the close would put the one
   app away with no launcher left to open it again, and the maximise and the
   resize edges belong to windowed mode, which a single-app client never
   enters (windowed-mode.js). The sidebar toggle stays — it is the app's own.
   The bar's trailing room for them goes too; !important because the slot is
   set at a weight (html.kik-one.kik-windowed #app-space …) no selector here
   should have to out-count. */
html body[data-kik-single-app] .kik-win-close,
html body[data-kik-single-app] .kik-win-max,
html body[data-kik-single-app] .kik-win-handle,
html.kik-single-app body .kik-win-close,
html.kik-single-app body .kik-win-max,
html.kik-single-app body .kik-win-handle {
    display: none !important;
}
html body[data-kik-single-app] #app-space .application-body,
html.kik-single-app body #app-space .application-body {
    --kik-win-trailing-slot: 0px !important;
}
html body[data-kik-single-app] #global-header,
html body[data-kik-single-app] :is(#global-nav-side, .kik-dock-veil),
html body[data-kik-single-app] #cloud-nav-rail,
html.kik-single-app body #global-header,
html.kik-single-app body :is(#global-nav-side, .kik-dock-veil),
html.kik-single-app body #cloud-nav-rail {
    display: none !important;
}

/* NO MEETINGS APP ON THE HUB'S PHONE FACE, FOR NOW (Daniel, 2026-09-28).
   A team space's call is reached from its Video call tab; the Meetings app
   itself stays off the phone's dock and More menu. Hidden rather than left
   out of the markup: the dock and the popover mirror each other by position
   (the nth-child rules above), and a missing entry would shift the rest. The
   side face only (the Hub); One and Pro keep theirs. */
@media (width <= 768px) {
    body[data-global-nav="side"] .gns-more-popover .gns-more-app[data-gns-app="meetings"],
    body[data-global-nav="side"] #global-nav-side .gns-apps .gns-item[data-gns-app="meetings"] {
        display: none !important;
    }
}

/* A landing's app-name title is the phone's alone (see the ≤768px block). */
.pat-toolbar .toolbar-title.kik-phone-app-title {
    display: none;
}

/* ── THE PAGE GOES SOFT UNDER THE DOCK (Daniel, 2026-09-28) ──────────────
 *
 * What scrolls behind and below the floating dock grows blurrier towards the
 * bottom of the screen, so the content fades into the edge instead of being
 * cut off sharp around the pill.
 *
 * A backdrop blur behind a gradient mask is a crossfade between the sharp
 * page and a blurred copy, not a blur that deepens, so it is built from TWO
 * layers whose masks start at different heights: a light blur over the whole
 * strip, a heavier one only in its lower part — together a ramp.
 *
 * The layers are the pseudo-elements of .kik-dock-veil, an empty element
 * beside the dock in #main (base.html). Beside, not inside: the dock's own
 * backdrop-filter makes it a backdrop root, and a layer inside it could only
 * blur the dock. In #main, not on the body: #main is fixed, so it is the
 * stacking context the dock's z-index counts in — on the body the layers
 * stacked OVER the dock and blurred its icons. The veil itself carries no
 * filter, so it is no backdrop root for its two layers.
 *
 * As tall as the dock stands off the bottom plus the dock, plus a margin
 * above it for the ramp to begin in. Under the dock (999), over the page; it
 * takes no touches. Where the dock is dropped for an office editor, the soft
 * edge goes with it. Off the phone layout, there is no veil.
 *
 * LOCKED TO THE DOCK (Daniel, 2026-09-28). The veil is not inside the dock,
 * so it cannot inherit where the dock is: every rule that moves or drops the
 * dock names the veil beside it (`:is(#global-nav-side, .kik-dock-veil)` —
 * the scroll auto-hide, the composer step-aside, the carousel's pins, Kiki's
 * dock, single-app clients), and both read their geometry from the three
 * variables below rather than each stating its own.
 *
 * --kik-dock-travel is the veil's height AND how far both slide when the dock
 * hides: moving one distance keeps them locked, and it being the veil's full
 * height takes the whole soft edge off screen with the dock (which then
 * clears the edge by the same 32px, shadow included). */
@media (width <= 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) {
        --kik-dock-bottom: max(10px, var(--kik-safe-bottom, 0px));
        --kik-dock-inline: 10px;
        --kik-dock-height: var(--workspace-tabs-bar-height, var(--gns-mobile-height, 64px));
        --kik-dock-travel: calc(var(--kik-dock-bottom) + var(--kik-dock-height) + 32px);
    }
}
/* The installed-PWA inset of the floating pill (the standalone block above). */
@media (width <= 768px) and (display-mode: standalone) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) {
        --kik-dock-bottom: 23px;
        --kik-dock-inline: 23px;
    }
}
/* One/Pro: the edge-to-edge bar ON the bottom, the safe area inside it. */
@media (width <= 768px) {
    body[data-global-nav="compact"] {
        --kik-dock-bottom: 0px;
        --kik-dock-height: calc(var(--workspace-tabs-bar-height, var(--gns-mobile-height, 64px))
            + var(--kik-safe-bottom, 0px));
    }
}
.kik-dock-veil {
    display: none;
}
@media (width <= 768px) {
    body:is([data-global-nav="side"], [data-global-nav="compact"]) .kik-dock-veil {
        display: block;
        position: fixed;
        inset-inline: 0;
        bottom: 0;
        height: var(--kik-dock-travel);
        z-index: 998;
        pointer-events: none;
        /* The dock's own clock, so the two slide as one. */
        transition: transform calc(var(--base-animation-duration, 0.25s) * 2) ease;

        &::before,
        &::after {
            content: "";
            position: absolute;
            inset: 0;
        }
        &::before {
            -webkit-backdrop-filter: blur(2px);
            backdrop-filter: blur(2px);
            -webkit-mask-image: linear-gradient(to bottom, transparent, #000 45%);
            mask-image: linear-gradient(to bottom, transparent, #000 45%);
        }
        &::after {
            -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
            -webkit-mask-image: linear-gradient(to bottom, transparent 40%, #000);
            mask-image: linear-gradient(to bottom, transparent 40%, #000);
        }
    }
    body:is([data-global-nav="side"], [data-global-nav="compact"]):has(:is(.collabora-editor-shell, .onlyoffice-editor-shell)) .kik-dock-veil {
        display: none;
    }
}

/* A hub whose platform owner switched the rail off (Configuration → Hubs →
   edit → Settings; data-kik-rail from body_mode_attrs.html). Above the phone
   width the side rail goes and so does the room everything reserves for it -
   the same variables a single-app client zeroes above. Nothing replaces it
   (Daniel, 2026-10-01: "hide it, nothing else"); the phone's dock is not the
   rail and stays. Pro's cloud rail is not rendered at all (base.html).

   The horizontal bar goes with it (Daniel, 2026-10-01: "no navigation bar at
   all, also not the horizontal one"): on Pro it is the rail's presence that
   hides #global-header (cloud-nav-rail.css, :has(#cloud-nav-rail)), so leaving
   the rail out brought the top bar back. Header, its height and the line
   under it go, as for a single-app client. */
@media (width > 768px) {
    html body[data-kik-rail="off"] {
        --gns-width: 0px;
        --gns-width-collapsed: 0px;
        --global-header-height: 0px;
        --separation-line-thickness: 0px;
    }
    html body[data-kik-rail="off"] :is(#global-nav-side, #global-header),
    html body[data-kik-rail="off"] #main::after {
        display: none !important;
    }
}
