/* WINDOWED MODE — fullscreen turns open apps into windows. The rules that need
 * html.kik-windowed, which only exists above the 681px phone breakpoint.
 *
 * While the instance is in browser fullscreen (the global-header toggle),
 * windowed-mode.js stamps `kik-windowed` on <html> and every OPEN app body
 * shows at once — not just the active one. A body without stored geometry
 * (or re-maximised) fills #app-space exactly as before; one the user has
 * dragged or resized carries `.kik-floating` plus inline geometry vars
 * (--kik-win-x/y/w/h, percentages of #app-space — that's what makes a saved
 * layout reproduce proportionally on a different screen).
 *
 * The app body is a size container named `main` (as well as the `screen`
 * name every .application-body carries — libraries/quaive/application-body),
 * so every `@container main (...)` rule inside an app answers to the WINDOW's
 * width — floating or maximised — and the window's own chrome asks its two
 * boundaries (680, 768) of that container rather than of the --screen-* flags,
 * which the body cannot derive for itself.
 *
 * Its sibling, libraries/kikaron/app-window/app-window.css, is the One/Pro
 * chrome at EVERY width — the sidebar column, the slots, the toggles' display
 * and travel — and is the file that knows both modes: a rule for One outside
 * windowed mode says `:not(.kik-windowed)` there. Nothing here carries that
 * negation, and nothing here can reach a phone.
 *
 * Linked with media="(min-width: 681px)", the same breakpoint base.html and
 * windowed-mode.js use to decide kik-windowed, so the three can never
 * disagree. Note what that buys and what it does not: a non-matching
 * stylesheet is still DOWNLOADED, just at low priority and without blocking
 * the first paint. The win is critical-path time, not bytes.
 */

/* Floating windows keep this much clear of every #app-space edge — read by
   windowed-mode.js and applied in the drag/resize clamp, so a theme can
   widen it without touching the script. */
#app-space {
  --kik-window-edge-margin: 10px;
}

/* THE GROUND UNDER A LIFTED WINDOW. The lift itself — --kik-window-shadow and
   --kik-window-move-duration — is stated in static/base/base.css; see the note
   in the block below for why it cannot live here. */
:root {
  /* --kik-window-shadow and --kik-window-move-duration are stated in
     static/base/base.css, not here. This stylesheet ships ONLY to the One /
     Pro faces (kikaron_stylesheets.html gates it on the face), and the
     shadow is read from outside windowed mode — the usugami theme maps
     --modal-panel-panel-box-shadow onto it — so a token living here left every
     modal on the HUB with an unresolvable var(), i.e. no shadow at all
     (reported 2026-08-21). The tokens are shared depth language; only the
     window geometry below is face-gated. */

  /* …and the ground goes a touch deeper while they are out (Daniel,
     2026-08-17) — the other half of the same lift: things standing off a
     surface read as lifted only if the surface recedes. Mixed off
     --kik-sw-stage rather than stated as a second literal, so a retune of the
     stage carries here — including the ACCOUNT's own pick, which lands on that
     token and nowhere else (partials/stage_colour_style.html) — and NOT by
     overriding that token — the bento card's
     own fill is derived from it (dashboard/styling/all.css), and moving the
     token would repaint the cards along with their ground. */
  --kik-desk-peek-stage: color-mix(in srgb, var(--kik-sw-stage), black 50%);
}

html.kik-one.kik-windowed #app-space:is(.state-on, .injecting) {
  /* The stage clips its windows. A window may be dragged partly off it
     (the JS clamp keeps a tenth of the stage covered so it can be grabbed
     back), and without this the overhang would extend the document's own
     scroll area — a page-level scrollbar, and a stage you could scroll away
     from. Safe for the chrome that shares the stage: the modal panel space
     sits outside #app-space entirely and is fixed-positioned besides. */
  overflow: hidden;

  /* Every open app is a window. Display beats the generated per-app rule
     (apps.css shows only the active body); state-paused's display:none
     !important still wins, which is right — the portal face has no windows.

     Held back while the stage is settling — see the rule below the block. */
  .application-body.state-on {
    display: block;
    position: absolute;
    /* Length-based on BOTH sides of the maximise/restore flip (inset: 0
       left width at auto and killed the animation): all four geometry
       properties are lengths in either state, so the flip glides. */
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    /* MOVEMENT IS A TRANSFORM, GEOMETRY IS NOT (Daniel, 2026-08-20).
       left/top/width/height stay the window's resting box — what gets stored,
       what maximise animates. But anything that only *moves* a window and
       leaves its size alone — a drag, the desk peek — offsets it here
       instead. Those are layout properties: animating them relaid out and
       repainted the whole app subtree every frame, and the window's own
       container-type made each width change re-evaluate its container queries
       too. WebKit shows it plainly (Safari stutters exactly as the Mac app
       does; Blink absorbs it), and a translate is work the compositor already
       does for free.
       Safe on this element specifically: container-type already makes it a
       containing block, so `position: fixed` inside a window was contained
       before this and is contained now. */
    transform: translate3d(var(--kik-win-tx, 0px), var(--kik-win-ty, 0px), 0);
    transition:
      left var(--kik-window-move-duration) ease,
      top var(--kik-window-move-duration) ease,
      width var(--kik-window-move-duration) ease,
      height var(--kik-window-move-duration) ease,
      transform var(--kik-window-move-duration) ease,
      border-radius var(--kik-window-move-duration) ease,
      box-shadow var(--kik-window-move-duration) ease;
  }

  .application-body.state-on.kik-floating {
    left: var(--kik-win-x, 6%);
    top: var(--kik-win-y, 6%);
    width: var(--kik-win-w, 64%);
    height: var(--kik-win-h, 70%);
    /* Relative like everything else about a window — the JS clamp
       enforces the same 15% floor on the stored numbers. */
    min-width: 15%;
    min-height: 15%;
    /* The house corner. A window is a floating surface like every other
       one on this face (wells, cards, the sidebar column), so it rounds by
       the theme's own --border-radii rather than a radius of its own —
       change the theme, the windows follow. The override var stays as the
       escape hatch for a theme that genuinely wants its windows rounder. */
    border-radius: var(--kik-window-border-radius, var(--border-radii));
    box-shadow: var(--kik-window-shadow);
    /* Clips the app to the rounded window. Sidebar and document-body are
       absolutely positioned children, so nothing legitimate overflows. */
    overflow: hidden;
  }

  /* THE DESK IS THE SURFACE, NOT A WINDOW. It stays at the bottom of the
     stack no matter what is activated — windowed-mode.js never hands it a
     z-index, and this floor keeps it under even a window that has not been
     raised yet (those carry no inline z-index either, so without these two
     the order would fall to DOM order and the desk sits last). Reaching the
     desk is therefore never a raise: the windows slide out of the way
     instead (html.kik-desk-peek). */
  .application-body.state-on {
    z-index: 1;
  }

  #application-body-dashboard.state-on {
    z-index: 0;
  }
}

/* --- the peek lifts the chrome and the cards too ------------------------
   While the windows are pushed out, the global header and every bento card
   take the WINDOW's shadow, over the window's own travel time — so the whole
   screen lifts as one gesture instead of the windows lifting away from a flat
   page. Both come off the shared tokens above; the card's ring goes through
   --kik-cloud-card-shadow (cloud-dashboard.css paints the card FROM that
   token, so this swaps the value rather than fighting the declaration, and a
   theme that restyles the ring keeps its say outside the peek).

   The transition is declared on the plain windowed-mode selector, not inside
   the peek: a property only animates when the element carries the transition
   in BOTH states, and a rule that arrives with .kik-desk-peek would make the
   shadow appear instantly on the way in and glide only on the way out. */
html.kik-one.kik-windowed {
  #global-header,
  .kik-cloud-card {
    transition: box-shadow var(--kik-window-move-duration) ease;
  }

  /* Same reason as the shadows: the transition has to be here rather than in
     the peek block, or the ground would darken on a cut and lighten on a
     glide. */
  #document-content-dashboard {
    transition: background-color var(--kik-window-move-duration) ease;
  }

  &.kik-desk-peek {
    #global-header {
      box-shadow: var(--kik-window-shadow);
    }

    /* The desk's own surface — dashboard/styling/all.css paints it
       --kik-sw-stage above the phone breakpoint, and this deepens exactly that
       one surface for the duration of the peek. Same 681px gate, because below
       it the desk is the phone home wearing the app wash and there is no stage
       to deepen. */
    @media (min-width: 681px) {
      #document-content-dashboard {
        background-color: var(--kik-desk-peek-stage);
      }
    }

    .kik-cloud-card {
      --kik-cloud-card-shadow: var(--kik-window-shadow);
    }
  }
}

/* THE STAGE SETTLES BEFORE IT SHOWS ITS WINDOWS.

   ``kik-win-settling`` is stamped on <html> before the first paint (base.html)
   and dropped at the end of windowed-mode.js's boot. Until then this puts the
   stage back the way it looks outside windowed mode: ONLY the app the stage
   names active paints, and every other open window waits.

   Why it matters is not cosmetic. Every unfilled app body carries a
   pat-lazy-load anchor with ``autoload-visible``, and that fetch carries
   ``_app_body`` — which makes the server RECORD the app as open
   (core.context_processors.record_full_page_visit). A body that is visible for
   a single frame during the boot shuffle therefore opens its app, for good:
   the "random new apps appear when I refresh" report (Daniel, 2026-08-20).
   Nothing can fire while the body is display:none.

   The ACTIVE app is exempt without naming it, by specificity: the generated
   per-app rule (automations/apps.css) is
   ``#app-space.active-app-<slug>.state-on #application-body-<slug>`` — TWO
   ids — and this selector has one, so it hides every open body except the one
   that rule is speaking about. Same arithmetic the show-all rule above relies
   on, in the other direction. */
html.kik-one.kik-windowed.kik-win-settling #app-space:is(.state-on, .injecting) .application-body.state-on {
  display: none;
}
html.kik-one.kik-windowed #app-space:is(.state-on, .injecting) .application-body.state-on.kik-floating {
  --kik-win-inset: var(--small-screen-padding);
}

/* THE CHAT CARD LINES UP WITH THE TOOLBAR'S LAST BUTTON. In a window the bar
   spans the full width and insets its buttons by --kik-win-inset (the rule
   below), so that inset IS the distance from the window's edge to the right
   edge of the close ×. Give the dock the same one and the card's right edge
   lands exactly there, with the bottom following it for a square gap.

   The desk's own gap (--modal-panel-gap, libraries/quaive/document-body) is
   the wrong measure inside a window: it is sized for the page, and left the
   card floating well clear of the controls it should be flush with. */
html.kik-one.kik-windowed #app-space .application-body.state-on .kiki-dock {
  --kik-kiki-dock-inset: var(--kik-win-inset, 10px);
  /* THE SEAM, IN A WINDOW: 73px (Daniel, 2026-08-20) rather than the desk's
     85px — a window's chrome above the card is shorter, so the card meets its
     bar higher up. Measured, not composed, for the same reason the desk value
     is (see libraries/quaive/document-body).

     AND SHE STAYS PUT WHILE YOU SCROLL. A window's bar does not leave on a
     scroll — it is pinned to the window's own top edge — so the card has no
     room to rise into, and the desk's rise rule (libraries/quaive/document-body)
     would have moved it up against a bar that is still there. Pinning the two
     tops together is what switches that rule off, without it having to know
     anything about windows. */
  --kik-kiki-dock-top-with-bar: 73px;
  --kik-kiki-dock-top-bar-away: var(--kik-kiki-dock-top-with-bar);
}

/* Toolbar controls are 28px in a FLOATING window, against the 35px page
   default. A floating window is a smaller frame than a page and its bar is
   chrome around the content rather than the content's own header, so the
   controls step down a notch with it. Set on the window body, so everything
   measured off the height follows in one go: the bar, its icon buttons, the
   search field, the backdrop plate and --kik-win-toolbar-height (the
   clearance the content keeps under the bar), which is calc()'d from it
   below.

   A MAXIMISED window is the whole screen again — a page, not a panel — so it
   keeps the house 35px. That is the inherited default, so the step-down is
   scoped to .kik-floating rather than declared twice: restoring a window
   hands its bar back to the same variables every non-windowed screen uses,
   with nothing to keep in sync.

   The LINE height goes with the button height. It is declared as a flat 35px
   at :root (quaive/settings/artefacts/variables.css), not off the button
   height, so leaving it behind would keep every glyph vertically centred for
   a bar 7px taller than the one it is in. */
html.kik-one.kik-windowed .application-body.state-on.kik-floating {
  --pat-toolbar-button-height: 28px;
  --pat-toolbar-button-line-height: 28px;
}

/* --- TWO TRAILING SLOTS ---------------------------------------------------
   The main bar's trailing end keeps room for the window's own controls
   (--kik-win-trailing-slot, read by every reservation in app-window.css):
   the close alone outside windowed mode, close + maximise inside it. With the
   #app-space id, because the default (0px) is declared with it in
   main-toolbar.css and a class-only selector here would lose to it. */
html.kik-one.kik-windowed #app-space .application-body.state-on {
  --kik-win-trailing-slot: calc(2 * var(--kik-win-slot));
}

/* --- THE BAR IS THE WINDOW'S DRAG HANDLE --------------------------------- */
html.kik-one.kik-windowed .application-body.state-on {
  /* The title is a label, not a control — let its clicks fall through to
     the toolbar surface, which is a drag handle. Not when the title IS a
     field (pat_toolbar's ``toolbar_title_input`` — the task panel's bar
     edits the task's name there): a field that cannot be clicked cannot be
     typed into. */
  .pat-toolbar .toolbar-title:not(input) {
    pointer-events: none;
  }

  /* …except where the title IS a control. The chat header's name is the open
     conversation's, and tapping it opens the same user card / member list its
     avatar does (native-chat.js applyHeaderDmTap marks both nodes) — the name
     is the thing you read, so it must open what the picture beside it opens.
     Falling through to the drag handle swallowed every click on it while the
     avatar next door worked. The rest of that bar still drags the window. */
  .pat-toolbar #kik-chat-header-title {
    pointer-events: auto;
  }

  /* A WINDOWED bar never slides out — auto-hide included. Pins the transform
     lever the way the kiki-dock does: a window on a desk keeps its bar.

     …UNLESS THE WINDOW IS SMALL, which is the whole of the exception. A bar
     pinned across a narrow window is a band the content cannot have back, so
     a phone-sized window lets a `sticky auto-hide` bar slide away exactly as
     a phone does (the stock component's own rule). Gated on the width rather
     than out-specified: an arms race between two `!important` rules is not
     a design. */
  @container main (width > 680px) {
    & .document-body .pat-toolbar.sticky {
      --pat-toolbar-auto-hide-offset: 0 !important;
    }
    & .document-body .pat-toolbar.sticky.auto-hide {
      transform: none !important;
    }
  }
}

/* …and the SIDEBAR's OWN bar takes the trailing reservation once the column
   has the window to itself. Below the app's small threshold the sidebar fills
   the window (quaive/sidebar), so it — not the document body — is what the
   maximise and close toggles overlay, and the trailing controls of its bar end
   up underneath them: in Messages, at 430px wide, the conversation search sat
   squarely under the close button and the "+" under the maximise, with only
   the More menu still clear of them. Same slot as the main bar's rule in
   app-window.css, on the same section, for the same two reasons.

   Published as a token and not applied straight from here, because an app
   whose sidebar bar states its OWN trailing inset — Messages does, so its last
   control clears the column's edge — overrides a padding declared here (an id
   selector beats any number of classes) and has to add the slot to its own.
   The token is absent everywhere else, so the `0px` fallback such an app
   composes with costs nothing off this face.

   Declared on the sidebar rather than on the app body: the app body IS the
   `screen` container, and a container query cannot match the element that
   establishes it. (The LEADING counterpart — the side slot the sidebar's own
   bar keeps clear for the handle — is libraries/kikaron/sidebar-handle's, for
   every face.) */
@container main (width <= 680px) {
  html.kik-one.kik-windowed .application-body.state-on aside.sidebar {
    --kik-win-controls-slot: var(--kik-win-trailing-slot);
  }
  html.kik-one.kik-windowed .application-body.state-on aside.sidebar .pat-toolbar:not(.well-title-group *) .toolbar-section.quick-functions {
    padding-inline-end: var(--kik-win-controls-slot);
  }
}

/* THE DRAWER STOPS CLEAR OF THE WINDOW'S CONTROLS (Daniel, 2026-09-04) — in
   windowed mode that is two of them, the maximise and the close, so the
   reserve app-window.css declares for the close alone is restated here for
   both. The width rule that reads it is app-window.css's, one for both
   modes. */
@container main (width <= 768px) {
@container screen style(--kik-sidebar-drawer: true) {
  html.kik-one.kik-windowed .application-body.state-on aside.sidebar.left {
    --kik-drawer-reserve: calc(2 * var(--kik-win-inset) + 2 * var(--pat-toolbar-button-height) + var(--pat-toolbar-element-separation, 0.5rem));
  }
}
}

/* A WINDOWED bar's open search field stops clear of the window toggles —
   the controls stay visible and clickable at every size (Daniel,
   2026-08-19; supersedes the earlier cover-the-toggles cut, whose z-drop
   also hid them at sizes where the stock field never reached them). Same
   double slot the quick-functions reservation keeps, so the open input
   ends on the line the closed controls already stop at. Scoped by the
   same container condition as the overlay itself — outside it the stock
   field takes over and brings its own reservation. */
@container pat-toolbar ((max-width: 520px) or style(--screen-small: true)) {
  html.kik-one.kik-windowed .application-body.state-on .toolbar-section .pat-toolbar-search-field:is(.focus, .has-value, .fixed) input[type="search"] {
    inset-inline-end: calc(var(--pat-toolbar-padding) + var(--kik-win-trailing-slot));
  }

  html.kik-one.kik-windowed .application-body.state-on .toolbar-section .pat-toolbar-search-field:is(.focus, .has-value, .fixed) .kik-search-clear {
    inset-inline-end: calc(var(--pat-toolbar-padding) + var(--kik-win-trailing-slot) + 5px);
  }

  /* The overlay's leading reservation (toolbar.css guards it on the app
     body DECLARING a sidebar — :has() cannot ask whether the back control
     is actually painted) is right for every windowed band by accident or
     design: narrow windows show the in-bar button.icon.back, wider ones
     park the fixed kik-win-side toggle on the same spot. The ONE case with
     nothing at the bar's start is a >=769 window with its sidebar open —
     there the bar itself is inset past the sidebar (the db-left rules
     above), the side toggle stays behind over the sidebar, and the kept
     slot just exposed a sliver of the covered filter select. Same gate and
     body condition as the db-left rule, so the two move together. */
  @container main (width > 768px) {
    html.kik-one.kik-windowed .application-body.state-on.sidebar-left-open:not(.sidebar-left-closed):has(aside.sidebar) .pat-toolbar:not(aside.sidebar *) .toolbar-section .pat-toolbar-search-field:is(.focus, .has-value, .fixed) input[type="search"] {
      inset-inline-start: var(--pat-toolbar-padding);
    }

    html.kik-one.kik-windowed .application-body.state-on.sidebar-left-open:not(.sidebar-left-closed):has(aside.sidebar) .pat-toolbar:not(aside.sidebar *) .toolbar-section .pat-toolbar-search-field:is(.focus, .has-value, .fixed):after {
      inset-inline-start: var(--pat-toolbar-padding);
    }
  }

  /* (The SIDEBAR's own bar keeps its overlay clear of the handle by the
     side slot — libraries/kikaron/sidebar-handle, every face.) */
}

/* --- A COLLAPSE-SCREEN MODAL IN A WINDOW ------------------------------
   A `collapse-screen` modal stops being a panel once its container is
   narrow and becomes the screen instead: full bleed, square corners, an
   ordinary toolbar (libraries/patterns/modal/modal.css). Inside a window
   that leaves the window with no chrome at all — the frame's controls sit
   at z-index 12 and the panel at 9998, so the modal covers them, and the
   only way out of the window becomes the modal's own back arrow.

   So the frame keeps exactly ONE control over the modal: the close. Not
   the maximise toggle and not the sidebar toggle — a modal filling the
   window has no sidebar to reveal, and a maximise button under a screen is
   a control nobody reaches for. The close stays because closing the window
   is a thing to be able to do at any moment, and so do the RESIZE HANDLES:
   the window keeps its edges whatever is open inside it.

   The bar then has to leave room for it: `--modal-toolbar-trailing-reserve`
   is the slot the modal bar's quick-functions section keeps at its end, and
   it is stated here because only the window layer knows a control is
   overlaying that corner. The close moved to the LEADING edge in this mode,
   so nothing of the modal's own competes for the space.

   Windowed mode only: outside it the frame's close and maximise are hidden
   anyway (app-window.css), so there would be nothing to make room for.

   The container query resolves against `.application-body` — the app's own
   `main` — for every rule below, because each SUBJECT is a CHILD of it. A
   modal is collapsed when the WINDOW is narrow, which is the same question
   the modal itself asked.

   The subject, not the :has() guard, is what the query is asked about — and
   an element can never match a query on the container it establishes
   itself. Written with the app body as the subject and the children nested
   inside, the query resolved against the OUTER #main, the stage, and in a
   floating window on a wide stage — the only place this block exists for —
   it never fired: the maximise stayed painted under the panel and the bar
   kept no room for the close (Daniel, 2026-09-07). The reserve is therefore
   declared on the modal space, a child, from where it inherits down to the
   panel and its toolbar just the same. */
@container main (width <= 680px) {
  html.kik-one.kik-windowed .application-body.state-on:has(> .modal-space > dialog.pat-modal-panel.collapse-screen[open]) > .kik-win-max,
  html.kik-one.kik-windowed .application-body.state-on:has(> .modal-space > dialog.pat-modal-panel.collapse-screen[open]) > .kik-win-side {
    display: none !important;
  }

  /* Over the panel (9998) rather than under it. */
  html.kik-one.kik-windowed .application-body.state-on:has(> .modal-space > dialog.pat-modal-panel.collapse-screen[open]) > .kik-win-close {
    z-index: 10000;
  }

  /* The one control left over the corner — the close, an inset in from the
     edge — as the reserve the panel's quick-functions section keeps
     (libraries/patterns/modal, --modal-close-trailing-reserve).

     --modal-close-slot AND NOT --kik-win-slot, though both name the same
     idea — a control's width plus the gap before it. The difference is where
     each is DECLARED, and a custom property substitutes where it is declared:
     --kik-win-slot resolves against the document's 35px button, while a
     windowed toolbar's buttons are 28px (this file, further up). Reserving
     the wider one left the difference standing as air between the bar's last
     control and the × — 45px kept for a 38px control (Daniel, 2026-09-07:
     "the gap ... is just too big", twice).

     --modal-close-slot is declared on the panel for exactly this reason, and
     libraries/patterns/modal says so where it declares it: at :root it "came
     out several pixels wider than the close actually is, and the bar carried
     the difference as a gap between its last control and the ×". The same
     fault, one level out. Reading the panel's own slot means the reserve is
     whatever that panel draws its controls at, at any size, with nothing to
     keep in step.

     No arithmetic against the bar's padding either: a slot is the footprint
     plus its separation, which is the whole distance the last control has to
     stand off.

     ON THE PANEL, for the same reason: a property substitutes where it is
     READ, and --modal-close-slot is declared on the panel. Set on the modal
     space — the child this block otherwise speaks through, because the app
     body cannot match a query on the container it establishes — it would
     resolve against a box that never declared the slot, come out invalid,
     and fall to the 0px default: no reserve at all. The panel is inside the
     queried container just the same, so naming it costs nothing. */
  html.kik-one.kik-windowed .application-body.state-on > .modal-space > dialog.pat-modal-panel.collapse-screen[open] {
    --modal-toolbar-trailing-reserve: var(--modal-close-slot);
  }
}

/* --- A MAXIMISED WINDOW'S MODAL CLEARS THE SCREEN, NOT THE WINDOW -------
   A panel is centred in its app's own modal space, which is the window, and
   a maximised window is #app-space: everything under the global header. So
   the panel stood one gap under the header and one gap above the foot of the
   screen — 20px and 20px inside its box, but seen from the screen's edges
   header-plus-gap at the top against a bare gap at the bottom, and a modal
   that sits high (Daniel, 2026-09-04: "as much clear the bottom of the
   viewport as they sit clear the top of the viewport").

   Maximised only. A floating window is a box the user placed, and its panel
   belongs to that box, wherever on the stage it stands; the header is only
   part of a modal's surroundings once the window IS the stage.

   NOT GATED ON kik-windowed (Daniel, 2026-09-11: "still sits too low", with
   the Mail panel 21px under the header and 20px off the floor — the
   library's own centring, in the pane). Windowed mode is fullscreen-only by
   default, so outside fullscreen the face runs without the class, and an app
   body that is not windowed fills the stage exactly as a maximised window
   does: same box, same header above it, same panel centred in it. The gate
   is the geometry — an app body that is not floating IS the stage.

   AND THE FLOATING EXCLUSION COUNTS ONLY WHILE WINDOWED MODE IS ON (Daniel,
   2026-09-11, second round: still low, and the console showed the Mail body
   wearing `kik-floating` under an <html> with no `kik-windowed`). The class
   is stamped by apply() while the mode is on and never taken off when the
   mode goes — leaving fullscreen drops kik-windowed and leaves every window's
   classes as they were — and every other floating rule is gated on
   html.kik-windowed, so the stale class costs nothing until a selector reads
   it bare. Two selectors, then: outside windowed mode every app body is the
   stage, whatever it wears; inside it, the ones that are not floating. This
   file loads on One and Pro alone, which is where the header stands over a
   stage.

   The header's height (and the line under it, which the stage's own top
   offset counts too) is added to the room kept BELOW the panel: its cap
   loses that much, and its centre moves up by half of it — the middle of a
   band whose foot has risen by H is H/2 higher. Both terms, for the same
   reason the phone's dock reserve carries both (patterns/modal): the cap
   without the shift leaves a short panel where it was, the shift without the
   cap pushes a tall one off the top. --global-header-height is 0px wherever
   the header is not painted (the side rail, the phone's bottom bar), so both
   terms collapse there on their own.

   AND THE GUTTER IS 15px FROM THE HEADER, not the library's 20px from the
   stage (Daniel, 2026-09-11: "optically in the vertical centre of the
   viewport, 15px clear of the global header"). The cap above is built from
   --modal-panel-gutter-block, so it is re-declared here rather than the cap
   rewritten: the gutter is what a tall panel actually shows, once at the top
   under the header and once at the foot as its share of the symmetric band.
   Measured from the header's bottom EDGE — the line under it is the header's
   edge to the eye — so the separation line is taken out of the figure: the
   stage begins below that line, and a 15px gutter counted from the stage
   would stand 16px under the header. Where there is no line the term is 0px
   and the gutter is the whole 15px.

   The contents' own cap says the same thing in the space's units: it is what
   actually bounds a panel whose contents ask for more (Mail's square states a
   height), and one that kept the old figure would overrun the dialog it sits
   in. Not below the phone band: there a `collapse-screen` panel IS the window
   and has no clearance to speak of, and any other panel's centre and cap are
   the dock reserve's business (patterns/modal). The gate is the band, not
   `:not(.collapse-screen)` — Mail's panel wears that class at every width
   and is an ordinary gutter-and-all panel until the band says otherwise.
   Child combinators throughout, at the weight the shared rules need
   beating. */
@container screen style(--screen-small: false) {
  html.kik-one:not(.kik-windowed) .application-body.state-on > .modal-space > .pat-modal-panel,
  html.kik-one.kik-windowed .application-body.state-on:not(.kik-floating) > .modal-space > .pat-modal-panel {
    /* What stands between the stage and the top of the screen: the header
       and the line under it — the same two terms #app-space's own `top` is
       built from (quaive/content). */
    --kik-win-modal-foot: calc(var(--global-header-height, 0px) + var(--separation-line-thickness, 0px));
    /* The room a panel keeps under the header, edge to edge. */
    --kik-win-modal-header-clearance: 15px;
    --modal-panel-gutter-block: calc(var(--kik-win-modal-header-clearance) - var(--separation-line-thickness, 0px));
    top: calc(50% - var(--kik-win-modal-foot) / 2);
    max-height: calc(100% - 2 * var(--modal-panel-gutter-block) - var(--kik-win-modal-foot));

    > .modal-panel-contents:has(> .modal-panel-body) {
      max-height: calc(100cqh - 2 * var(--modal-panel-gutter-block) - var(--kik-win-modal-foot));
    }
  }
}

/* --- Touch drag surfaces ------------------------------------------------
   A floating window moves under a finger, but only from furniture that
   scrolls nothing: its toolbars. touch-action must be declared BEFORE the
   gesture starts — the browser decides at pointerdown whether the touch is
   its (panning) or ours, and nothing in JS can take it back afterwards —
   so it sits in CSS, on the same surfaces windowed-mode.js accepts as a
   touch drag zone. Content and sidebar panes are deliberately left out:
   they are draggable with a mouse and scrollable with a finger, and on a
   touch screen scrolling has to win. */
html.kik-one.kik-windowed .application-body.state-on.kik-floating :is(
  .pat-toolbar,
  .toolbar-section,
  .kik-fc-toolbar
) {
  touch-action: none;
}

/* …except that a toolbar's own CONTROLS keep their normal handling: a
   select, a text field or a scrollable chunk inside the bar still wants the
   browser's gestures. Only the bar's empty surface drags the window. */
html.kik-one.kik-windowed .application-body.state-on.kik-floating :is(
  .pat-toolbar,
  .toolbar-section,
  .kik-fc-toolbar
) :is(input, textarea, select, [contenteditable], .pat-scroll-box) {
  touch-action: auto;
}

/* A WINDOW KEEPS ITS EDGES WHILE A MODAL IS OPEN IN IT. The app's own modal
   space sits at 9997 and its panel at 9998; the handles are strips at the
   window's edges at 60, so a modal — full-bleed or panel-shaped, it makes no
   difference to a 12px strip under a dim — put the window's size out of reach
   for as long as it was open. The frame outranks what is inside it: the handles
   go over the space while this window has a panel of its own.

   Scoped to a panel in THIS window's space, so a document-wide dialog (a
   global confirm, the login notice) still covers everything, edges included —
   that one is not the window's to resize around. */
html.kik-one.kik-windowed .application-body.state-on.kik-floating:has(> .modal-space > dialog.pat-modal-panel[open]) > .kik-win-handle {
  z-index: 10000;
}

/* The eight strips partials/app_body_frame.html renders into each app body
   (hidden at every width by app-window.css) become hit areas — they paint
   nothing — on a floating window. */
html.kik-one.kik-windowed .application-body.state-on.kik-floating > .kik-win-handle {
  display: block;
  /* A finger resizes too. Without this the browser claims the gesture for
     panning before the first pointermove and the window never moves —
     which is why dragging worked on the iPad's trackpad and not by
     touch. */
  touch-action: none;

  &[data-edge="n"] { top: 0; left: 20px; right: 20px; height: 12px; cursor: ns-resize; }
  &[data-edge="s"] { bottom: 0; left: 20px; right: 20px; height: 12px; cursor: ns-resize; }
  &[data-edge="w"] { left: 0; top: 20px; bottom: 20px; width: 12px; cursor: ew-resize; }
  &[data-edge="e"] { right: 0; top: 20px; bottom: 20px; width: 12px; cursor: ew-resize; }
  &[data-edge="nw"] { top: 0; left: 0; width: 20px; height: 20px; cursor: nwse-resize; }
  &[data-edge="se"] { bottom: 0; right: 0; width: 20px; height: 20px; cursor: nwse-resize; }
  &[data-edge="ne"] { top: 0; right: 0; width: 20px; height: 20px; cursor: nesw-resize; }
  &[data-edge="sw"] { bottom: 0; left: 0; width: 20px; height: 20px; cursor: nesw-resize; }
}

/* --- Covered windows are not rendered -----------------------------------
   Windowed mode keeps every open app's body live — that is what makes it
   multi-tasking rather than one-at-a-time — but with a MAXIMISED front
   window, the default, that window fills the stage and every other open app
   goes on being laid out, painted and dragged through style recalc behind
   it. The cost grew with the number of apps left open, which is what Daniel
   felt on the iPad: closing a few gave it its frames back. (Outside windowed
   mode this never arose — CSS shows only the active body there.)

   Covered = no window on the stage is floating (so the front one fills it)
   and this is not the front one. The first half is asked here, of the stage;
   the second is `.kik-front`, which windowed-mode.js keeps on the active
   app's window — the one thing a stylesheet cannot derive, since it cannot
   compare a window's id against the stage's active-app class. The desk peek
   floats every window, so nothing is covered while it is on; stated as well
   as implied, so the two never disagree.

   content-visibility rather than display:none, so scroll positions, lazy
   loads, editors and live subscriptions survive being put away and coming
   back is a re-render rather than a rebuild. The window's box is fully
   positioned by the rules above, so nothing collapses while it is skipped. */
html.kik-one.kik-windowed:not(.kik-desk-peek) #app-space:not(:has(> .application-body.state-on.kik-floating)) > .application-body.state-on:not(.kik-front) {
  content-visibility: hidden;
}
