/* File Vault — стили страницы приложения (index.html) поверх vault.css. Четыре части по порядку: экран хранилища,
   экран входа, окно просмотра со свойствами, экран администрирования; у каждой части своя шапка и свои слои.
   Правила — только в слоях layout и pages.
   Это исходный файл: его правят на месте. Ничто его не собирает и ни с чем побайтно не сверяет. Куда писать
   правило и что проверяет стили — docs/UI.md, раздел 13. */

/* ==========================================================================
   File Vault — storage screen (#app-shell): skeleton, sidebar, toolbar, selection bar.
   The first part of app.css.

   Phone layout (base): the document scrolls; sticky app bar, context row, the list as a paper sheet,
   fixed bottom navigation and the floating «+». The folder tree is a drawer.
   Desktop layout: a fixed-height shell; sidebar (brand, sections, tree, account), a top bar and a card
   that scrolls inside with a sticky column header.
   ========================================================================== */

@layer layout {
  /* ------------------------------------------------------------------------
     Shell
     div#app-shell.app-shell[.is-selecting] > main#explorer.explorer > aside#tree-pane.tree-pane + section.content-pane
     ------------------------------------------------------------------------ */
  .explorer {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
  }

  /* The storage screen has the bottom navigation: its floating button, the toast and the upload panel stand above it.
     (The toast and the panel are later siblings of #app-shell.) */
  .app-shell,
  .app-shell:not([hidden]) ~ .toast,
  .app-shell:not([hidden]) ~ .upload-panel {
    --bottom-bar-space: var(--bottomnav-height);
  }

  /* Phone layout: the sidebar has no box of its own — its navigation is the fixed bottom bar, its tree is
     the fixed drawer, the rest is hidden. It must not get a transform, a filter or overflow here. */
  .tree-pane > .brand,
  .user-block,
  .tree-resizer {
    display: none;
  }

  .content-pane,
  .section-view {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
  }

  .is-selecting .app-bar,
  .is-selecting .fab {
    display: none;
  }

  /* ------------------------------------------------------------------------
     Folder drawer (phone layout) / tree block of the sidebar (desktop)
     div.drawer[.is-open] > div.drawer-header (h2.drawer-title + button.drawer-close) + div.tree-scroll > ul.folder-tree
                            + div.usage#usage[hidden] > div.usage-row (div.usage-text + [div.bar[.is-error]]) ×N
     The used space is the last child of the drawer: under the tree in the phone drawer and in the sidebar.
     It takes two fifths of the window height at most and scrolls by itself beyond that — a person in several
     groups with quotas, in a low window, keeps the folder tree.
     Open it together with .sheet-backdrop.
     ------------------------------------------------------------------------ */
  .drawer {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: var(--z-popover);
    display: flex;
    flex-direction: column;
    width: min(320px, 86vw);
    padding: var(--safe-top) 0 var(--safe-bottom) var(--safe-left);
    background: var(--surface-overlay);
    border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
    box-shadow: var(--shadow-lg);
    visibility: hidden;
    transform: translateX(-100%);
  }

  .drawer.is-open {
    visibility: visible;
    transform: none;
  }

  .drawer-header {
    display: flex;
    align-items: center;
    flex: none;
    height: var(--appbar-height);
    padding: 0 6px 0 var(--gutter);
  }

  .drawer-title {
    flex: 1;
    min-width: 0;
    font-size: var(--font-size-3xl);
    line-height: var(--leading-lg);
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-tight);
  }

  .tree-scroll {
    flex: 1;
    min-height: 0;
    padding: 0 var(--space-2) var(--space-4);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .usage {
    display: grid;
    flex: none;
    gap: var(--space-2);
    max-height: 40vh;
    padding: var(--space-3) var(--gutter);
    overflow-y: auto;
    overscroll-behavior: contain;
    border-top: 1px solid var(--border);
  }

  .usage-row {
    display: grid;
    gap: var(--space-1);
    min-width: 0;
  }

  .usage-text {
    overflow-wrap: anywhere;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
  }

  @media (prefers-reduced-motion: no-preference) {
    .drawer {
      transition: transform var(--duration-slow) var(--ease-out), visibility 0s linear var(--duration-slow);
    }

    .drawer.is-open {
      transition: transform var(--duration-slow) var(--ease-out);
    }
  }

  /* ------------------------------------------------------------------------
     Toolbar: context row in the phone layout, top bar on desktop
     div.toolbar > div.toolbar-title (nav.breadcrumb + span.folder-stats) + div.file-search
       + label.sort-select-wrap + button.sort-dir-btn + button.view-toggle + button.toolbar-zip
       + button#new-folder-btn[hidden] + div.toolbar-upload
     ------------------------------------------------------------------------ */
  .toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    flex: none;
    min-height: var(--context-height);
    padding: 0 calc(var(--gutter) - 12px + var(--safe-right)) 0 calc(var(--gutter) + var(--safe-left));
  }

  .toolbar-title {
    display: grid;
    flex: 1;
    min-width: 0;
  }

  .folder-stats {
    overflow: hidden;
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    line-height: var(--leading-xs);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* A path cut at its start says so with an ellipsis instead of the fade of the component: under a fade the way
     began with half a letter and an arrow that comes from nothing («ы › Сайт компании»). The ellipsis is a
     sticky child of the scrolling path that takes no room in it (its negative margin gives its width back, so
     showing it moves nothing and changes no scroll position); it stands at the left edge over whatever is
     scrolled there and covers it with the colour of the page, on which the toolbar lies in both layouts.
     Presses go through it to the crumb under it. docs/UI.md 7.20 */
  .toolbar .breadcrumb {
    --crumb-cut-sign: calc(var(--space-2) + 1.5em);
  }

  .toolbar .breadcrumb::before {
    content: "…";
    position: sticky;
    left: 0;
    z-index: 1;
    flex: none;
    box-sizing: border-box;
    width: var(--crumb-cut-sign);
    margin-right: calc(var(--crumb-cut-sign) * -1);
    padding-left: var(--space-2);
    background: linear-gradient(to right, var(--bg) calc(var(--space-2) + 0.85em), transparent);
    color: var(--text-muted);
    visibility: hidden;
    pointer-events: none;
  }

  /* Not while the focus is in the path: the browser scrolls a focused crumb flush to the left edge, and the sign
     would lie over its first letter and its focus pill — the path then fades as the component does. */
  .toolbar .breadcrumb.is-cut-start:not(:focus-within)::before {
    visibility: visible;
  }

  .toolbar .breadcrumb.is-cut-start:not(:focus-within) {
    -webkit-mask-image: none;
    mask-image: none;
  }

  .toolbar .breadcrumb.is-cut-start.is-cut-end:not(:focus-within) {
    -webkit-mask-image: linear-gradient(to left, transparent, black var(--space-6));
    mask-image: linear-gradient(to left, transparent, black var(--space-6));
  }

  /* Phone layout: the app bar shows the name of the current folder, so the context row shows the way to it —
     «Корень › Документы»: the name of the open folder is hidden together with the separator before it (the
     last-but-one child of the path), so the way ends with a folder, not with an arrow that points at nothing.
     At the root the single crumb stays. */
  @media (max-width: 899px), (max-height: 499px) {
    .toolbar .breadcrumb > .crumb.link ~ .crumb.current,
    .toolbar .breadcrumb > .crumb-sep:nth-last-child(2) {
      display: none;
    }

    /* The move window: the name of what is moved takes three lines at most (a name of 255 characters filled the
       first screen of the sheet); the script gives the whole name in a title. The name is an element inside the
       lead: the lead itself is a row of the window's grid, and a row whose item clips its overflow shrinks to
       nothing when the window is lower than its content.
       p.modal-lead#move-file-name > span.move-lead[title] */
    .move-lead {
      display: -webkit-box;
      overflow: hidden;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 3;
    }

    /* The search field is closed until the search button of the app bar opens it (.is-open: a full-screen layer) */
    .toolbar > .file-search:not(.is-open) {
      display: none;
    }
  }

  /* Phone layout: «Загрузить» and «Новая папка» are in the sheet behind «+», «Скачать ZIP» of the current folder
     is in its menu (.folder-menu, opened from the app bar) */
  .toolbar-zip,
  .toolbar-upload > .btn {
    display: none;
  }

  /* The upload button is not shown in the phone layout, but its box stays in the tree, out of the flow:
     it holds the create menu (.popover), which in this layout is the sheet behind the floating «+» */
  .toolbar-upload {
    position: absolute;
  }

  /* Sort key: the visible value with the native select laid over it (the system picker opens on tap).
     label.sort-select-wrap > span.visually-hidden + select.sort-select + span.sort-select-value */
  .sort-select-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    flex: none;
    min-width: var(--hit-size);
    height: var(--hit-size);
    padding-left: var(--space-2);
    font-size: var(--font-size-md);
    line-height: var(--leading-sm);
    font-weight: var(--weight-medium);
  }

  .sort-select {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    font-size: var(--font-size-xl);
    opacity: 0;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
  }

  .sort-select:focus-visible + .sort-select-value {
    border-radius: var(--radius-xs);
    outline: 2px solid var(--focus-ring);
    outline-offset: 4px;
  }

  /* Sort direction: aria-pressed="true" means ascending. It reads as one chip with the key: «Имя ↑» */
  .sort-dir-btn {
    --btn-fg: var(--accent);
    --btn-fg-hover: var(--accent);
    margin-left: -10px;
  }

  .sort-dir-btn > .icon {
    --icon-size: 16;
  }

  .sort-dir-btn[aria-pressed="true"] > .sort-ico-down,
  .sort-dir-btn[aria-pressed="false"] > .sort-ico-up {
    display: none;
  }

  /* ------------------------------------------------------------------------
     Section header: «Недавние», «Избранное» and «Корзина» have a title instead of a path, and one sentence under it
     div.toolbar.section-toolbar[.is-title-only] > div.toolbar-title (h2.section-title + span.folder-stats) [+ button]
     div.section-note > p.context-help [+ details.context-help]
     Phone layout: the app bar shows the title, so the note comes first and a toolbar that holds only the title
     has no row. Desktop: the same 64px top bar, the note is one line under it.
     ------------------------------------------------------------------------ */
  .section-title {
    display: none;
  }

  .section-toolbar.is-title-only {
    display: none;
  }

  .section-toolbar .folder-stats {
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
  }

  .section-note {
    flex: none;
    order: -1;
    padding: 0 calc(var(--gutter) + var(--safe-right)) var(--space-3) calc(var(--gutter) + var(--safe-left));
  }

  /* The closed summary is a full-size target; its empty half is taken back from the padding of the note */
  .section-note > details.context-help {
    margin-bottom: calc(var(--space-3) * -1);
  }

  .section-note > details.context-help[open] {
    margin-bottom: 0;
  }

  @media (min-width: 600px) {
    .section-note {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      column-gap: var(--space-2);
    }

    /* The closed summary is a full-size target in a row with the sentence: both of its empty halves are taken
       back, so the two texts share a line and the note is as high with «Подробнее» as without it */
    .section-note > details.context-help:not([open]) {
      margin-top: calc(var(--space-3) * -1);
    }

    .section-note > details.context-help[open] {
      flex: 1 1 100%;
    }
  }

  /* Rows of the three sections. «Недавние»: the download action is as quiet as the quick actions of a file row.
     «Избранное»: the action is the brass star (.btn.fav.is-active), a toggle that removes the mark.
     «Корзина»: the two actions are .side-entry-actions.is-wide (text buttons from 600px, the menu behind «⋯» below
     that); from 600px the date («удалён …») takes the width of its text. */
  .recent-list .side-entry-actions > .btn {
    --btn-fg: var(--text-muted);
    --btn-fg-hover: var(--text);
  }

  @media (min-width: 600px) {
    .trash-list .side-entry-date {
      width: auto;
      padding-right: var(--space-3);
    }
  }

  /* ------------------------------------------------------------------------
     Files panel: the sheet that holds the list
     section#files-section.files-section > div.list-head + div#empty-state.empty + ul#file-list.file-list + div.list-pagination
     ------------------------------------------------------------------------ */
  .files-section {
    flex: 1;
    padding: var(--space-2) var(--safe-right) calc(var(--bottomnav-height) + var(--fab-size) + var(--space-8) + var(--safe-bottom)) var(--safe-left);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    background: var(--surface);
    box-shadow: var(--shadow-outline);
  }

  /* Files from the computer are dragged over the panel (the product toggles .drop-active) */
  .files-section.drop-active,
  .files-section.drop-active > .list-head {
    background: linear-gradient(var(--accent-soft), var(--accent-soft)) var(--surface);
  }

  .files-section.drop-active {
    box-shadow: 0 0 0 2px var(--accent);
  }

  /* Desktop only: the label shown under the panel while files are dragged over it (p.drop-hint, next sibling of
     the panel) and the addition «Или перетащите файлы сюда» to the sentence of the empty state (span.empty-drop-hint) */
  .empty-drop-hint,
  .drop-hint {
    display: none;
  }

  /* ------------------------------------------------------------------------
     Selection bar: exists only while something is selected
     div#selection-bar.selection-bar[hidden] > label.check.select-all-label + span.selection-count
       + button#move-selected-btn + button#delete-selected-btn.selection-delete + button#clear-selection-btn.selection-clear
     Phone layout: it replaces the app bar. Desktop: it replaces the top bar.
     It is a brand surface: the same colours in both themes.
     ------------------------------------------------------------------------ */
  .selection-bar {
    position: sticky;
    top: 0;
    z-index: var(--z-bar);
    display: flex;
    align-items: center;
    gap: var(--space-1);
    flex: none;
    height: calc(var(--appbar-height) + var(--safe-top));
    padding: var(--safe-top) calc(var(--gutter) - 10px + var(--safe-right)) 0 calc(var(--gutter) - 10px + var(--safe-left));
    background: var(--brand);
    color: var(--brand-contrast);
  }

  .selection-count {
    flex: 1;
    order: -1;
    min-width: 0;
    margin-left: 18px;
    overflow: hidden;
    font-size: var(--font-size-2xl);
    line-height: var(--leading-md);
    font-weight: var(--weight-bold);
    font-variant-numeric: tabular-nums;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .selection-bar > .btn {
    --btn-fg: var(--brand-contrast);
    --btn-fg-hover: var(--brand-contrast);
    width: var(--icon-button-size);
    height: var(--icon-button-size);
    padding: 0;
  }

  .selection-bar > .btn > span {
    display: none;
  }

  .selection-bar > .btn > .icon {
    --icon-size: 24;
    margin: 0;
  }

  .selection-bar > .selection-clear {
    order: -2;
  }

  .select-all-label {
    margin-right: var(--space-2);
    font-size: var(--font-size-md);
    font-weight: var(--weight-medium);
  }

  .select-all-label > .check-box {
    border-color: var(--brand-line);
    background: transparent;
    color: var(--brand);
  }

  .select-all-label > input:checked + .check-box,
  .select-all-label > input:indeterminate + .check-box {
    border-color: var(--brand-contrast);
    background: var(--brand-contrast);
  }

  .selection-bar :focus-visible,
  .select-all-label > input:focus-visible + .check-box {
    outline-color: var(--brand-contrast);
  }

  @media (hover: hover) {
    .selection-bar > .btn:hover {
      --btn-layer: var(--brand-state-hover);
    }

    .select-all-label > input:enabled:hover + .check-box {
      border-color: var(--brand-contrast);
    }
  }

  /* A short window (a phone on its side): the empty folder shows its title, its sentence and both buttons
     between the folder header and the bottom navigation — without the drawn tile and with small margins.
     With them the buttons were cut in half by the navigation and nothing said that the page scrolls */
  @media (max-height: 499px) {
    .files-section > .empty {
      padding-top: var(--space-4);
      padding-bottom: var(--space-4);
    }

    .files-section > .empty > .empty-icon {
      display: none;
    }
  }

  /* ------------------------------------------------------------------------
     Desktop layout
     ------------------------------------------------------------------------ */
  @media (min-width: 900px) and (min-height: 500px) {
    .explorer {
      display: grid;
      grid-template-columns: var(--tree-width, var(--sidebar-width)) minmax(0, 1fr);
      height: 100vh;
      height: 100dvh;
      min-height: 0;
      overflow: hidden;
    }

    /* No bottom navigation in this layout */
    .app-shell,
    .app-shell:not([hidden]) ~ .toast,
    .app-shell:not([hidden]) ~ .upload-panel {
      --bottom-bar-space: 0px;
    }

    /* Sidebar */
    .tree-pane {
      position: relative;
      display: flex;
      flex-direction: column;
      min-width: 0;
      min-height: 0;
      padding: 0 var(--space-3) var(--space-3);
    }

    .tree-pane > .brand {
      display: flex;
      flex: none;
      height: var(--topbar-height);
      padding: 0 var(--space-1);
    }

    .drawer {
      position: static;
      z-index: auto;
      flex: 1;
      width: auto;
      min-height: 0;
      margin-top: var(--space-5);
      padding: 0;
      border-radius: 0;
      background: none;
      box-shadow: none;
      visibility: visible;
      transform: none;
      transition: none;
    }

    .drawer-header {
      height: auto;
      padding: 0 var(--space-3);
    }

    .drawer-title {
      color: var(--text-muted);
      font-size: var(--font-size-xs);
      line-height: var(--leading-md);
      letter-spacing: 0.02em;
    }

    .drawer-close {
      display: none;
    }

    .tree-scroll {
      padding: 0;
      scrollbar-width: thin;
      scrollbar-color: var(--border-strong) transparent;
    }

    .usage {
      padding: var(--space-3) var(--space-3) 0;
    }

    /* Account block: opens the account menu above itself. The menu takes the room above the block at most (the
       padding of the sidebar under it, a gap, and a margin at the top of the window) and scrolls inside */
    .user-anchor {
      --user-block-height: 56px;
      flex: none;
      margin-top: var(--space-3);
    }

    .user-anchor > .popover {
      max-height: min(560px, calc(100vh - var(--user-block-height) - 2 * var(--space-3) - var(--space-1)));
      max-height: min(560px, calc(100dvh - var(--user-block-height) - 2 * var(--space-3) - var(--space-1)));
    }

    .user-block {
      display: flex;
      align-items: center;
      gap: var(--space-3);
      width: 100%;
      height: var(--user-block-height);
      padding: 0 var(--space-2);
      border-radius: var(--radius-md);
      text-align: left;
    }

    .user-block[aria-expanded="true"] {
      background: var(--state-pressed);
    }

    .user-meta {
      display: grid;
      flex: 1;
      min-width: 0;
    }

    .user-name {
      overflow: hidden;
      font-size: var(--font-size-md);
      line-height: var(--leading-sm);
      font-weight: var(--weight-medium);
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .user-login {
      overflow: hidden;
      color: var(--text-muted);
      font-size: var(--font-size-xs);
      line-height: var(--leading-xs);
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .user-block > .icon {
      --icon-size: 16;
      color: var(--text-subtle);
    }

    /* Sidebar width handle: 180–560px, written to --tree-width on #explorer */
    .tree-resizer {
      position: absolute;
      top: 0;
      right: -4px;
      bottom: 0;
      z-index: 1;
      display: block;
      width: 8px;
      cursor: col-resize;
      touch-action: none;
    }

    .tree-resizer::after {
      content: "";
      position: absolute;
      top: var(--space-4);
      bottom: var(--space-4);
      left: 3px;
      width: 2px;
      border-radius: 1px;
      background: transparent;
    }

    .tree-resizer:focus-visible {
      outline: none;
    }

    .tree-resizer:focus-visible::after,
    body.tree-resizing .tree-resizer::after {
      background: var(--focus-ring);
    }

    /* Main column. It takes all the room to the right of the sidebar and has no largest width: on a wide
       screen its right edge is the edge of the window (owner's decision of 07.10.2026, evening). */
    .content-pane {
      min-height: 0;
      padding: 0 var(--space-6) var(--space-6) var(--space-2);
    }

    /* Containing block of the drop hint. Section rows and their column titles start on the tile column of the
       file table (after the checkbox column) */
    .section-view {
      --side-indent: calc(var(--space-3) + var(--check-compact-size) + var(--check-compact-gap));
      position: relative;
      min-height: 0;
    }

    /* One action: the column of the quick actions of the file table, so size and date stand where they do there */
    [data-side-panel="recent"],
    [data-side-panel="favorites"] {
      --side-actions-width: var(--col-actions);
    }

    /* Two text buttons, «Восстановить» and «Удалить навсегда» */
    [data-side-panel="trash"] {
      --side-actions-width: 292px;
    }

    .trash-list .side-entry-date {
      width: var(--col-date);
      padding-right: 0;
    }

    .explorer .app-bar,
    .explorer .fab {
      display: none;
    }

    /* Top bar. The title sits on the checkbox line of the table */
    .toolbar {
      gap: var(--space-2);
      height: var(--topbar-height);
      min-height: 0;
      padding: 0 0 0 var(--space-3);
    }

    /* The title takes what its path needs: the search field gives way first, down to its minimum; after that the
       statistics move under the path, and only then the path itself scrolls. The empty last item is the slack
       that keeps the statistics on the line while the search field is still giving way. */
    .toolbar-title {
      display: flex;
      flex: 1 1 auto;
      flex-wrap: wrap;
      align-items: baseline;
      align-content: center;
      gap: 0 var(--space-3);
      margin-right: var(--space-3);
    }

    .toolbar-title::after {
      content: "";
      flex: none;
      width: var(--space-2);
    }

    .toolbar .breadcrumb {
      --crumb-leading: var(--leading-lg);
      flex: 0 1 auto;
      font-size: var(--font-size-3xl);
      letter-spacing: var(--tracking-tight);
    }

    /* The name of the open folder gives way before the path scrolls: it shrinks and ends in an ellipsis, down to
       a width that still reads, so a deep path is cut (and faded) at its start only and the last parent stays in
       view. Without this a name wider than the title filled it alone and faded out against the search field.
       The floor is ten em or the name's own width when that is less: a floor wider than the text would stretch a
       short name and push the start of a path that fits out of its box. CSS cannot name the width of the text
       inside min(), so the script measures it and sets --crumb-current-width on the path (syncBreadcrumbCut in
       app.js; docs/UI.md 7.20). The third limit keeps the floor inside the row: what the row has left after
       the arrow before the name (--crumb-room, measured by the same script) less the sign of the cut — in a
       narrow window ten em is wider than the row itself, and the name was cut at both ends. All limits are
       lengths without a percentage: a percentage is left out of the intrinsic width of the path, and the title
       would then be sized for a name that is cut afterwards. */
    .toolbar .breadcrumb > .crumb.current {
      flex: 0 1 auto;
      min-width: min(10em, var(--crumb-current-width, 0px), calc(var(--crumb-room, 100vw) - var(--crumb-cut-sign)));
      max-width: 20em;
    }

    /* While the script measures the name: its own width, whatever the room */
    .toolbar .breadcrumb.is-measured > .crumb.current {
      flex: none;
      min-width: 0;
      max-width: none;
    }

    .section-title {
      display: block;
      overflow: hidden;
      font-size: var(--font-size-3xl);
      line-height: var(--leading-lg);
      font-weight: var(--weight-bold);
      letter-spacing: var(--tracking-tight);
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .section-toolbar.is-title-only {
      display: flex;
    }

    /* A text button at the end of the bar: its label ends on the edge of the card */
    .section-toolbar > .btn-ghost {
      margin-right: calc(var(--space-3) * -1);
    }

    .section-note {
      order: 0;
      min-height: calc(var(--hit-size) + var(--space-2));
      margin-top: calc(var(--space-2) * -1);
      padding: 0 0 var(--space-2) var(--space-3);
    }

    .section-note > details.context-help,
    .section-note > details.context-help:not([open]) {
      margin-top: 0;
      margin-bottom: 0;
    }

    .folder-stats {
      flex: none;
      font-size: var(--font-size-sm);
      line-height: var(--leading-sm);
    }

    /* At its minimum the field ends the placeholder with an ellipsis («Поиск по име…») */
    .toolbar > .file-search {
      flex: 0 1000 280px;
      min-width: 160px;
    }

    .sort-select-wrap,
    .sort-dir-btn {
      display: none;
    }

    .toolbar-zip,
    .toolbar-upload > .btn {
      display: inline-flex;
    }

    /* «Скачать ZIP» of the current folder: a square glyph button (with title and aria-label) until there is room
       for the label next to a deep path — from 1440px */
    .toolbar-zip {
      width: var(--control-height);
      padding: 0;
    }

    .toolbar-zip > .icon {
      margin: 0;
    }

    .toolbar-zip > span {
      display: none;
    }

    .toolbar-upload {
      position: relative;
    }

    /* Wide enough for the three segments of the theme switch */
    .account-menu {
      width: 336px;
      max-width: calc(100vw - 32px);
    }

    /* The menu of the current folder belongs to the app bar of the phone layout */
    .folder-menu {
      display: none;
    }

    /* The card scrolls inside; its column header is sticky. It is a column: the pager is pinned to its bottom
       edge and an empty state is centred in it */
    .files-section {
      display: flex;
      flex-direction: column;
      min-height: 0;
      padding: 0;
      overflow-y: auto;
      overscroll-behavior: contain;
      border-radius: var(--radius-lg);
      box-shadow: var(--shadow-outline), var(--shadow-sm);
      scrollbar-width: thin;
      scrollbar-color: var(--border-strong) transparent;
    }

    .files-section > * {
      flex: none;
    }

    .files-section > .list-pagination {
      position: sticky;
      bottom: 0;
      z-index: var(--z-sticky);
      margin-top: auto;
      background: var(--surface);
    }

    .files-section > .empty {
      margin: auto 0;
    }

    /* The upload card (400px wide, in the bottom right corner — the corner of the list card) is on the screen:
       the product sets .has-upload-card on #app-shell. Collapsed, the card must cover no control. A list without
       a pager keeps scroll room of the collapsed card (its 52px header and 3px line) under its last row. A pager
       is as high as that card and stands in its place (the product sets .has-pager on the panel while the pager
       is shown): it only keeps its controls to the left of the card */
    .app-shell.has-upload-card .files-section:not(.has-pager) {
      padding-bottom: calc(55px + var(--space-2));
    }

    .app-shell.has-upload-card .files-section > .list-pagination {
      padding-right: calc(400px + var(--space-4));
    }

    /* …while the panel has room for them there: «Назад», the page number and «Далее» with the paddings need
       740px of the panel beside the card. In a narrower panel (a window of 900–1000px, or a wide sidebar) the
       pager stands above the collapsed card at its full width and keeps the band of the card under its
       controls. A percentage in a padding is the width of the panel, in the block direction too — so the two
       clamps switch at that width without a media query, which would not know the width of the sidebar:
       wider, the first is 416px and the second the usual 12px; narrower, the gutter and the band. */
    .app-shell.has-upload-card .files-section.has-pager > .list-pagination {
      padding-right: clamp(var(--gutter), calc((100% - 740px) * 999), calc(400px + var(--space-4)));
      padding-bottom: clamp(var(--space-3), calc((740px - 100%) * 999), calc(55px + var(--space-2) + var(--space-3)));
    }

    .files-section.drop-active + .drop-hint {
      position: absolute;
      bottom: var(--space-6);
      left: 50%;
      z-index: var(--z-float);
      display: flex;
      align-items: center;
      gap: var(--space-2);
      padding: var(--space-3) var(--space-4);
      border-radius: var(--radius-md);
      background: var(--brand);
      box-shadow: var(--shadow-md);
      color: var(--brand-contrast);
      font-size: var(--font-size-md);
      line-height: var(--leading-sm);
      font-weight: var(--weight-medium);
      white-space: nowrap;
      pointer-events: none;
      transform: translateX(-50%);
      box-sizing: border-box;
      max-width: calc(100% - var(--space-6) * 2);
    }

    .drop-hint > .icon {
      --icon-size: 18;
    }

    /* span#drop-hint-text > «Отпустите файлы — они загрузятся в папку «» + span.drop-hint-name + «»»: a long
       name gives way and ends in an ellipsis, the words around it stay whole */
    .drop-hint > span {
      display: flex;
      min-width: 0;
    }

    .drop-hint-name {
      overflow: hidden;
      min-width: 0;
      text-overflow: ellipsis;
    }

    /* The upload card is on the screen: the hint stands above the collapsed card, not under its corner */
    .app-shell.has-upload-card .files-section.drop-active + .drop-hint {
      bottom: calc(55px + var(--space-2) + var(--space-6));
    }

    /* Bulk bar in place of the top bar: same 64px slot, checkbox on the checkbox line of the table */
    .is-selecting .toolbar {
      display: none;
    }

    .selection-bar {
      position: static;
      gap: var(--space-2);
      height: calc(var(--control-height-sm) + var(--space-4));
      margin: calc((var(--topbar-height) - var(--control-height-sm) - var(--space-4)) / 2) 0;
      padding: 0 var(--space-2) 0 var(--space-3);
      border-radius: var(--radius-lg);
    }

    .selection-count {
      order: 0;
      margin-left: 0;
      font-size: var(--font-size-lg);
      line-height: var(--leading-sm);
    }

    .select-all-label {
      margin-right: var(--space-1);
    }

    /* The same box as the checkbox of the table under it (.row-check) */
    .select-all-label > .check-box {
      width: var(--check-compact-size);
      height: var(--check-compact-size);
      margin-top: 1px;
      border-radius: var(--check-compact-radius);
    }

    .select-all-label > .check-box::after {
      width: var(--check-compact-glyph);
      height: var(--check-compact-glyph);
      margin: calc(var(--check-compact-glyph) / -2) 0 0 calc(var(--check-compact-glyph) / -2);
    }

    .select-all-label > span:last-child {
      position: absolute;
      width: 1px;
      height: 1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    .selection-bar > .btn {
      --btn-border: var(--brand-line);
      width: auto;
      height: var(--control-height-sm);
      padding: 0 var(--space-3);
      border-radius: var(--radius-sm);
      font-size: var(--font-size-md);
    }

    .selection-bar > .btn > span {
      display: inline;
    }

    .selection-bar > .btn > .icon {
      --icon-size: 18;
      margin: 0 -2px;
    }

    .selection-bar > .selection-clear {
      order: 0;
    }

    .selection-bar > .selection-clear > .icon {
      display: none;
    }

    /* The destructive action is the one light object on the bar */
    .selection-bar > .selection-delete {
      --btn-bg: var(--brand-contrast);
      --btn-fg: var(--brand-danger);
      --btn-fg-hover: var(--brand-danger);
      --btn-border: transparent;
    }
  }

  @media (min-width: 900px) and (min-height: 500px) and (hover: hover) {
    .user-block:hover {
      background: var(--state-hover);
    }

    .tree-resizer:hover::after {
      background: var(--border-strong);
    }

    .selection-bar > .selection-delete:hover {
      --btn-layer: var(--brand-danger-soft);
    }
  }

  /* Dragging files in needs a mouse */
  @media (min-width: 900px) and (min-height: 500px) and (hover: hover) and (pointer: fine) {
    .empty-drop-hint {
      display: inline;
    }
  }

  @media (min-width: 1440px) and (min-height: 500px) {
    .toolbar-zip {
      width: auto;
      padding: 0 var(--space-4);
    }

    .toolbar-zip > .icon {
      margin: 0 -2px;
    }

    .toolbar-zip > span {
      display: inline;
    }
  }

  /* Narrow desktop: the sidebar gives way, secondary items of the top bar fold */
  @media (min-width: 900px) and (max-width: 1199px) and (min-height: 500px) {
    :root {
      --sidebar-width: 232px;
    }

    .folder-stats {
      display: none;
    }
  }

  /* Narrowest desktop: the name gets what the two other columns can spare. The date column shows the date
     without the time (span.file-date-short, «5 окт. 2026», at most 90px, in place of span.file-date); the size
     column holds a folder's statistics («130 файлов · 2,9 КБ», 120px of text) and stands 16px before the date,
     not 24px — the least that keeps the two column titles apart. Column titles and sorting stay. The tiles view
     has no columns and is not touched. Section rows fold their columns back into the line under the name and get
     the height of a two-line row. */
  @media (min-width: 900px) and (max-width: 1023px) and (min-height: 500px) {
    .app-shell {
      --col-size: 136px;
      --col-date: 100px;
    }

    .app-shell .file-list:not(.tiles-view) .file-size,
    .app-shell .files-section:not(.is-tiles) .list-head-size {
      padding-right: var(--space-4);
    }

    .app-shell .file-list:not(.tiles-view) .file-date,
    .app-shell .side-head-size,
    .app-shell .side-head-date,
    .app-shell .side-entry-size,
    .app-shell .side-entry-date {
      display: none;
    }

    .app-shell .file-list:not(.tiles-view) .file-date-short,
    .app-shell .side-entry-list.has-columns .side-entry-meta {
      display: block;
    }

    .app-shell .side-entry-list.has-columns > .side-entry {
      min-height: calc(var(--row-height) + var(--space-2));
    }
  }
}

@layer pages {
  /* Thumbnail in the icon of a row or a tile (docs/UI.md 7.9):
       span.entry-icon.has-thumb > svg.icon + img.entry-thumb [+ span.entry-thumb-play]
     The picture lies over the glyph and fills the icon box; modules/thumbnails.js puts it there when it
     has arrived, so a row without a thumbnail keeps its glyph. A frame of a video carries a play mark. */
  .entry-icon.has-thumb {
    position: relative;
    overflow: hidden;
  }

  .entry-thumb {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .entry-thumb-play {
    position: absolute;
    right: 2px;
    bottom: 2px;
    display: grid;
    place-items: center;
    width: 16px;
    height: 16px;
    border-radius: var(--radius-full);
    background: var(--stage-control-bg);
    color: var(--stage-text);
  }

  .entry-thumb-play > .icon {
    --icon-size: 9;
  }
}

@layer pages {
  /* Access mark in the corner of a folder's icon, and the owner chip (docs/UI.md 7.8, 7.9):
       span.entry-icon.folder-icon.has-access-mark > svg.icon + span.entry-access-mark > svg.icon
         (i-users on an own folder opened to others, i-user on a received one)
       span.tag-chips > span.tag-chip.owner-chip («от ivan») + the tags
     A folder of one's own that is opened to other people and a folder received from another person carry
     the mark — on the icon, so a list row and a tile show it alike and the brass marks of the name row keep
     their places. The owner chip is the first chip of a received folder: it is what tells it from an own
     folder of the same name. */
  .entry-icon.has-access-mark {
    position: relative;
  }

  .entry-access-mark {
    position: absolute;
    right: -4px;
    bottom: -4px;
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text-muted);
  }

  .entry-access-mark > .icon {
    --icon-size: 11;
  }

  .is-received .entry-access-mark {
    border-color: var(--accent);
    color: var(--accent);
  }

  .tag-chip.owner-chip {
    background: var(--accent-soft);
    color: var(--text);
  }

  /* In the folders tree (7.21) a received folder says whose it is after its name, inside the same span — an
     own folder of the same name may stand beside it; a narrow tree cuts it with the name, the title keeps it. */
  .tree-owner {
    color: var(--text-muted);
    font-size: var(--font-size-sm);
  }

  /* The window «Доступ к папке» (docs/UI.md, section 11, point 22). What is shown once the list has come
     stands in a block of its own, spaced like the body of the window; the choice of a right stands in the
     row of its person, beside «Убрать», and is as wide as its longest word — not as the row. */
  #access-content {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-4);
  }

  .access-list .item-actions > .access-level {
    flex: none;
    width: auto;
  }

  #access-retry-btn {
    justify-self: start;
  }
}

/* ==========================================================================
   File Vault — sign-in screen (#auth-screen). The second part of app.css.

   main#auth-screen.auth-screen > div.auth-card
     header.auth-head  > div.brand.auth-brand (img.brand-mark + h1.brand-name) + p#auth-lead.auth-lead
     div.auth-panel    > div.segmented.auth-tabs + p#auth-error.form-error + div#auth-notice.alert.alert-success
                         + form.auth-form (x2)
       form.auth-form  > .field … + div.auth-actions (> .btn.btn-block …) [+ div.alert.auth-note]
     div.auth-panel    > div.empty.auth-state (span.empty-icon + h1|h2.empty-title + p … + div.auth-actions)
                         a state that is one message: #boot-failed of div#boot-screen (the same skeleton with
                         the head alone until the server has answered), #link-login-state of #auth-screen

   Phone layout: the brand stands on the paper, the form is a sheet with a rounded top that reaches the
   bottom of the screen (the same paper-and-sheet construction as the storage screen). A phone lower than
   700px shows the lockup of the system (one line, 32px mark), so that the fields start higher.
   From 600px, or from 700px of height: the lockup of the sign-in screen, a 56px mark above the name.
   From 600px: a centred column, the sheet becomes a card.
   Short and wide window (a phone in landscape): brand on the left, the card on the right, the buttons in a row.
   ========================================================================== */
@layer layout {
  .auth-screen {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
  }

  .auth-card {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
  }

  .auth-head {
    display: grid;
    justify-items: center;
    gap: var(--space-3);
    padding: calc(var(--space-6) + var(--safe-top)) calc(var(--gutter) + var(--safe-right)) var(--space-5) calc(var(--gutter) + var(--safe-left));
    text-align: center;
  }

  .auth-panel {
    display: grid;
    flex: 1;
    align-content: start;
    gap: var(--space-4);
    min-width: 0;
    padding: var(--space-5) calc(var(--gutter) + var(--safe-right)) calc(var(--space-8) + var(--safe-bottom)) calc(var(--gutter) + var(--safe-left));
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    background: var(--surface);
    box-shadow: var(--shadow-outline);
  }

  /* A phone of ordinary height has room for a taller head (from 600px the head has its own paddings) */
  @media (min-height: 700px) {
    .auth-head {
      padding-top: calc(var(--space-12) + var(--safe-top));
      padding-bottom: var(--space-8);
    }
  }

  @media (min-width: 600px) {
    .auth-screen {
      align-items: center;
      justify-content: center;
      padding: var(--space-8) var(--gutter);
    }

    .auth-card {
      flex: none;
      width: 100%;
      max-width: 400px;
    }

    .auth-head {
      padding: 0 0 var(--space-6);
    }

    .auth-panel {
      flex: none;
      padding: var(--space-6);
      border-radius: var(--radius-lg);
      box-shadow: var(--shadow-outline), var(--shadow-sm);
    }
  }

  /* A phone in landscape: two columns, so that the sign-in form fits the screen without scrolling — with its
     error line too (a 667 × 375 window is the measure) */
  @media (min-width: 600px) and (max-height: 499px) {
    .auth-screen {
      padding: var(--space-2) calc(var(--gutter) + var(--safe-right)) var(--space-2) calc(var(--gutter) + var(--safe-left));
    }

    .auth-card {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(0, 400px);
      align-items: start;
      gap: var(--space-8);
      max-width: 720px;
    }

    .auth-head {
      justify-items: start;
      padding: var(--space-4) 0 0;
      text-align: left;
    }

    .auth-panel {
      gap: var(--space-2);
      padding: var(--space-3) var(--space-4);
    }
  }
}

@layer pages {
  /* The name is the title of the page */
  .auth-brand > .brand-name {
    font-size: var(--font-size-4xl);
    line-height: var(--leading-lg);
  }

  /* The lockup of the sign-in screen: a 56px mark above the name (docs/UI.md, section 6, allows 48–64px here). A phone lower
     than 700px keeps the lockup of the system — one line, 32px mark */
  @media (min-width: 600px), (min-height: 700px) {
    .auth-brand {
      flex-direction: column;
      gap: var(--space-4);
    }

    .auth-brand > .brand-mark {
      width: 56px;
      height: 56px;
    }
  }

  .auth-lead {
    max-width: 24em;
    color: var(--text-muted);
    text-wrap: balance;
    font-size: var(--font-size-lg);
    line-height: var(--leading-sm);
  }

  .auth-form {
    display: grid;
    gap: var(--space-4);
    min-width: 0;
  }

  /* The buttons of a form: stacked, the primary one first */
  .auth-actions {
    display: grid;
    gap: var(--space-3);
    margin-top: var(--space-2);
  }

  /* A state of the sheet that is one message with its actions — the start of the page without an answer about
     the session (#boot-failed), the sign-in by a one-time link (#link-login-state): the empty-state component
     without paddings of its own (the sheet has them), its buttons stacked at the width of the sheet */
  .auth-state {
    padding: var(--space-2) 0 0;
  }

  /* A message inside the state — the notice about the session a link would replace, the reason of a refusal —
     takes the width of the sheet and reads from the left, like every alert */
  .auth-state > .alert,
  .auth-state > .form-error {
    justify-self: stretch;
    max-width: none;
    margin-top: var(--space-2);
    text-align: start;
  }

  /* The text of an alert stays the text colour (the block around it is muted) */
  .auth-state > .alert {
    color: var(--text);
  }

  .auth-state > .auth-actions {
    justify-self: stretch;
    margin-top: var(--space-4);
  }

  @media (min-width: 600px) and (max-height: 499px) {
    .auth-brand {
      align-items: flex-start;
    }

    .auth-state {
      padding-top: 0;
    }

    .auth-state > .empty-icon {
      width: 48px;
      height: 48px;
      margin-bottom: 0;
    }

    .auth-state > .empty-icon > .icon {
      --icon-size: 28;
    }

    .auth-state > .auth-actions {
      margin-top: var(--space-2);
    }

    .auth-brand > .brand-mark {
      width: 48px;
      height: 48px;
    }

    .auth-form {
      gap: var(--space-3);
    }

    /* Side by side: one row of buttons instead of two. As in every row of buttons the primary one stands on the
       right and takes the room that is left; a form with one button gives it the whole row */
    .auth-actions {
      grid-auto-flow: column;
      grid-auto-columns: auto minmax(96px, 1fr);
      margin-top: 0;
    }

    .auth-actions > .btn-primary {
      order: 1;
    }
  }
}

/* ==========================================================================
   File Vault — viewer (#view-modal) and properties (#props-modal)
   The third part of app.css.
   Sections:
     1. Layout: the viewer as a full screen (phone layout) or a large dialog (desktop)
     2. Header, notice, description strip
     3. Stages: image, PDF frame, text, document, table, book, video, audio, messages (no preview, failed, loading)
   ========================================================================== */

/* 1. Layout ---------------------------------------------------------------- */
@layer layout {
  /* The viewer is a .modal.modal-screen: a screen in the phone layout (any width below the desktop layout, and every
     short window) — its bar stands on the paper, like the app bar of the storage screen */
  .view-dialog {
    background: var(--bg);
  }

  .view-header {
    display: flex;
    align-items: center;
    flex: none;
    height: var(--view-bar-height);
    padding: 0 calc(var(--gutter) - 12px + var(--safe-right)) 0 calc(var(--gutter) - 10px + var(--safe-left));
  }

  .view-notice,
  .view-desc,
  .view-open-bar {
    flex: none;
  }

  /* The stage: a panel with a rounded top, like the list panel of the storage screen. It scrolls whatever it
     holds: a fitted picture, a frame and a table (.is-flush, its own scroller) never overflow it; text, documents
     and — in a short window — a message with its actions do */
  .view-stage {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    background: var(--surface);
    box-shadow: var(--shadow-outline);
  }

  .view-stage.is-dark {
    background: var(--stage-bg);
    box-shadow: var(--stage-edge);
    color: var(--stage-text);
  }

  /* Desktop layout: a large dialog over the dimmed storage screen, the stage takes everything under the header */
  @media (min-width: 900px) and (min-height: 500px) {
    .view-dialog {
      background: var(--surface-overlay);
    }

    .view-header {
      padding: 0 var(--space-3) 0 var(--space-6);
    }

    .view-stage {
      border-top: 1px solid var(--border);
      border-radius: 0;
      background: transparent;
      box-shadow: none;
    }

    .view-stage.is-dark {
      border-top: 0;
      background: var(--stage-bg);
      box-shadow: none;
    }

    /* Paper and sheet: documents (.is-scroll: text, docx, a book) lie on the paper canvas */
    .view-stage.is-scroll {
      padding: var(--space-6);
      background: var(--bg);
    }

    .view-stage.is-flush {
      background: var(--surface);
    }
  }
}

@layer pages {
  /* 2. Header -------------------------------------------------------------- */
  /* Phone layout: the icon buttons of the bar are those of the app bar — 24px glyphs in the text colour */
  .view-header .btn-icon {
    --btn-fg: var(--text);
  }

  .view-header .btn-icon > .icon {
    --icon-size: 24;
  }

  /* One close control: the back arrow of a screen in the phone layout, the cross of a dialog on desktop */
  .view-close > .view-close-x {
    display: none;
  }

  .view-header-icon {
    display: none;
  }

  /* The name starts on the text edge of the rows of the storage screen: gutter + tile + gap. The bar starts 10px
     before the gutter (the optical inset of its first button, as in the app bar) and the button takes its width */
  .view-title {
    display: grid;
    flex: 1;
    min-width: 0;
    margin: 0 var(--space-2) 0 calc(var(--tile-size) + var(--space-3) + 10px - var(--icon-button-size));
  }

  .view-file-name {
    overflow: hidden;
    font-size: var(--font-size-xl);
    line-height: var(--leading-sm);
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-tight);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .view-meta {
    display: flex;
    min-width: 0;
    overflow: hidden;
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    line-height: var(--leading-xs);
    white-space: nowrap;
  }

  /* Proportional figures: the counter changes its width between «9 / 11» and «10 / 11» anyway, and the tabular
     «1» of the typeface stands apart («1 1 / 1 1») */
  .view-count {
    flex: none;
  }

  .view-size {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* The same separator as the typed one of a meta line: « · » */
  .view-count:not([hidden]) + .view-size::before {
    content: " · ";
    white-space: pre;
  }

  .view-actions {
    display: flex;
    align-items: center;
    flex: none;
    gap: var(--space-1);
  }

  /* Labelled buttons in the bar are as high as its icon buttons */
  .view-header .btn-primary,
  .view-header .btn-secondary {
    height: var(--icon-button-size);
  }

  /* A bordered button keeps the text colour when it is a glyph */
  .view-header .btn-secondary {
    --btn-fg: var(--text);
  }

  /* «Скачать» is .btn-collapse: a square below 600px, labelled from 600px. «Открыть отдельно» is
     .btn-collapse-wide: a glyph in the whole phone layout (two labelled buttons leave a long name half of the bar
     up to about 760px), labelled on desktop. It is in the bar wherever the bottom bar of a portrait phone is not */
  .view-open,
  .view-desc-btn {
    display: none;
  }

  .view-menu {
    flex: none;
  }

  /* Notice of a file opened from the trash */
  .view-notice {
    margin: 0 var(--gutter) var(--space-2);
  }

  /* Description: one quiet line under the bar; the text sits on the text edge of the title */
  .view-desc-bar {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--hit-size);
    padding: 0 calc(var(--gutter) - 12px + var(--safe-right)) 0 calc(var(--gutter) + var(--safe-left));
  }

  .view-desc-toggle {
    display: flex;
    align-items: center;
    flex: 1;
    gap: var(--space-3);
    min-width: 0;
    min-height: var(--hit-size);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: var(--font-size-md);
    line-height: var(--leading-sm);
    text-align: left;
    cursor: default;
  }

  /* The text fits its line: nothing to expand. The script disables the toggle (no tab stop, no aria-expanded);
     the text keeps its colour */
  .view-desc-toggle:disabled {
    color: var(--text);
  }

  .view-desc-ico {
    margin: 0 calc((var(--tile-size) - 20px) / 2);
    color: var(--text-muted);
  }

  .view-desc-text {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .view-desc-chevron {
    --icon-size: 16;
    display: none;
    margin-left: calc(var(--space-2) * -1);
    color: var(--text-muted);
  }

  /* Longer than one line: the line is a disclosure */
  .view-desc.is-clamped .view-desc-toggle {
    cursor: pointer;
  }

  .view-desc.is-clamped .view-desc-chevron {
    display: block;
  }

  /* Read-only (a file in the trash: no «Изменить» after the line): the toggle runs to the end of the bar, so the
     collapsed chevron takes the place of the expanded one — on the axis of the last button of the bar above it */
  .view-desc.is-readonly .view-desc-chevron {
    margin-right: calc((var(--icon-button-size) - 16px) / 2);
  }

  /* Expanded: the whole text (it scrolls after a third of the screen), «Изменить» under it on the text edge */
  .view-desc.is-open .view-desc-bar {
    flex-wrap: wrap;
    padding-bottom: var(--space-1);
  }

  .view-desc.is-open .view-desc-toggle {
    flex: 1 1 100%;
    align-items: flex-start;
  }

  .view-desc.is-open .view-desc-ico {
    margin-top: calc((var(--hit-size) - 20px) / 2);
  }

  /* The chevron stands on the axis of the last button of the bar above it */
  .view-desc.is-open .view-desc-chevron {
    margin: calc((var(--hit-size) - 16px) / 2) calc((var(--icon-button-size) - 16px) / 2) 0 0;
    transform: rotate(180deg);
  }

  .view-desc.is-open .view-desc-text {
    flex: 1 1 auto;
    max-height: 30vh;
    padding: calc((var(--hit-size) - var(--leading-sm)) / 2) 0;
    overflow-y: auto;
    overflow-wrap: anywhere;
    white-space: pre-wrap;
  }

  /* The 1px border of the text button is not part of an alignment: its label ends on the gutter line, and in the
     two rules below it starts on the text edge and its glyph stands on the tile axis */
  .view-desc-edit-btn {
    margin-right: -1px;
  }

  .view-desc.is-open .view-desc-edit-btn {
    margin-top: calc(var(--space-2) * -1);
    margin-left: calc(var(--tile-size) - 1px);
  }

  .view-desc-edit-btn > .icon {
    display: none;
  }

  /* No description yet: the whole line is the «Добавить описание» action */
  .view-desc.is-empty .view-desc-edit-btn {
    gap: var(--space-3);
    margin-left: -1px;
    padding-left: 0;
    font-size: var(--font-size-md);
  }

  .view-desc.is-empty .view-desc-edit-btn > .icon {
    display: block;
    margin: 0 calc((var(--tile-size) - 18px) / 2);
  }

  .view-desc-edit {
    display: none;
    gap: var(--space-3);
    padding: var(--space-1) calc(var(--gutter) + var(--safe-right)) var(--space-4) calc(var(--gutter) + var(--safe-left));
  }

  .view-desc.is-editing .view-desc-bar {
    display: none;
  }

  .view-desc.is-editing .view-desc-edit {
    display: grid;
  }

  .view-desc-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
  }

  /* PDF on a portrait phone: the way out to the browser's own viewer */
  .view-open-bar {
    padding: var(--space-3) calc(var(--gutter) + var(--safe-right)) calc(var(--space-3) + var(--safe-bottom)) calc(var(--gutter) + var(--safe-left));
  }

  @media (min-width: 600px) {
    .view-actions {
      gap: var(--space-2);
    }

    /* The form starts on the text edge of the line it replaces */
    .view-desc-edit {
      max-width: 640px;
      padding-left: calc(var(--gutter) + var(--tile-size) + var(--space-3) + var(--safe-left));
    }
  }

  @media (min-width: 600px), (max-height: 499px) {
    .view-open {
      display: inline-flex;
    }

    .view-open-bar {
      display: none;
    }
  }

  /* Short window: the description leaves the layout and opens from the bar as a panel over the stage */
  @media (max-height: 499px) {
    .view-desc-btn {
      display: inline-flex;
    }

    .view-desc {
      display: none;
    }

    .view-desc.is-open,
    .view-desc.is-editing {
      position: absolute;
      top: calc(var(--view-bar-height) + var(--safe-top));
      right: 0;
      left: 0;
      z-index: var(--z-popover);
      display: block;
      max-height: calc(100% - var(--view-bar-height) - var(--safe-top) - var(--space-4));
      overflow-y: auto;
      border-radius: 0 0 var(--radius-lg) var(--radius-lg);
      background: var(--bg);
      box-shadow: var(--shadow-lg);
    }

    .view-desc.is-open .view-desc-chevron {
      display: none;
    }

    .view-notice {
      margin-bottom: var(--space-1);
    }
  }

  @media (min-width: 900px) and (min-height: 500px) {
    /* A window: its icon buttons are the quiet ones of a dialog */
    .view-header .btn-icon {
      --btn-fg: var(--text-muted);
    }

    .view-header .btn-icon > .icon {
      --icon-size: 20;
    }

    .view-close {
      order: 3;
      margin-left: var(--space-1);
    }

    .view-close > .view-close-back {
      display: none;
    }

    .view-close > .view-close-x {
      display: block;
    }

    .view-header-icon {
      display: grid;
    }

    .view-title {
      display: flex;
      align-items: center;
      gap: var(--space-3);
      margin: 0 var(--space-4) 0 var(--space-3);
    }

    .view-file-name {
      font-size: var(--font-size-2xl);
      line-height: var(--leading-md);
    }

    .view-meta {
      align-items: center;
      flex: none;
      gap: var(--space-2);
      font-size: var(--font-size-sm);
      line-height: var(--leading-sm);
    }

    /* The counter is a chip next to the name: the box of the system .count (7.7) with proportional figures. It is
       not .count itself — in the phone layout the counter is plain text of the meta line */
    .view-count {
      display: inline-grid;
      place-items: center;
      height: var(--leading-sm);
      padding: 0 calc(var(--space-3) / 2);
      border-radius: var(--radius-xs);
      background: var(--chip-bg);
      font-size: var(--font-size-xs);
      line-height: var(--leading-xs);
      font-weight: var(--weight-bold);
    }

    .view-count:not([hidden]) + .view-size::before {
      content: none;
    }

    .view-actions {
      gap: var(--space-2);
    }

    .view-notice {
      margin: 0 var(--space-6) var(--space-3);
    }

    .view-desc-bar {
      padding: 0 var(--space-3) var(--space-2) var(--space-6);
    }

    .view-desc-edit {
      max-width: 720px;
      padding: 0 var(--space-6) var(--space-5) calc(var(--space-6) + var(--tile-size) + var(--space-3));
    }
  }

  @media (min-width: 900px) and (min-height: 500px) and (hover: hover) and (pointer: fine) {
    .view-header .btn-primary,
    .view-header .btn-secondary {
      height: var(--control-height);
    }

    .view-desc-bar {
      min-height: var(--control-height);
    }
  }

  @media (hover: hover) {
    .view-desc.is-clamped .view-desc-toggle:hover .view-desc-chevron {
      color: var(--text);
    }
  }

  @media (prefers-reduced-motion: no-preference) {
    .view-desc-chevron {
      transition: transform var(--duration-fast) var(--ease-standard);
    }
  }

  /* 3. Stages -------------------------------------------------------------- */
  /* Image: fitted into the stage on both axes, never enlarged beyond its own size */
  .view-image {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    max-width: 100%;
    height: auto;
    max-height: 100%;
    margin: auto;
    object-fit: contain;
  }

  /* Previous / next. Portrait phone: in the lower corners, where a fitted picture leaves room;
     short windows and desktop: at the sides, on the middle line. Swipe does the same on touch. */
  .view-nav-btn {
    position: absolute;
    bottom: calc(var(--space-4) + var(--safe-bottom));
    display: grid;
    place-items: center;
    width: var(--stage-nav-size);
    height: var(--stage-nav-size);
    border-radius: var(--radius-md);
    background: var(--stage-control-bg);
    box-shadow: inset 0 0 0 1px var(--stage-control-line);
    color: var(--stage-text);
  }

  .view-nav-btn > .icon {
    --icon-size: 24;
  }

  .view-prev {
    left: calc(var(--space-4) + var(--safe-left));
  }

  .view-next {
    right: calc(var(--space-4) + var(--safe-right));
  }

  .view-nav-btn:disabled {
    box-shadow: none;
    color: var(--stage-text-muted);
    opacity: 0.5;
    cursor: default;
  }

  .view-nav-btn:focus-visible,
  .view-stage.is-dark :focus-visible {
    outline-color: var(--stage-focus);
  }

  .view-nav-btn:active {
    background: var(--stage-control-hover);
  }

  /* A picture of the set failed to load: the message stands on the theme's surface, not on the dark stage,
     and the buttons that lead on to the next picture take the theme's colours */
  .view-stage:not(.is-dark) > .view-nav-btn {
    background: var(--control-bg);
    box-shadow: inset 0 0 0 1px var(--border-strong);
    color: var(--text);
  }

  .view-stage:not(.is-dark) > .view-nav-btn:disabled {
    box-shadow: none;
    color: var(--text-muted);
  }

  .view-stage:not(.is-dark) > .view-nav-btn:focus-visible {
    outline-color: var(--focus-ring);
  }

  .view-stage:not(.is-dark) > .view-nav-btn:active {
    background: var(--chip-bg);
  }

  @media (max-height: 499px) {
    .view-nav-btn {
      top: 50%;
      bottom: auto;
      margin-top: calc(var(--stage-nav-size) / -2);
    }
  }

  @media (min-width: 900px) and (min-height: 500px) {
    .view-nav-btn {
      top: 50%;
      bottom: auto;
      margin-top: calc(var(--stage-nav-size) / -2);
    }

    .view-prev {
      left: var(--space-5);
    }

    .view-next {
      right: var(--space-5);
    }
  }

  @media (hover: hover) {
    .view-nav-btn:hover {
      background: var(--stage-control-hover);
    }

    .view-stage:not(.is-dark) > .view-nav-btn:hover {
      background: var(--chip-bg);
    }
  }

  /* PDF and other framed previews: the frame fills the stage */
  .view-frame,
  .view-video {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
  }

  /* Text, documents: the stage is the sheet in the phone layout; a page on the paper canvas on desktop */
  .view-docx {
    padding: var(--space-5) calc(var(--gutter) + var(--safe-right)) calc(var(--space-8) + var(--safe-bottom)) calc(var(--gutter) + var(--safe-left));
    color: var(--text);
    font-size: var(--font-size-lg);
    line-height: var(--leading-md);
    overflow-wrap: break-word;
  }

  .view-docx h1,
  .view-docx h2,
  .view-docx h3 {
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-tight);
  }

  .view-docx h1 {
    margin-bottom: var(--space-2);
    font-size: var(--font-size-4xl);
    line-height: var(--leading-lg);
  }

  .view-docx h2 {
    margin: var(--space-6) 0 var(--space-2);
    font-size: var(--font-size-2xl);
    line-height: var(--leading-md);
  }

  .view-docx h3 {
    margin: var(--space-4) 0 var(--space-2);
    font-size: var(--font-size-xl);
  }

  .view-docx p,
  .view-docx ul,
  .view-docx ol {
    margin-bottom: var(--space-3);
  }

  .view-docx ul,
  .view-docx ol {
    padding-left: var(--space-6);
  }

  .view-docx ul {
    list-style: disc;
  }

  .view-docx ol {
    list-style: decimal;
  }

  .view-docx strong {
    font-weight: var(--weight-bold);
  }

  .view-docx a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  .view-docx img {
    max-width: 100%;
    height: auto;
  }

  /* A table keeps readable columns and scrolls sideways on its own instead of breaking words */
  .view-docx > table {
    display: block;
    max-width: 100%;
    margin: var(--space-4) 0;
    overflow-x: auto;
    border-collapse: collapse;
    font-size: var(--font-size-md);
    line-height: var(--leading-sm);
  }

  /* The grid is the hairline of the spreadsheet preview; --border-strong is the outline of controls */
  .view-docx > table th,
  .view-docx > table td {
    min-width: 5.5em;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border);
    text-align: left;
    vertical-align: top;
    overflow-wrap: normal;
  }

  .view-docx > table th {
    background: var(--chip-bg);
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  /* A converted document wraps the content of a cell in paragraphs: the last one adds no space under it */
  .view-docx > table th > :last-child,
  .view-docx > table td > :last-child {
    margin-bottom: 0;
  }

  /* A Word 97–2003 document is shown as its text: line breaks, tabs and runs of spaces are its only layout */
  .view-docx-doc > p,
  .view-docx-doc > table td {
    white-space: pre-wrap;
    tab-size: 4;
  }

  /* A line of the page, not of the document: «Документ пуст», «Показано начало документа» */
  .view-doc-note {
    color: var(--text-muted);
  }

  /* The content is on the way (#view-loading stands in the stage): the sheet is not drawn empty beside it */
  .view-docx:empty {
    display: none;
  }

  /* An archive: what is inside it, one level at a time — the summary line, the path from the name of the
     archive to the open folder, the rows (public/modules/archive-view.js). Names come from the archive: in the
     rows and in the name of the open folder they wrap and are never cut, there is no other place to read them */
  .view-archive {
    padding: var(--space-5) calc(var(--gutter) + var(--safe-right)) calc(var(--space-8) + var(--safe-bottom)) calc(var(--gutter) + var(--safe-left));
    color: var(--text);
  }

  .view-archive-summary {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
  }

  /* The path wraps by lines (the rules of the storage's breadcrumb do not apply here): a piece above the open
     folder is one line, cut with an ellipsis when it is longer than the path — its name is read whole once the
     folder is open —, the name of the open folder is whole and wraps */
  .view-archive-path {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0 var(--space-1);
    min-width: 0;
    margin: var(--space-1) 0 var(--space-2);
  }

  .view-archive-crumb {
    min-height: var(--control-height-sm);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
  }

  /* A crumb above the open folder is a button: a touch target in both directions, however short the name. It is
     a block, not a flex box: the ellipsis is drawn only for the text of a block (a button centres it by height) */
  button.view-archive-crumb {
    display: block;
    min-width: var(--control-height-sm);
    /* Room for the separator that follows: it stays at the end of the line of a cut name */
    max-width: calc(100% - var(--space-6));
    overflow: hidden;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .view-archive-crumb.is-current {
    display: inline-flex;
    align-items: center;
    min-width: 0;
    color: var(--text);
    font-weight: var(--weight-medium);
    overflow-wrap: anywhere;
  }

  .view-archive-sep {
    display: inline-flex;
    flex: none;
    color: var(--text-subtle);
  }

  .view-archive-list {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  /* A folder row opens as a whole: it is one button that holds the icon, the name and the count, and its
     ::after covers the paddings of the row as well (the row is the containing block). The button is the
     touch target; its focus ring is drawn by that ::after, around the whole row */
  .view-archive-row {
    position: relative;
  }

  .view-archive-open {
    display: flex;
    flex: 1;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
    min-height: var(--control-height-sm);
    border-radius: var(--radius-sm);
    text-align: left;
  }

  .view-archive-open::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
  }

  .view-archive-go {
    display: inline-flex;
    flex: none;
    color: var(--text-subtle);
  }

  /* One focus ring for the list: around the row, with the radius of a control, whether the row is a folder
     (the focus is on its button, the ring on the button's ::after) or a file (the row itself takes the focus,
     and only from the script: the first row of a level) */
  .view-archive-open:focus-visible {
    outline: none;
  }

  .view-archive-open:focus-visible::after {
    border-radius: var(--radius-sm);
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
  }

  .view-archive-row:focus-visible {
    border-radius: var(--radius-sm);
  }

  .view-archive-more {
    display: flex;
    justify-content: center;
    padding: var(--space-4) 0 0;
    border-top: 1px solid var(--border);
  }

  @media (hover: hover) {
    button.view-archive-crumb:hover,
    .view-archive-open:hover .item-title {
      color: var(--accent);
    }
  }

  /* Plain text: md, json, csv, log, yml as well. Lines wrap; the line length is capped */
  .view-text {
    max-width: 88ch;
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
    overflow-wrap: anywhere;
    white-space: pre-wrap;
    tab-size: 4;
  }

  /* Spreadsheet: sheet tabs and one two-axis scroll area; the header row and the first column stay */
  .view-docx-xls {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 0;
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
  }

  .view-xls {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
  }

  .view-xls-tabs {
    flex: none;
    padding: 0 calc(var(--gutter) - var(--space-3) + var(--safe-right)) 0 calc(var(--gutter) - var(--space-3) + var(--safe-left));
  }

  /* A workbook may have more sheets than the strip is wide. Tabs hide their scrollbar (docs/UI.md 7.6), and a
     mouse then has no way to the far sheets: with a fine pointer the strip of sheets shows a thin scrollbar.
     A touch screen keeps the bare strip — a finger drags it. */
  @media (hover: hover) and (pointer: fine) {
    .view-xls-tabs {
      scrollbar-width: thin;
      scrollbar-color: var(--border-strong) transparent;
    }

    /* Browsers without scrollbar-width (Safari before 18.2) */
    @supports not (scrollbar-width: thin) {
      .view-xls-tabs::-webkit-scrollbar {
        display: block;
        height: 6px;
      }

      .view-xls-tabs::-webkit-scrollbar-thumb {
        border-radius: var(--radius-full);
        background: var(--border-strong);
      }
    }
  }

  .view-xls-sheet {
    flex: 1;
    min-height: 0;
    padding-bottom: var(--safe-bottom);
    overflow: auto;
    overscroll-behavior: contain;
  }

  .view-xls-sheet:focus-visible {
    outline-offset: -2px;
  }

  .view-xls table {
    min-width: 100%;
    border-spacing: 0;
    border-collapse: separate;
  }

  .view-xls th,
  .view-xls td {
    padding: calc(var(--space-5) / 2) var(--space-3);
    border-right: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    font-weight: var(--weight-regular);
    text-align: left;
    white-space: nowrap;
  }

  .view-xls th:first-child,
  .view-xls td:first-child {
    padding-left: calc(var(--gutter) + var(--safe-left));
  }

  /* A table built by SheetJS has neither a header nor row headings (tbody is its first child, every cell is
     a td): its first row and its first column are taken for them, as in the drawn table */
  .view-xls thead th,
  .view-xls tbody:first-child > tr:first-child > td {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    background: linear-gradient(var(--chip-bg), var(--chip-bg)) var(--surface);
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    line-height: var(--leading-sm);
    font-weight: var(--weight-medium);
  }

  /* The first column stays in place only in the wide layout (below): on a phone a first column of
     ordinary long text would cover the sheet and leave nothing of the other columns to scroll to */
  .view-xls tbody th,
  .view-xls tbody:first-child > tr > td:first-child {
    color: var(--text-muted);
  }

  /* A sheet without cells: one muted line on the text edge of the tabs */
  .view-xls-empty {
    padding: var(--space-5) calc(var(--gutter) + var(--safe-right)) var(--space-5) calc(var(--gutter) + var(--safe-left));
    color: var(--text-muted);
  }

  /* Tabular figures only where numbers stand in a column: in names and row numbers they tear «1 500» apart */
  .view-xls .is-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  /* Phone layout: a cell of long text wraps, so that one long name does not push the numbers — the reason an
     estimate is opened — off the screen. A number never wraps */
  @media (max-width: 899px), (max-height: 499px) {
    .view-xls th:not(.is-num),
    .view-xls td:not(.is-num) {
      max-width: min(60vw, 18em);
      white-space: normal;
      overflow-wrap: break-word;
    }

    /* The row taken for the header stays one line high: a wrapping header cell broke a short title inside
       a word («Кол-» / «во») and doubled the sticky row */
    .view-xls thead th:not(.is-num),
    .view-xls tbody:first-child > tr:first-child > td {
      white-space: nowrap;
    }
  }

  .view-xls .is-total td,
  .view-xls .is-total th {
    border-bottom: 0;
    background: linear-gradient(var(--tile-file-bg), var(--tile-file-bg)) var(--surface);
    font-weight: var(--weight-bold);
  }

  /* Markdown document (docs/UI.md, section 9): the sheet of the editor.
     div#view-md.view-md > div.md-editor[.is-readonly] (made by modules/md-editor.js) > div.milkdown
                           > div.milkdown-top-bar (the toolbar, hidden while reading) + div.ProseMirror
     The library's stylesheet sits in the components layer (vendor/md-editor): here its variables
     --crepe-* get the tokens, and the sizes and the focus ring come back to the rules of the system. */
  .view-md {
    --crepe-color-background: transparent;
    --crepe-color-on-background: var(--text);
    --crepe-color-surface: var(--surface);
    --crepe-color-on-surface: var(--text);
    --crepe-color-outline: var(--border-strong);
    --crepe-color-primary: var(--accent);
    --crepe-color-hover: var(--state-hover);
    --crepe-color-selected: var(--accent-soft);
    --crepe-color-inline-area: var(--surface-sunken);
    --crepe-color-inline-code: var(--text);
    --crepe-base-font-size: var(--font-size-xl);
    --crepe-font-default: var(--font-sans);
    --crepe-font-title: var(--font-sans);
    --crepe-font-code: var(--font-mono);
    --crepe-shadow-1: var(--shadow-sm);
    --crepe-shadow-2: var(--shadow-md);
    color: var(--text);
  }

  .view-md .milkdown .ProseMirror {
    max-width: 800px;
    margin: 0 auto;
    padding: var(--space-5) calc(var(--gutter) + var(--safe-right)) calc(var(--space-8) + var(--safe-bottom)) calc(var(--gutter) + var(--safe-left));
    line-height: var(--leading-md);
    overflow-wrap: break-word;
  }

  .view-md .milkdown .ProseMirror h1,
  .view-md .milkdown .ProseMirror h2,
  .view-md .milkdown .ProseMirror h3,
  .view-md .milkdown .ProseMirror h4,
  .view-md .milkdown .ProseMirror h5,
  .view-md .milkdown .ProseMirror h6 {
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-tight);
  }

  /* The toolbar stands under the bar of the window and stays there while the sheet scrolls. */
  .view-md .milkdown .milkdown-top-bar {
    top: 0;
    z-index: var(--z-sticky);
  }

  .view-md .milkdown .milkdown-top-bar .top-bar-item,
  .view-md .milkdown .milkdown-top-bar .top-bar-heading-button {
    min-width: var(--hit-size);
    height: var(--hit-size);
  }

  /* Toolbar icons are the strokes of icons.svg, not the fills of the library. */
  .view-md .milkdown .milkdown-top-bar svg.icon {
    fill: none;
    stroke: currentColor;
  }

  /* The library takes the focus ring off everything inside it; buttons and fields get it back. */
  .view-md .milkdown :focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
  }

  .view-md .milkdown .ProseMirror:focus-visible {
    outline: none;
  }

  /* The address field of the link tooltip: 16px, or an iPhone zooms the page on focus. */
  .view-md .milkdown input {
    font-size: var(--font-size-xl);
  }

  .view-md .md-image {
    color: var(--text-muted);
    font-size: var(--font-size-md);
  }

  /* The library hides a table handle by opacity alone, and it goes on taking presses: a handle that
     is not shown takes none, and a document that is only read has no handles at all. */
  .view-md .milkdown .milkdown-table-block .handle[data-show="false"] {
    visibility: hidden;
  }

  .view-md .md-editor.is-readonly .milkdown-table-block .handle {
    display: none;
  }

  /* The compact layout: the buttons of a table handle are finger-sized. */
  @media (max-width: 899px), (max-height: 499px) {
    .view-md .milkdown .milkdown-table-block .line-handle .add-button,
    .view-md .milkdown .milkdown-table-block .cell-handle .button-group button {
      min-width: var(--hit-size);
      min-height: var(--hit-size);
    }
  }

  /* The address in the link tooltip is a line, not a link: it takes no presses (md-editor.js, rule 3). */
  .view-md .milkdown .link-preview .link-display {
    pointer-events: none;
  }

  /* Book (epub.js): the page fills the stage, the position and the two page buttons stand under it.
     div#view-epub.view-epub > div#view-epub-host.view-epub-host (the frame of epub.js)
                             + div.view-epub-nav > button#view-epub-prev + span#view-epub-loc.view-epub-loc + button#view-epub-next
     The book frame has its own document: the script gives it the colours of the theme (--surface, --text) */
  .view-epub {
    display: flex;
    flex-direction: column;
    height: 100%;
  }

  .view-epub-host {
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }

  .view-epub-host > iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
  }

  /* The first page of a book is being laid out: the sheet needs its size, so it stays in the flow,
     and the message of the stage («Загрузка…») lies over it instead of standing below the fold */
  .view-epub:not([hidden]) ~ .view-message:not([hidden]) {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    min-height: 0;
    background: var(--surface);
  }

  .view-epub-nav {
    display: flex;
    align-items: center;
    flex: none;
    gap: var(--space-2);
    padding: var(--space-1) calc(var(--gutter) - var(--space-3) + var(--safe-right)) calc(var(--space-1) + var(--safe-bottom)) calc(var(--gutter) - var(--space-3) + var(--safe-left));
    border-top: 1px solid var(--border);
  }

  .view-epub-loc {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Messages of the stage — no preview, failed preview, loading — and the audio card are the system empty state
     (.empty, 7.17) with this modifier: centred in the stage, a tile (or the spinner) instead of the door, and the
     actions stacked at full width below 600px.
     div.empty.view-message > span.entry-icon.entry-icon-lg[.type-…|.is-danger] | span.spinner.spinner-lg
                            + h3.empty-title + p.view-message-meta + p + div.empty-actions */
  .view-message {
    align-content: center;
    min-height: 100%;
    padding: var(--space-8) calc(var(--gutter) + var(--safe-right)) calc(var(--space-12) + var(--safe-bottom)) calc(var(--gutter) + var(--safe-left));
  }

  .view-message > .entry-icon,
  .view-message > .spinner {
    margin-bottom: var(--space-2);
  }

  /* The title may be a file name of any length */
  .view-message > .empty-title {
    max-width: 30em;
    overflow-wrap: anywhere;
  }

  .view-message > .view-message-meta {
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
  }

  /* The native audio control of the card */
  .view-audio > audio {
    width: 100%;
    max-width: 440px;
    margin-top: var(--space-4);
  }

  @media (max-width: 599px) {
    .view-message > .empty-actions {
      width: 100%;
    }

    .view-message > .empty-actions > .btn {
      flex-basis: 100%;
    }
  }

  @media (min-width: 600px) {
    .view-docx {
      max-width: 760px;
      margin: 0 auto;
    }

    .view-archive {
      max-width: 760px;
      margin: 0 auto;
    }

    .view-docx-xls {
      max-width: none;
    }

    /* The book page keeps the line length of a document; its buttons stay at the edges of the stage */
    .view-epub-host {
      align-self: center;
      width: 100%;
      max-width: 760px;
    }
  }

  @media (min-width: 900px) and (min-height: 500px) {
    .view-docx {
      max-width: 840px;
      padding: var(--space-12) calc(var(--space-12) + var(--space-2));
      border-radius: var(--radius-lg);
      background: var(--surface);
      box-shadow: var(--shadow-outline), var(--shadow-sm);
    }

    .view-docx-text {
      max-width: 800px;
      padding: var(--space-8) var(--space-10);
    }

    /* The list of an archive lies on a sheet of the paper canvas, like a document */
    .view-archive {
      max-width: 840px;
      padding: var(--space-6) var(--space-8) var(--space-8);
      border-radius: var(--radius-lg);
      background: var(--surface);
      box-shadow: var(--shadow-outline), var(--shadow-sm);
    }

    .view-docx-xls {
      max-width: none;
      padding: 0;
      border-radius: 0;
      box-shadow: none;
    }

    .view-xls-tabs {
      padding: 0 var(--space-3);
    }

    /* The book page is a sheet on the paper canvas, as wide as a text preview; the buttons stand under it */
    .view-epub {
      max-width: 800px;
      margin: 0 auto;
    }

    .view-epub-host {
      max-width: none;
      border-radius: var(--radius-lg);
      background: var(--surface);
      box-shadow: var(--shadow-outline), var(--shadow-sm);
    }

    .view-epub-nav {
      padding: var(--space-3) 0 0;
      border-top: 0;
    }

    .view-xls th:first-child,
    .view-xls td:first-child {
      padding-left: var(--space-6);
    }

    /* The first column stays in place */
    .view-xls tbody th,
    .view-xls tbody:first-child > tr > td:first-child,
    .view-xls thead th:first-child {
      position: sticky;
      left: 0;
    }

    /* Long text in it wraps instead of taking the width of the sheet. A table cell does not obey max-width,
       so the product wraps the content of the first cell of a row in div.view-xls-first and the cap is its */
    .view-xls-first {
      width: max-content;
      max-width: min(40vw, 20em);
      white-space: normal;
      overflow-wrap: break-word;
    }

    .view-xls thead th:first-child,
    .view-xls tbody:first-child > tr:first-child > td:first-child {
      z-index: calc(var(--z-sticky) + 1);
    }
  }
}

/* ==========================================================================
   File Vault — administration screen (#admin-screen). The fourth part of app.css.

   The screen replaces the storage screen: a sticky app bar (back to the storage, title, refresh), sticky tabs
   and one panel per tab. The document scrolls at every size; the content column is capped at 1152px.

   Phone layout (base): users are cards in one sheet, the primary action is the floating button.
   From 800px: users become a table (user, status, actions); from 1200px groups get a column of their own.
   Desktop layout: a 64px bar with labelled buttons, as high as the top bar of the storage screen.

   Breakpoints of this screen besides the system's (the document scrolls, so none of them asks for a height):
   800px — the users table and three node cards in a row; 900px — the log is a table; 1024px — transfers and
   the queue side by side; 1200px — the «Группы» column of the users table.
   ========================================================================== */

@layer layout {
  /* ------------------------------------------------------------------------
     Skeleton
     section#admin-screen.admin-screen > header.app-bar.app-bar-page + main.admin-main
       > div.admin-tabs[role=tablist] + div.admin-panel[data-admin-panel=users|groups|peers][hidden]
     The bar is the page bar of the system; the content stands on the page column (--page-pad-start / -end).
     ------------------------------------------------------------------------ */
  .admin-screen {
    --admin-bar-height: calc(var(--appbar-height) + var(--safe-top));
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
  }

  .admin-main {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
  }

  /* The tabs stay under the bar while a long tab scrolls. Tab labels start on the edge of the content.
     Below 600px the row is packed (8px tab padding, no gap) and the counters of users and groups are not shown:
     the four tabs, with the mark of a silent node, fit a 360px screen */
  .admin-tabs {
    --tab-pad: var(--space-2);
    position: sticky;
    top: var(--admin-bar-height);
    z-index: var(--z-bar);
    flex: none;
    gap: 0;
    padding: 0 calc(var(--page-pad-end) - var(--tab-pad)) 0 calc(var(--page-pad-start) - var(--tab-pad));
    background: var(--bg);
  }

  .admin-tabs .count {
    display: none;
  }

  .admin-panel {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
    gap: var(--space-4);
    padding: var(--space-4) var(--page-pad-end) calc(var(--space-8) + var(--safe-bottom)) var(--page-pad-start);
  }

  /* Room for the floating button under the last card */
  .admin-panel[data-admin-panel="users"] {
    padding-bottom: calc(var(--fab-size) + var(--space-8) + var(--safe-bottom));
  }

  @media (min-width: 600px) {
    .admin-tabs {
      --tab-pad: var(--space-3);
      gap: var(--space-1);
    }

    .admin-tabs .count {
      display: inline-grid;
    }

    .admin-panel[data-admin-panel="users"] {
      padding-bottom: calc(var(--space-8) + var(--safe-bottom));
    }
  }

  @media (min-width: 900px) and (min-height: 500px) {
    .admin-screen {
      --admin-bar-height: var(--topbar-height);
    }

    .admin-panel {
      gap: var(--space-5);
      padding-top: var(--space-5);
      padding-bottom: var(--space-12);
    }
  }

  /* ------------------------------------------------------------------------
     The upload panel over this screen (product): #upload-progress is a later sibling of the screen and stays on
     it while files are on their way; the script sets .has-upload-card on the screen while the panel is shown and
     writes the open tab into data-admin-view.
     Collapsed, phone layout: the bar stands beside the floating button only where the screen has one — the
     «Пользователи» tab below 600px; on the other tabs and from 600px it takes the corner itself.
     Every panel keeps scroll room for the collapsed bar or card under its last control.
     ------------------------------------------------------------------------ */
  .admin-screen.has-upload-card .admin-panel {
    padding-bottom: calc(var(--fab-size) + var(--space-8) + var(--safe-bottom));
  }

  @media (max-width: 899px), (max-height: 499px) {
    .admin-screen:not([hidden]) ~ .upload-panel.collapsed {
      right: calc(var(--gutter) + var(--safe-right));
      width: calc(100% - 2 * var(--gutter) - var(--safe-left) - var(--safe-right));
    }
  }

  @media (max-width: 599px) {
    .admin-screen[data-admin-view="users"]:not([hidden]) ~ .upload-panel.collapsed {
      right: calc(var(--gutter) + var(--fab-size) + var(--space-3) + var(--safe-right));
      width: calc(100% - 2 * var(--gutter) - var(--fab-size) - var(--space-3) - var(--safe-left) - var(--safe-right));
    }
  }

  /* Desktop: the collapsed card is its 52px header and 3px line, 24px above the bottom of the window */
  @media (min-width: 900px) and (min-height: 500px) {
    .admin-screen.has-upload-card .admin-panel {
      padding-bottom: calc(55px + var(--space-6) + var(--space-4));
    }
  }
}

@layer pages {
  /* ------------------------------------------------------------------------
     Tabs: the third label is shortened to «Узлы» below 600px so that all three fit a 360px screen
     button.admin-tab > span.tab-label[data-label] + span.count
     button.admin-tab > span (span.tab-label + span.admin-tab-more > span.tab-label) + span.tab-alert (a node needs attention)
     ------------------------------------------------------------------------ */
  .admin-tab-more {
    display: none;
  }

  @media (min-width: 600px) {
    .admin-tab-more {
      display: inline;
    }
  }

  /* ------------------------------------------------------------------------
     Panel pieces (the help line is .help, a titled block is .page-section, column titles are .list-head)
     div.admin-toolbar > div.input-group.admin-search (svg.icon + input[type=search] + button.btn.btn-icon[hidden])
                         + button.btn.btn-primary.admin-create
     The clear button is hidden while the field is empty. A query that finds nobody replaces the list and its
     column titles with div.empty inside the card.
     div.card.admin-card                                          a sheet that holds a list or a table
     p.admin-empty                                                the one line of a card whose list has no rows
                                                                  («Активных передач нет.», «Очередь пуста…»)
     ------------------------------------------------------------------------ */
  .admin-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
  }

  .admin-search {
    flex: 1;
  }

  .admin-search > .icon {
    --icon-size: 18;
  }

  /* Below 600px the same action is the floating button */
  .admin-create {
    display: none;
  }

  .admin-card {
    min-width: 0;
  }

  /* A card without rows says so in one muted line on the padding of a row */
  .admin-empty {
    padding: var(--space-4);
    color: var(--text-muted);
    font-size: var(--font-size-md);
    line-height: var(--leading-sm);
  }

  /* Column titles stick under the bar and the tabs (the document scrolls); they are the top of their card */
  .admin-card > .list-head {
    top: calc(var(--admin-bar-height) + var(--control-height));
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }

  @media (min-width: 600px) {
    .admin-empty {
      padding-right: var(--space-5);
      padding-left: var(--space-5);
    }

    .admin-search {
      flex: 0 1 320px;
    }

    .admin-create {
      display: inline-flex;
      margin-left: auto;
    }

    .admin-fab {
      display: none;
    }
  }

  /* ------------------------------------------------------------------------
     Users
     div.list-head.user-head > span.user-head-name + span.user-head-groups + span.user-head-status + span
     ul#user-list.item-list.user-list > li.item.user-item.active|pending [.is-self]
       span.avatar[.avatar-muted]
       div.user-id > div.item-title (span.user-name [> span.user-self] + span.badge) + span.badge.user-status + span.user-usage[.is-over]
       div.item-meta                                 @login · email
       div.user-groups > span.tag-chips.is-wrapping | span.user-nogroups
       div.item-actions > [button.btn.btn-secondary.btn-sm.user-cta] + div.popover-anchor.user-menu (> «⋯» + div.popover)
     Phone layout: a card — name, role and status flow as one wrapping line; «⋯» is pinned to the top right
     corner, on the axis of the avatar; the one action that matters («Включить», every inactive user has it)
     sits under the text.
     800–1199px: a table — user, status (as wide as «неактивен»), actions («Включить» and «⋯»); the groups run
     under the name across the row. Below 800px a name, a role and an email of real length do not fit a column
     next to those two, so the card stays.
     From 1200px: the groups are a column. Not earlier: the name column is then 5/9 of what the fixed columns
     leave — 404px at 1200px, room for a long name with its role badge and an address on one line, but only
     307px at 1024px, where the badge dropped under the name and the address broke in two.
     span.user-usage — «15 ГБ из 20 ГБ»: in the card it flows in the line of the role and the status; in the
     table it stands under the status, in its column (128px up to 1199px). The longest texts, «1023 МБ из 1000 ГБ»
     and «1023 КБ из 1023 МБ», measure about 117px and fit; a wider one wraps inside the column.
     ------------------------------------------------------------------------ */
  .user-item {
    position: relative;
    display: grid;
    grid-template-columns: 36px minmax(0, 1fr);
    align-items: start;
    column-gap: var(--space-3);
    row-gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
  }

  .user-item > .avatar {
    grid-row: 1 / span 2;
    margin-top: var(--space-1);
  }

  .user-id {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-2);
    grid-column: 2;
    min-width: 0;
    padding-right: calc(var(--icon-button-size) - var(--space-2));
  }

  .user-id > .item-title {
    display: contents;
  }

  /* The name takes the first line; role and status always start the second */
  .user-name {
    flex: 1 1 100%;
    min-width: 0;
  }

  .user-self {
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--weight-regular);
  }

  .user-usage {
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    white-space: nowrap;
  }

  .user-usage.is-over {
    color: var(--danger);
  }

  .user-item > .item-meta,
  .user-groups,
  .user-cta {
    grid-column: 2;
  }

  .user-groups {
    min-width: 0;
    margin-top: var(--space-1);
  }

  .user-nogroups {
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--chip-height);
  }

  .user-item > .item-actions {
    display: contents;
  }

  .user-cta {
    justify-self: start;
    margin-top: var(--space-2);
  }

  .user-menu {
    position: absolute;
    top: var(--space-3);
    right: var(--space-1);
  }

  /* A wider card keeps the side padding of a list row in a card */
  @media (min-width: 600px) {
    .user-item {
      padding-right: var(--space-5);
      padding-left: var(--space-5);
    }

    .user-menu {
      right: var(--space-2);
    }
  }

  @media (min-width: 800px) {
    .user-head,
    .user-item {
      grid-template-columns: 36px minmax(0, 1fr) 128px 140px;
      column-gap: var(--space-3);
      padding-right: var(--space-2);
      padding-left: var(--space-5);
    }

    .user-head {
      display: grid;
    }

    .user-head-name {
      grid-column: 1 / span 2;
    }

    .user-head-groups {
      display: none;
    }

    .user-item {
      align-items: center;
      row-gap: 0;
    }

    .user-item > .avatar {
      align-self: center;
      margin-top: 0;
    }

    .user-id {
      display: contents;
    }

    .user-id > .item-title {
      display: flex;
      grid-column: 2;
      grid-row: 1;
    }

    .user-name {
      flex: 0 1 auto;
    }

    /* In the table the line of the login and the address is one line: an address longer than the column is cut
       with an ellipsis (the product gives the whole line in a title) instead of making the row three lines tall */
    .user-item > .item-meta {
      grid-row: 2;
      overflow: hidden;
      overflow-wrap: normal;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* The break offered before «@» (for the card) would still break a nowrap line */
    .user-item > .item-meta > wbr {
      display: none;
    }

    .user-groups {
      grid-column: 2 / -1;
      grid-row: 3;
      margin-top: var(--space-2);
    }

    .user-status {
      grid-column: 3;
      grid-row: 1;
      justify-self: start;
      align-self: end;
    }

    .user-usage {
      grid-column: 3;
      grid-row: 2;
      justify-self: start;
      align-self: start;
      white-space: normal;
    }

    .user-item > .item-actions {
      display: flex;
      justify-content: flex-end;
      grid-column: 4;
      grid-row: 1 / span 2;
    }

    .user-cta {
      margin-top: 0;
    }

    .user-menu {
      position: relative;
      top: auto;
      right: auto;
    }
  }

  @media (min-width: 1200px) {
    .user-head,
    .user-item {
      grid-template-columns: 36px minmax(0, 5fr) minmax(0, 4fr) 148px 148px;
      column-gap: var(--space-4);
    }

    .user-head-groups {
      display: block;
    }

    /* Two lines of chips stretch both rows: the name and the login line stay together in the middle */
    .user-id > .item-title {
      align-self: end;
    }

    .user-item > .item-meta {
      align-self: start;
    }

    .user-groups {
      grid-column: 3;
      grid-row: 1 / span 2;
      margin-top: 0;
    }

    .user-status {
      grid-column: 4;
    }

    .user-usage {
      grid-column: 4;
    }

    .user-item > .item-actions {
      grid-column: 5;
    }
  }

  @media (min-width: 900px) and (min-height: 500px) {
    .user-item:last-child {
      border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    }
  }

  @media (min-width: 900px) and (min-height: 500px) and (hover: hover) {
    .user-item:hover {
      background: var(--state-hover);
    }
  }

  /* ------------------------------------------------------------------------
     Groups
     form#create-group-form.group-form > label.field + span.field-error[hidden] + button.btn.btn-primary
     ul#group-list.item-list.group-list > li.item.group-item > span.item-icon + div.item-main + div.item-actions
       div.item-main > div.item-title (> span.clamp-2: a name of any length takes two lines at most, the product
       gives the whole name in a title) + div.item-meta.clamp-2 (span.group-usage[.is-over] «95 ГБ из 100 ГБ»
       [«— переполнена»] + « · » + span.group-members). The used space leads the line of the members and is not a
       line of its own: a third line took the first group off the first screen of a low window (844x390).
       > button.btn.btn-ghost.btn-sm.btn-collapse (icon + span) ×2 — glyphs below 600px, labelled from 600px
     «Удалить» of a group with members: aria-disabled="true" + aria-describedby (the rule in the help line)
     ------------------------------------------------------------------------ */
  .group-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }

  /* The line about a name of spaces is a child of the form, not of the field: in the row of field and button it
     takes a row of its own under the field, and the button stays on the line of the input. 6px under the input,
     as inside a .field */
  .group-form > .field-error {
    margin-top: calc(6px - var(--space-3));
  }

  /* The glyph buttons end 4px from the edge of the card */
  .group-list > .group-item {
    padding-right: var(--space-1);
  }

  .group-usage.is-over {
    color: var(--danger);
  }

  @media (min-width: 600px) {
    .group-form {
      grid-template-columns: minmax(0, 360px) auto;
      align-items: end;
      justify-content: start;
    }

    .group-form > .field-error {
      grid-row: 2;
      grid-column: 1 / -1;
    }

    .group-list > .group-item {
      padding-right: var(--space-2);
    }
  }

  @media (min-width: 900px) and (min-height: 500px) and (hover: hover) {
    .group-list > .group-item:hover {
      background: var(--state-hover);
    }

    .group-list > .group-item:first-child {
      border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    }

    .group-list > .group-item:last-child {
      border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    }
  }

  /* ------------------------------------------------------------------------
     Nodes
     ul#peer-list.peer-list > li.card.group-item.peer-item[.is-offline]
       div.peer-head > span.item-icon.is-ok|is-danger + div.peer-title (span.peer-name + span.peer-url.mono) + span.user-badge.peer-*
       dl.peer-facts > div > dt + dd[.peer-fact-bad]
     ------------------------------------------------------------------------ */
  .peer-list {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }

  .peer-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
    gap: var(--space-3);
    min-width: 0;
    padding: var(--space-4);
  }

  .peer-head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--space-3);
  }

  .peer-head > .item-icon {
    grid-row: 1 / span 2;
  }

  .peer-title {
    display: contents;
  }

  .peer-name {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    min-width: 0;
    overflow: hidden;
    font-weight: var(--weight-bold);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* The status never gives up its padding to a long name */
  .peer-head > .user-badge {
    grid-column: 3;
    grid-row: 1;
    min-width: max-content;
  }

  /* The address is a value people copy: it wraps instead of being cut */
  .peer-url {
    grid-column: 2 / -1;
    grid-row: 2;
    align-self: start;
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    line-height: var(--leading-xs);
    overflow-wrap: anywhere;
  }

  .peer-facts {
    display: grid;
    gap: var(--space-1);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border);
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
  }

  .peer-facts > div {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
  }

  .peer-facts dt {
    flex: none;
    color: var(--text-muted);
  }

  .peer-facts dd {
    min-width: 0;
    text-align: right;
  }

  .peer-fact-bad {
    color: var(--danger);
    font-weight: var(--weight-medium);
  }

  /* One wide card per row: the facts stand side by side, each name over its value */
  @media (min-width: 600px) and (max-width: 799px) {
    .peer-facts {
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: var(--space-4);
    }

    .peer-facts > div {
      flex-direction: column;
      justify-content: flex-start;
      gap: 0;
    }

    .peer-facts dd {
      text-align: left;
    }
  }

  /* Three cards in a row from 800px: a card is then at least 243px wide, enough for an address on one line */
  @media (min-width: 800px) {
    .peer-list {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }

  /* Three narrow cards: the status moves under the name, the address runs under the whole head */
  @media (min-width: 800px) and (max-width: 1023px) {
    .peer-head {
      grid-template-columns: auto minmax(0, 1fr);
      row-gap: var(--space-1);
    }

    .peer-head > .user-badge {
      grid-column: 2;
      grid-row: 2;
      justify-self: start;
    }

    .peer-url {
      grid-column: 1 / -1;
      grid-row: 3;
      margin-top: var(--space-1);
    }
  }

  @media (min-width: 900px) and (min-height: 500px) {
    .peer-list {
      gap: var(--space-4);
    }

    .peer-item {
      padding: var(--space-5);
    }
  }

  /* ------------------------------------------------------------------------
     Transfers and the queue (side by side from 1024px)
     div.peers-pair > section.page-section ×2
     ul#transfer-list.transfer-list > li.group-item.transfer-item
       > div.transfer-top (span.transfer-name + span.user-badge.act-in|act-out) + div.bar.sync-transfer-bar
       + div.transfer-meta (span + span.transfer-pct)
     p.queue-summary > strong ; ul#sync-files-list.item-list.queue-list > li.item.group-item.queue-item
       > div.item-main > div.queue-name + div.item-meta ; div.queue-more > button
     ------------------------------------------------------------------------ */
  .peers-pair {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    gap: var(--space-4);
  }

  .transfer-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
  }

  .transfer-item + .transfer-item {
    border-top: 1px solid var(--border);
  }

  .transfer-top {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
  }

  .transfer-name,
  .queue-name {
    min-width: 0;
    overflow: hidden;
    font-weight: var(--weight-medium);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .transfer-name {
    flex: 1;
  }

  .transfer-meta {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
  }

  .transfer-pct {
    flex: none;
    color: var(--text);
    font-weight: var(--weight-medium);
    font-variant-numeric: tabular-nums;
  }

  .queue-summary {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border);
    color: var(--text-muted);
    font-size: var(--font-size-md);
    line-height: var(--leading-sm);
  }

  .queue-summary > strong {
    color: var(--text);
    font-weight: var(--weight-bold);
  }

  .queue-more {
    display: flex;
    justify-content: center;
    padding: var(--space-1) var(--space-2);
    border-top: 1px solid var(--border);
  }

  /* The same side padding as the rows of a list in a card */
  @media (min-width: 600px) {
    .transfer-item,
    .queue-summary {
      padding-right: var(--space-5);
      padding-left: var(--space-5);
    }
  }

  @media (min-width: 1024px) {
    .peers-pair {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: var(--space-4);
    }
  }

  /* ------------------------------------------------------------------------
     Audit journal: the fourth tab, and the history block of the properties window (docs/UI.md 9)
     div.admin-toolbar.audit-toolbar > div.input-group.admin-search.audit-search
                                       + button.btn.btn-secondary.audit-filters-toggle[aria-expanded] (text + span.count[hidden])
     div.audit-filters[hidden] > label.field.audit-filter (span + select) ×3 + label.field.audit-filter.audit-filter-date (span + input[type=date]) ×2
                                 + button.btn.btn-ghost.btn-accent.audit-reset[hidden]
     ul.item-list.audit-list > li.item.audit-item[data-audit-id]
       > span.item-icon + div.item-main + div.item-actions (button.btn.btn-icon[data-audit-toggle]) + div.audit-details[hidden]
       div.item-main > div.item-title (span.audit-action + span.audit-name + span.audit-more)
                       + div.item-meta.audit-within + div.item-meta.audit-meta (span ×4: time, author, channel, address)
       div.audit-details > dl.props-list + div.audit-nested
       div.audit-nested > button[data-audit-nested] + ul.audit-nested-list (li > span.audit-nested-name + span.audit-nested-path)
                          + p.form-error[data-audit-nested-error] + button[data-audit-nested-more]
     div.audit-state#audit-partial | #audit-failed        one line and one button in place of the list
     section.props-history > p.field-label + p.form-error + ul.item-list.is-tight.audit-history-list (li.item.audit-item without
                             actions and details) + p.context-help + button
     The search is always there; the other conditions open under it by «Фильтры» (the count on the button says how many
     are set), so the list starts on the first screen of a phone held either way. The filters wrap; a filter never gets
     narrower than its caption. The details stand under the row, on the full width of the card.
     ------------------------------------------------------------------------ */
  .audit-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-3);
    min-width: 0;
  }

  .admin-search.audit-search {
    flex: 1;
  }

  .audit-filters-toggle {
    flex: none;
  }

  .audit-filter {
    flex: 1 1 140px;
    min-width: 0;
  }

  .audit-filter-date {
    flex: 1 1 140px;
  }

  .audit-reset {
    flex: none;
  }

  .audit-item {
    flex-wrap: wrap;
  }

  .audit-action {
    font-weight: var(--weight-bold);
  }

  .audit-more {
    color: var(--text-muted);
    font-weight: 400;
  }

  .audit-meta {
    display: flex;
    flex-wrap: wrap;
    column-gap: var(--space-2);
  }

  /* The dot follows a part, so a line that wraps ends with it and the next one starts with text */
  .audit-meta > span:not(:last-child)::after {
    content: "·";
    margin-left: var(--space-2);
  }

  .audit-details {
    display: grid;
    flex: 1 0 100%;
    gap: var(--space-3);
    min-width: 0;
    padding-bottom: var(--space-2);
  }

  .audit-nested {
    display: grid;
    justify-items: start;
    gap: var(--space-2);
    min-width: 0;
  }

  .audit-nested-list {
    display: grid;
    justify-self: stretch;
    gap: var(--space-2);
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .audit-nested-list > li {
    display: grid;
    gap: 2px;
    min-width: 0;
    overflow-wrap: anywhere;
  }

  .audit-nested-path {
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--leading-sm);
  }

  .audit-state {
    display: grid;
    justify-items: start;
    gap: var(--space-3);
    padding: var(--space-4);
    color: var(--text-muted);
  }

  .props-history {
    display: grid;
    justify-items: start;
    gap: var(--space-2);
    min-width: 0;
  }

  .audit-history-list {
    justify-self: stretch;
  }

  @media (min-width: 600px) {
    .audit-state {
      padding-right: var(--space-5);
      padding-left: var(--space-5);
    }

    .audit-filter-date {
      flex: 0 1 170px;
    }

    .admin-search.audit-search {
      flex: 0 1 320px;
    }
  }

  /* ------------------------------------------------------------------------
     Log
     div.list-head.log-head > span ×5
     ul#activity-list.log-list > li.group-item.log-item
       > span.log-time + span.log-dir (> span.user-badge.act-*) + span.log-op + span.log-object + span.log-peer
     Phone layout: two lines — operation and time, then direction, object and node; the direction has the width
     of its longest word, so the objects start on one vertical. From 900px (any height): a table.
     ------------------------------------------------------------------------ */
  .log-item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--space-2);
    row-gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
  }

  .log-item + .log-item {
    border-top: 1px solid var(--border);
  }

  /* Line break between the two lines of a row */
  .log-item::before {
    content: "";
    flex: none;
    order: 2;
    width: 100%;
    height: 0;
  }

  .log-op {
    flex: 1;
    order: 0;
    min-width: 0;
    font-weight: var(--weight-medium);
    overflow-wrap: anywhere;
  }

  .log-time {
    flex: none;
    order: 1;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .log-dir {
    display: flex;
    flex: none;
    order: 3;
    width: 84px;
  }

  .log-object {
    flex: 1;
    order: 4;
    min-width: 0;
    overflow: hidden;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .log-peer {
    flex: none;
    order: 5;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    white-space: nowrap;
  }

  @media (min-width: 900px) {
    .log-head,
    .log-item {
      grid-template-columns: 148px 112px minmax(0, 1fr) minmax(0, 1.6fr) 96px;
      column-gap: var(--space-4);
      padding: 0 var(--space-5);
    }

    .log-head {
      display: grid;
    }

    .log-item {
      display: grid;
      min-height: var(--row-height);
    }

    .log-item::before {
      content: none;
    }

    .log-time,
    .log-dir,
    .log-op,
    .log-object,
    .log-peer {
      order: 0;
    }

    .log-dir {
      width: auto;
    }

    .log-op {
      overflow: hidden;
      overflow-wrap: normal;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .log-object {
      color: var(--text);
      font-size: inherit;
    }

    /* A node id longer than its column is cut with an ellipsis; the product gives the whole id in a title */
    .log-peer {
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
}
