/* Linked from every page's <head> with media (max-width: 900px), so a wide
   window never applies a rule from here; the rule that hides the shell's
   leftovers in a wide window is therefore in native.js, next to the code
   that creates them. */

/* =========================================================
   TABU ON A PHONE: THE SHELL
   Loaded by assets/native.js on a phone: inside the iPhone app and in a
   browser at the site's 900px breakpoint (decided 17 Sep 2026: one look).
   The class is html.app: "the app's chrome", wherever it is drawn. On a
   browser env(safe-area-inset-top) is 0 and every inset below collapses.

   The site's own phone layout (style.css, MOBILE APP SHELL block) already is
   the app: bottom bar, thin top bar, Лента/Свайп switch. These rules are what
   an app does that a website must not:
     1. the page starts under the status bar, so the fixed chrome grows by the
        top safe-area inset and the body's reserved strip grows with it;
     2. the chrome slides away on the way down and comes back on the way up
        (native.js toggles html.chrome-hidden);
     3. the chrome is not text: no long-press callout, no selection, no grey
        tap flash, no rubber band on the page itself.
   ========================================================= */

/* ---- 1. status bar: everything fixed to the top moves down by the inset ---- */
html.app header{
  height:calc(56px + env(safe-area-inset-top));
  padding-top:env(safe-area-inset-top);
}
html.app header.search-open .search-suggest{top:calc(56px + env(safe-area-inset-top));}
/* the site draws a hairline under the header once the page scrolls (is-stuck);
   in the app the header is the app's own bar, it has no edge */
html.app header{border-bottom-color:transparent !important; box-shadow:none !important;}
html.app .home-mode-bar{border:0; box-shadow:none;}
/* the tab bar is the navigation; the footer's links live in Профиль → Настройки */
html.app footer{display:none !important;}

/* ---- 1b. Лента | Свайп lives IN the top bar, centred between the logo and the
   icons (TikTok), not on a row of its own under it. The site's bar keeps its
   place in the DOM and its id (swipe.js measures it): it becomes a 0px-tall
   fixed strip at the header's bottom edge, and its content is lifted 56px up
   into the header. offsetHeight 0 means swipe.js insets nothing, so the map
   runs right up under the header. The page stops reserving the bar's 58px. */
html.app body{padding-top:calc(56px + env(safe-area-inset-top)) !important;}
html.app .home-mode-bar{
  top:calc(56px + env(safe-area-inset-top)); height:0; padding:0; overflow:visible;
  background:transparent; pointer-events:none; z-index:31;
}
html.app .home-mode-bar > .page-w{
  position:absolute; left:0; right:0; top:-56px; height:56px;
  display:flex; align-items:center; justify-content:center; flex-wrap:nowrap;
  padding:0; pointer-events:none;
}
html.app .home-mode-switch{pointer-events:auto;}
/* the Карта search chip has no row of its own any more; it drops under the header */
html.app .home-search-chip{position:absolute; top:64px; left:16px; right:16px; margin:0; pointer-events:auto;}
/* the open search field takes the header's middle */
html.app header.search-open ~ .home-mode-bar{display:none;}
/* the words are on the header's ground in both modes; nothing to paint under them */
html.app .swipe-root::before{display:none;}

/* ---- 1c. Свайп: the map runs to the very top, under a see-through header.
   swipe.js puts the root under the header with an inline top; the app overrides
   it. The header keeps its height (the tap targets and the switch stay put) and
   loses its ground: a fade from the page colour to nothing, in either theme. */
html.app[data-home-mode="map"] .swipe-root{top:0 !important;}
/* the pill row (map credit, «Сначала рядом») sat on the old switch row; now
   it hangs just under the header, on the map */
html.app[data-home-mode="map"] .swipe-attrib,
html.app[data-home-mode="map"] .swipe-near{
  top:calc(56px + env(safe-area-inset-top) + 20px) !important;
}
html.app[data-home-mode="map"] .swipe-attrib{right:12px !important;}
html.app[data-home-mode="map"] .swipe-near{left:12px !important; right:auto !important; transform:translateY(-50%);}
/* an expanded post opens under the header, not under it and out of reach:
   its grabber stays where a thumb can pull it back down */
html.app .swipe-root{--swipe-expanded-top:calc(56px + env(safe-area-inset-top));}
html.app[data-home-mode="map"] header{
  background:linear-gradient(
    color-mix(in srgb, var(--bg) 92%, transparent),
    color-mix(in srgb, var(--bg) 55%, transparent) 70%,
    transparent);
  /* the header keeps a 1px transparent border; without this the gradient,
     sized to the padding box, repeats its first row into that 1px and draws a
     hairline of page colour across the map */
  background-origin:border-box; background-repeat:no-repeat;
  border-bottom-color:transparent !important;
}

/* ---- 2. chrome that hides on scroll ---- */
html.app header,
html.app .home-mode-bar,
html.app .bottom-nav{
  transition:transform .34s cubic-bezier(.22,.61,.36,1), opacity .28s ease, background-color .34s ease, border-color .34s ease;
}
/* Not on the header. will-change:transform makes it the containing block of
   its fixed child, the search suggestions (style.css pins them top:56px,
   bottom:0 inside the header): they measured 10px tall. The header must have
   no transform, filter, contain or perspective at rest; while it is hidden
   the suggestions cannot be open (native.js blocks the hide then). */
html.app .home-mode-bar,
html.app .bottom-nav{will-change:transform;}
/* The top bar stays. What goes is its ground and the wordmark: the switch,
   the search and the bell keep their places over the content, which shows
   through a frosted wash (as inside a post, section 23). The backdrop filter
   makes the header a containing block, which would clip the search
   suggestions; native.js shows the chrome whenever the search opens, so the
   two states never meet. */
html.app .logo{transition:opacity .28s ease;}
html.app.chrome-hidden .logo{opacity:0;}
html.app.chrome-hidden header{background:transparent;}
/* The frost (native.js puts .app-frost first in every header): a plain blur,
   no tint in either theme (decided 18 Sep 2026), behind the bar's contents,
   within the bar: never into the content.
   A single masked blur cannot fade: the mask only thins the blurred copy, so
   the smeared letters end on a line (seen on the phone). Instead four layers,
   each blurring more than the one below and ending higher up: under the
   clock all four stack, and down the bar's row (its last 56px) they drop out
   one by one, so the radius itself falls to nothing at the edge. --frost-blur
   is the strength of the top layer: 5px on the tab roots, 10px pushed. */
html.app .app-frost{
  position:absolute; left:0; right:0; top:0; bottom:0; z-index:-1; pointer-events:none;
  background:transparent; --frost-blur:5px;
}
html.app .app-frost > i{
  position:absolute; left:0; right:0; top:0; bottom:0;
  opacity:0; transition:opacity .34s ease;
}
html.app .app-frost > i:nth-child(1){
  -webkit-backdrop-filter:blur(calc(var(--frost-blur) * .15)); backdrop-filter:blur(calc(var(--frost-blur) * .15));
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 40px), transparent);
  mask-image:linear-gradient(to bottom, #000 calc(100% - 40px), transparent);
}
html.app .app-frost > i:nth-child(2){
  -webkit-backdrop-filter:blur(calc(var(--frost-blur) * .35)); backdrop-filter:blur(calc(var(--frost-blur) * .35));
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 48px), transparent calc(100% - 12px));
  mask-image:linear-gradient(to bottom, #000 calc(100% - 48px), transparent calc(100% - 12px));
}
html.app .app-frost > i:nth-child(3){
  -webkit-backdrop-filter:blur(calc(var(--frost-blur) * .6)); backdrop-filter:blur(calc(var(--frost-blur) * .6));
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 52px), transparent calc(100% - 26px));
  mask-image:linear-gradient(to bottom, #000 calc(100% - 52px), transparent calc(100% - 26px));
}
html.app .app-frost > i:nth-child(4){
  -webkit-backdrop-filter:blur(calc(var(--frost-blur) * .85)); backdrop-filter:blur(calc(var(--frost-blur) * .85));
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 56px), transparent calc(100% - 40px));
  mask-image:linear-gradient(to bottom, #000 calc(100% - 56px), transparent calc(100% - 40px));
}
html.app.chrome-hidden .app-frost > i{opacity:1;}
/* the frost needs the bar to carry no view-transition-name in the shell: see
   section 12 */
/* the tab bar never leaves (TikTok, Instagram): on scroll its ground turns
   frosted like the top bar's, the five items stay where the thumb knows them.
   Its frost is one even layer with the page colour at 60% over it (the top
   bar is blur only, decided 18 Sep 2026). */
html.app.chrome-hidden .bottom-nav{transform:none; opacity:1; background:transparent; border-top-color:transparent;}
html.app .bottom-nav .app-frost{background:color-mix(in srgb, var(--bg) 60%, transparent);}
html.app .bottom-nav .app-frost > i:first-child{
  -webkit-backdrop-filter:blur(var(--frost-blur)); backdrop-filter:blur(var(--frost-blur));
  -webkit-mask-image:none; mask-image:none;
}
html.app .bottom-nav .app-frost > i:not(:first-child){display:none;}
html.app.chrome-hidden .bottom-nav .app-frost > i{opacity:1;}
@media (prefers-reduced-motion: reduce){
  html.app header, html.app .home-mode-bar, html.app .bottom-nav{transition:none;}
}

/* the range reaches the screen's bottom edge, behind the tab bar (which paints
   its own ground), so when the bar slides away the mountains are still there
   instead of ending on a flat line 61px up */
html.app .range{bottom:0;}

/* ---- 2b. the app never scrolls sideways. A website that can be dragged
   left and right is a website; in the shell the page clips at its edges
   instead (clip, not hidden: no scroll container, sticky and fixed children
   unaffected). Rows that scroll on purpose (the filter chips) scroll inside
   their own box. Found 18 Sep 2026 when the feed overflowed a 402px phone. */
html.shell, html.shell body{overflow-x:clip;}

/* ---- 2c. no field under 16px: iOS zooms the page into any smaller input
   the moment it takes focus (the type search in the new-place dialog did) ---- */
html.app .tabu-modal-choice-search{font-size:16px !important;}

/* ---- 3. the chrome is not text ---- */
html.app{-webkit-text-size-adjust:100%;}
/* the page keeps its rubber band: pull-to-refresh (tabuPullToRefresh) rides on it */
html.app body{-webkit-tap-highlight-color:transparent;}
html.shell header,
html.shell .home-mode-bar,
html.shell .bottom-nav,
html.shell .vote-col,
html.shell .qactions{
  -webkit-user-select:none; user-select:none; -webkit-touch-callout:none;
}
html.app img{-webkit-touch-callout:none;}

/* ---- 4. a page title in the top bar, centred, on every page but the home
   (native.js writes .app-title; the home has the Лента | Свайп switch there) ---- */
html.app .app-title{
  position:absolute; left:0; right:0; top:env(safe-area-inset-top); height:56px;
  display:flex; align-items:center; justify-content:center;
  font-size:15px; font-weight:600; color:var(--text-strong); pointer-events:none;
}
html.app header.search-open .app-title{display:none;}

/* ---- 5. Карта: the map runs to the top under a fading header, like Свайп.
   The page sizes .map-wrap from --header-h and pads the body for the header;
   here the wrap climbs back up by that much and grows the same. The controls
   the page pins to the wrap's top move under the header. ---- */
html.app.page-map .map-wrap{
  /* down to the tab bar's top edge: 61px is the bar (52px of items + 4px
     above and below + its hairline) plus the home-indicator inset. The page's
     own 68px left a 7px strip of page colour between the white places sheet
     and the bar (18 Sep 2026). */
  height:calc(100dvh - 61px - env(safe-area-inset-bottom)) !important;
  margin-top:calc(-56px - env(safe-area-inset-top));
}
html.app.page-map header{
  background:linear-gradient(
    color-mix(in srgb, var(--bg) 92%, transparent),
    color-mix(in srgb, var(--bg) 55%, transparent) 70%,
    transparent);
  /* the header keeps a 1px transparent border; without this the gradient,
     sized to the padding box, repeats its first row into that 1px and draws a
     hairline of page colour across the map */
  background-origin:border-box; background-repeat:no-repeat;
  border-bottom-color:transparent !important;
}
/* Карта does not scroll: the map is the screen. The document is clipped (no
   rubber band, nothing to pull), and the wrap clips too instead of hiding:
   an overflow:hidden box is still scrollable by script, and WebKit scrolls
   it to reveal the caret when the answer field in the sheet takes focus,
   which shoved the whole map down under the keyboard (18 Sep 2026). Clip
   cannot scroll. The sheets scroll inside themselves as before. */
html.app.page-map, html.app.page-map body{height:100dvh; overflow:clip; overscroll-behavior:none;}
html.app.page-map .map-wrap{overflow:clip;}
html.app.page-map .map-toolbar{top:calc(66px + env(safe-area-inset-top)) !important;}
html.app.page-map .map-chips{top:calc(118px + env(safe-area-inset-top)) !important;}
html.app.page-map .map-wrap .leaflet-top{top:calc(56px + env(safe-area-inset-top));}

/* ---- 6. Табы: the page lays itself out for a phone (communities.html,
   its @media(max-width:900px) block: search, chips, «Твои табы», the hot
   cards, the list, «Создать таб»). The shell only drops the footer; the
   title is the header's (section 16). ---- */
html.app.page-communities footer, html.app.page-rating footer, html.app.page-campus-tabs footer{display:none;}

/* ---- 7. Вход and Регистрация: a screen, not a web form. No tab bar, no
   footer, no headline and figures, no labels shouting over the fields: the
   wordmark large and centred, the fields with their placeholders, one button,
   one line to switch. A back chevron top-left (native.js) leaves the screen. ---- */
html.app.page-login body, html.app.page-register body{padding-top:0 !important; padding-bottom:0 !important;}
html.app.page-login .bottom-nav, html.app.page-register .bottom-nav,
html.app.page-login .auth-brand, html.app.page-register .auth-brand,
html.app.page-login .card h2, html.app.page-register .card h2,
html.app.page-login .topbar-right, html.app.page-register .topbar-right,
html.app.page-login .topbar-search, html.app.page-register .topbar-search,
html.app.page-login .nav-rail-toggle, html.app.page-register .nav-rail-toggle,
html.app.page-login .app-title, html.app.page-register .app-title{display:none !important;}
html.app.page-login header, html.app.page-register header{
  position:static; height:auto; display:flex; justify-content:center;
  padding:calc(env(safe-area-inset-top) + 72px) 0 28px; background:transparent;
  transform:none !important;
}
/* a static header is no containing block: its frost (2) spread over the whole
   first screen and blurred the form once the page scrolled (chrome-hidden) */
html.app.page-login header .app-frost, html.app.page-register header .app-frost{display:none;}
html.app.page-login .topbar-left, html.app.page-register .topbar-left{flex:0 0 auto; display:flex; justify-content:center;}
html.app.page-login .logo, html.app.page-register .logo{transform:scale(1.8); transform-origin:center;}
html.app.page-login .auth-main, html.app.page-register .auth-main{padding:0 20px 40px;}
html.app.page-login .auth-shell, html.app.page-register .auth-shell{display:block; max-width:420px; margin:0 auto; padding:0;}
html.app.page-login .card form > label:not(.remember-row),
html.app.page-register .card form > label:not(.remember-row){
  position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap;
}
html.app.page-login .card input[type=text], html.app.page-login .card input[type=email], html.app.page-login .card input[type=password],
html.app.page-register .card input[type=text], html.app.page-register .card input[type=email], html.app.page-register .card input[type=password]{
  min-height:52px; font-size:16px; border-radius:14px; margin-bottom:10px;
}
html.app.page-login .pw-field, html.app.page-register .pw-field{margin-bottom:10px;}
/* 16+: the gender, the same height as the fields (the date is a text field, above) */
html.app.page-register .gender-pick{margin-bottom:10px; gap:6px;}
html.app.page-register .gender-pick button{min-height:52px; border-radius:14px; font-size:15px; padding:0 10px;}
/* under 360 the three take 13px words and 6px sides: «Не указывать» whole,
   and room around every word (sweep, 14 Oct 2026) */
@media(max-width:359px){ html.app.page-register .gender-pick button{font-size:var(--fs-meta); padding:0 6px;} }
html.app.page-login .pw-field input, html.app.page-register .pw-field input{margin-bottom:0;}
html.app.page-login .remember-row, html.app.page-register .remember-row{margin:6px 4px 14px; color:var(--muted);}
/* login: «Запомнить меня» and «Забыли пароль?» share one row, the row takes the margin */
html.app.page-login .login-opts{margin:6px 4px 14px;}
html.app.page-login .login-opts .remember-row{margin:0;}
html.app.page-login .btn-primary.btn-block, html.app.page-register .btn-primary.btn-block{
  min-height:52px; border-radius:14px; font-size:var(--fs-ui); margin-top:6px;
}
html.app.page-login .switch, html.app.page-register .switch{margin-top:22px; font-size:var(--fs-meta);}
/* the back chevron */
html.app .app-back{
  position:fixed; left:6px; top:calc(env(safe-area-inset-top) + 6px); z-index:40;
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  color:var(--text); background:none; border:0; padding:0;
}

/* ---- 8. Пост: the mockup's screen. Top bar = back chevron, the theme's name,
   share (native.js builds the three; the site's logo, bell and «Назад» row go).
   The answer box stops being a form at the end of the page and becomes the
   comment bar every social app has: pinned to the bottom in place of the tab
   bar, a pill that grows as you type, place and photo as icons, a round send
   button. Everything the box does (login round-trip, place, photo, submit) is
   still the site's own tabuAnswerComposer; only the clothes change. ---- */
html.app.page-question .bottom-nav,
html.app.page-question #breadcrumb,
html.app.page-question .q-back-row,
html.app.page-question .topbar-left,
html.app.page-question .topbar-right,
html.app.page-question .topbar-search,
html.app.page-question .q-compose > label,
html.app.page-question .composer-hint{display:none !important;}
html.app.page-question body{padding-bottom:calc(76px + env(safe-area-inset-bottom)) !important;}
html.app.page-question .app-title{max-width:60%; margin:0 auto; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
html.app .app-share{
  position:fixed; right:6px; top:calc(env(safe-area-inset-top) + 6px); z-index:40;
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  color:var(--text); background:none; border:0; padding:0;
}
html.app.chrome-hidden .app-back, html.app.chrome-hidden .app-share{transform:translateY(-120px);}
html.app .app-back, html.app .app-share{transition:transform .22s cubic-bezier(.2,.7,.2,1);}
/* a pushed page's title goes with its back and share: left alone it floated
   over the post's text, crisp and unreadable (sweep, 14 Oct 2026) */
html.app.has-back .app-title{transition:opacity .22s ease;}
html.app.has-back.chrome-hidden .app-title{opacity:0;}

/* the bar */
html.app.page-question .q-compose{
  position:fixed; left:0; right:0; bottom:0; z-index:20; margin:0;
  padding:8px 12px calc(8px + env(safe-area-inset-bottom));
  background:var(--bg); border-top:1px solid var(--border);
}
/* the box inside it is style.css's .cbox (tabuAnswerComposer), one frame */

/* 8b. «Написать продавцу» (CLOUD-15, the chief's review): on a Барахолка post
   that is not yours and not sold the bottom bar is the button and its one
   line, as in the approved mockup, and the answer box steps aside. It comes
   back (html.q-answering, question.html) once the person replies to an
   answer, taps the post's answers action or «Ответов пока нет»; the button
   then sits under the photo, as on a desktop. The seller's post and a sold
   one have no .q-market: their bar is the answer box as before. */
html.app.page-question:not(.q-answering) .q-market{
  position:fixed; left:0; right:0; bottom:0; z-index:20; margin:0; max-width:none; gap:10px;
  padding:12px 16px calc(12px + env(safe-area-inset-bottom));
  background:var(--bg); border-top:1px solid var(--border);
}
html.app.page-question:not(.q-answering) body:has(.q-market) .q-compose{display:none;}
html.app.page-question:not(.q-answering) body:has(.q-market){padding-bottom:calc(116px + env(safe-area-inset-bottom)) !important;}

/* ---- 9. screens that are not tabs: the chevron replaces the logo, and the
   web furniture the header now carries (breadcrumbs, a repeated h1) goes ---- */
html.app.has-back .topbar-left{visibility:hidden;}
/* not on Вход and Регистрация: the wordmark IS the screen there (section 7,
   large and centred, the header is static); the chevron sits in the corner
   beside it. Same specificity as the line above, so it has to follow it. */
html.app.page-login .topbar-left, html.app.page-register .topbar-left{visibility:visible;}
html.app .breadcrumb,
html.app.page-leaderboard .lb-head,
html.app.page-notifications .notif-shell > h1,
html.app.page-settings .settings-col > h1,
html.app.page-settings .settings-sub{display:none !important;}
html.app.page-notifications .notif-shell,
html.app.page-leaderboard .lb-shell{padding-top:8px;}
/* Новый пост is already a full-screen flow on phones; it only needs the status bar */
html.app.page-ask .flow{padding-top:env(safe-area-inset-top);}

/* ---- 11. the keyboard. The WebView does not resize (Keyboard resize:none in
   the shell), so the page stays full height and its background fills in
   behind the keyboard's rounded corners. native.js publishes the keyboard's
   height as --kb and sets html.kb-open from keyboardWillShow, before the
   keyboard has moved, so these transitions run alongside its slide. ---- */
html.app{--kb:0px;}
html.app .q-compose{transition:bottom var(--kb-t), padding var(--kb-t);}
html.app.kb-open .q-compose{bottom:var(--kb); padding-bottom:8px;}
/* the tab bar has no job while typing; it steps out with the same timing */
html.app.kb-open .bottom-nav{transform:translateY(100%);}
/* Свайп's open thread: its composer rides up, the thread keeps scrolling behind it */
html.app .swipe-thread-foot{transition:transform var(--kb-t);}
html.app.kb-open .swipe-thread-foot{transform:translateY(calc(-1 * var(--kb) + env(safe-area-inset-bottom)));}
html.app.kb-open .swipe-thread-scroll{padding-bottom:calc(var(--kb) + 14px);}
/* the reply box that opens inside a feed card is in the flow: the page scrolls
   it into view; the bar over it just needs room */
html.app.kb-open body{padding-bottom:calc(var(--kb) + 16px) !important;}

/* The keyboard is translucent: whatever the page draws under it shows
   through. A native app shows its plain ground there, so does this one: a
   fixed panel the keyboard's height, in the page colour, above the page's
   content and below the bars that ride up (native.js adds .app-kb-backdrop).
   Each riding bar also paints its own ground down to the keyboard, so nothing
   peeks between the bar and the panel while they move. */
/* Everything that rides the keyboard moves on the keyboard's own clock: the
   shell writes the duration UIKit announces into --kb-dur on keyboardWillShow
   (the hide uses the same), and --kb-ease is UIKit's keyboard curve. With a
   fixed .25s and our own curve the strip and the bars trailed the keyboard on
   the way down (18 Sep 2026). */
html.app{--kb-ease:cubic-bezier(.38,.7,.125,1);}
/* Both ways on the keyboard's own duration and curve. On a pull down the
   page flips first and the keyboard follows (native.js), so matching timing
   is the closest match; a dismissal that comes from UIKit (Готово, a tap
   elsewhere) reaches the page a frame or two late and is slightly shorter. */
html.app{--kb-t:calc(var(--kb-dur,.25s) * .85) var(--kb-ease);}
html.app.kb-open{--kb-t:var(--kb-dur,.25s) var(--kb-ease);}
html.app .app-kb-backdrop{
  position:fixed; left:0; right:0; bottom:0; height:0; z-index:14;
  background:var(--bg); pointer-events:none;
  transition:height var(--kb-t);
}
html.app.kb-open .app-kb-backdrop{height:var(--kb);}
html.app.kb-open .q-compose::after,
html.app.kb-open .swipe-thread-foot::after,
html.app.kb-open .flow-foot::after,
html.app.kb-open.page-map .place-panel::after{
  content:""; position:absolute; left:0; right:0; top:100%; height:var(--kb); background:var(--bg);
}
html.app.kb-open .swipe-thread-foot{background:var(--bg); position:relative;}

/* Карта: the results sheet is pinned to the wrap's bottom, which sits 61px +
   inset above the screen's edge; it rises so it clears the keyboard and stops
   growing before it reaches the search box under the header */
html.app.page-map .place-panel{transition:transform .22s ease, bottom var(--kb-t), max-height var(--kb-t), visibility 0s;}
html.app.kb-open.page-map .place-panel{
  bottom:calc(var(--kb) - 61px - env(safe-area-inset-bottom));
  max-height:calc(100dvh - var(--kb) - 120px - env(safe-area-inset-top));
}
html.app.kb-open.page-map .place-panel-foot{padding-bottom:12px;}

/* Новый пост: its own «Далее» bar rides up too */
html.app .flow-foot{transition:transform var(--kb-t); position:relative;}
html.app.kb-open .flow-foot{transform:translateY(calc(-1 * var(--kb) + env(safe-area-inset-bottom)));}
html.app.kb-open .flow-body{padding-bottom:calc(var(--kb) + 16px);}

/* the search suggestions fill the screen under the bar; with the keyboard up
   they end at its top edge, so the last row can be reached and the list
   scrolls inside itself */
html.app .search-suggest{transition:bottom var(--kb-t);}
html.app.kb-open .search-suggest{bottom:var(--kb); padding-bottom:6px;}

/* the place sheet is the whole screen on a phone (style.css); with the
   keyboard up the sheet ends at the keyboard. Its list and its map step are
   flex:1 inside it, so they shrink and the list scrolls, the map's «Готово»
   stays in view. The padding is the sheet's own ground, the page colour,
   behind the keyboard's translucent corners. */
html.app .tabu-sheet{transition:padding-bottom var(--kb-t);}
html.app.kb-open .tabu-sheet{padding-bottom:var(--kb);}

/* a dialog is centred on the page; with the keyboard up it sits at the top
   instead, and a tall one scrolls within what is left above the keyboard.
   The 20px is the backdrop's own padding, kept on all four sides. */
html.app.kb-open .tabu-modal-backdrop{
  align-items:flex-start; overflow-y:auto; overscroll-behavior:contain;
  padding-top:calc(20px + env(safe-area-inset-top)); padding-bottom:calc(var(--kb) + 20px);
}

/* ---- 12. motion between pages. Every tab and every post is its own page;
   the browser starts a transition between one and the next (cross-document
   view transitions, opted in below). The chrome is named, so the top bar
   and the tab bar stay put while the page under them changes. A post opened
   from a list slides in from the right over the list (html.vt-push on the
   new page, set in <head> from the tag native.js writes at the tap) and the
   back chevron slides it out again (vt-pop). The system's own back gesture
   already animates, so <head> skips the transition on back_forward. This
   sheet only applies on a phone (the head link's media), so the desktop
   opts into none of it.
   Tab to tab is a cut, not a fade: the transition's only job there is to
   hold the old page's frame on screen until the new document is fully
   drawn (the head's blocking="render" script and rel=expect target make
   sure of that), then swap straight to it, chrome held still. A crossfade
   would have shown the half-drawn new page bleeding through. ---- */
@view-transition{navigation:auto;}
@media (prefers-reduced-motion: reduce){
  @view-transition{navigation:none;}
}
/* The chrome is named only while a push or pop is under way (html.vt-push /
   html.vt-pop, set on the click and by the head script of the next page,
   cleared by native.js once the transition is over). A named element is
   rendered as its own group, and the frost's backdrop-filter inside it then
   has nothing behind it to blur: with the names always on, the bars blurred
   only after a navigation (the class lingered) and showed a flat wash after
   a reload. Found 18 Sep 2026; measured on the simulator: bar-row sharpness
   87 named, 31 unnamed. */
html.app.vt-push header, html.app.vt-pop header{view-transition-name:tabu-header;}
html.app.vt-push .app-back, html.app.vt-pop .app-back{view-transition-name:tabu-back;}
html.app.vt-push .app-share, html.app.vt-pop .app-share{view-transition-name:tabu-share;}
/* the tab bar has no name at all: it leaves with the page on a push and a
   pop (below), and every other transition is a 0s cut */
/* In the shell the stack pushes natively; nothing is ever named. After the
   names: same specificity, source order decides. */
html.shell header, html.shell .app-back, html.shell .app-share{view-transition-name:none;}
/* Вход and Регистрация draw the header as a big centred wordmark: it is part
   of the screen there, not a bar that could morph out of one */
html.app.page-login header, html.app.page-register header{view-transition-name:none;}
/* on a push the tab bar leaves with the list (iOS hides it with the screen
   that is pushed away); on a pop it is already in place under the post */
/* a page whose bars have scrolled away keeps them out of the transition:
   otherwise the other page's visible bar morphs into this page's hidden one
   and the bar is seen sliding off, a flash on every back */
html.app.chrome-hidden header, html.app.chrome-hidden .bottom-nav,
html.app.chrome-hidden .app-back, html.app.chrome-hidden .app-share{view-transition-name:none;}

/* the default, tab to tab and everything else: a cut, no fade */
html.app::view-transition-group(*){animation-duration:0s;}
html.app::view-transition-old(root), html.app::view-transition-new(root){animation-duration:0s;}

/* push: the post comes in from the right, the list drifts left under it;
   pop: the post, on top, leaves to the right, the list drifts back */
@keyframes tabu-vt-in{from{transform:translateX(100%);} to{transform:translateX(0);}}
@keyframes tabu-vt-out{from{transform:translateX(0);} to{transform:translateX(100%);}}
@keyframes tabu-vt-drift{from{transform:translateX(0);} to{transform:translateX(-18%);}}
@keyframes tabu-vt-undrift{from{transform:translateX(-18%);} to{transform:translateX(0);}}
html.app.vt-push::view-transition-old(root), html.app.vt-push::view-transition-new(root),
html.app.vt-pop::view-transition-old(root), html.app.vt-pop::view-transition-new(root){
  mix-blend-mode:normal; animation-duration:.26s; animation-timing-function:cubic-bezier(.2,.7,.2,1); animation-fill-mode:both;
}
html.app.vt-push::view-transition-old(root){animation-name:tabu-vt-drift;}
html.app.vt-push::view-transition-new(root){animation-name:tabu-vt-in;}
html.app.vt-pop::view-transition-old(root){animation-name:tabu-vt-out; z-index:1;}
html.app.vt-pop::view-transition-new(root){animation-name:tabu-vt-undrift;}

/* ---- 13. overlays under the status bar. The place sheet is the whole screen
   on a phone (style.css), and its head, with the × or the back arrow, sat
   under the status bar on a device: it grows by the inset like the header
   does. The lightbox and Новый пост already clear it; a centred dialog never
   reaches it. ---- */
html.app .tabu-sheet-head{flex-basis:calc(56px + env(safe-area-inset-top)); padding-top:env(safe-area-inset-top);}
html.app .tabu-sheet-close{top:calc(6px + env(safe-area-inset-top));}

/* ---- 14. the top bar is complete in the first frame. The chevron and the
   fixed titles are drawn here, from html.has-back and html.page-<name>, which
   every page's <head> sets before its first paint; native.js appends the real
   .app-back and .app-title at the end of <body>, so with the motion between
   pages (section 12) the new page's first frame used to have an empty bar
   and the words popped in after it. The button's own glyph replaces the drawn
   one the moment it exists (:has), same size, same spot: 6px in from the
   corner, 44px button, 24px icon. The post's title is the tab's name, read
   from the post itself, so it stays native.js's; its share glyph is drawn
   here the same way. ---- */
html.app.has-back header::before,
html.app.page-question header::after{
  content:""; position:absolute; pointer-events:none; background:var(--text);
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat; -webkit-mask-position:center; mask-position:center;
}
html.app.has-back header::before{
  left:16px; top:calc(env(safe-area-inset-top) + 16px); width:24px; height:24px;
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 5l-7 7 7 7'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 5l-7 7 7 7'/%3E%3C/svg%3E");
}
html.app.page-question header::after{
  right:18px; top:calc(env(safe-area-inset-top) + 18px); width:20px; height:20px;
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v13'/%3E%3Cpath d='M8 7l4-4 4 4'/%3E%3Cpath d='M5 12v8h14v-8'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v13'/%3E%3Cpath d='M8 7l4-4 4 4'/%3E%3Cpath d='M5 12v8h14v-8'/%3E%3C/svg%3E");
}
html.app.has-back body:has(.app-back) header::before,
html.app.page-question body:has(.app-share) header::after{display:none;}
/* Вход and Регистрация draw the header static and tall (section 7): the
   chevron is the fixed button's alone there */
html.app.page-login header::before, html.app.page-register header::before{display:none;}

/* the title, one per page, the site's own word for it; the same box as
   .app-title (section 4). native.js writes no .app-title for these pages. */
html.app.page-map header::after, html.app.page-communities header::after,
html.app.page-notifications header::after, html.app.page-profile header::after,
html.app.page-archive header::after, html.app.page-leaderboard header::after,
html.app.page-settings header::after, html.app.page-ask header::after,
html.app.page-rating header::after, html.app.page-campus-tabs header::after{
  position:absolute; left:0; right:0; top:env(safe-area-inset-top); height:56px;
  display:flex; align-items:center; justify-content:center;
  font-size:15px; font-weight:600; color:var(--text-strong); pointer-events:none;
}
/* the word itself is the page's own: <header data-title="{{t:nav.map}}">, filled
   by the server in the page's language (i18n) */
html.app.page-map header::after, html.app.page-communities header::after,
html.app.page-notifications header::after, html.app.page-profile header::after,
html.app.page-archive header::after, html.app.page-leaderboard header::after,
html.app.page-settings header::after, html.app.page-ask header::after,
html.app.page-rating header::after, html.app.page-campus-tabs header::after{content:attr(data-title);}
/* !important: the title rules above (page class + header) outrank this one */
html.app header.search-open::before, html.app header.search-open::after{display:none !important;}

/* ---- 15. hit areas. «Отмена» in the open search is a 17px word; the thumb
   gets 44px without the word moving (the .qcard-reply .admin-btn::after
   pattern in style.css). ---- */
html.app .mobile-search-cancel{position:relative;}
html.app .mobile-search-cancel::after{
  content:""; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:100%; height:44px;
}

/* =========================================================
   16. native tells
   A handful of things that read as "website" on a phone, none of them
   touching how a page looks under a real cursor. None of the 127 :hover
   rules in style.css or a page's own <style> are edited; each one that
   changes a tappable row or control's look is answered here instead.
   ========================================================= */

/* 16a. sticky hover. iOS keeps :hover applied after a tap until the next
   tap lands somewhere else, so a row or pill stays lit. (hover: none) is
   true on a touch screen and false for a real pointer, so a mouse still
   gets the hover in style.css untouched; every rule below sets back only
   the properties its hover rule changes, to that rule's own rested value. */
@media (hover: none){
  html.app .tabu-sheet-row:hover{background:none;}
  html.app .tabu-sheet-into:hover{background:none; color:var(--muted);}
  html.app .tabu-sheet-close:hover{background:none;}
  html.app .tabu-sheet-action:hover{background:none;}
  /* is-new is excluded: its own (non-hover) rule already outranks the plain
     hover rule on color/border-color by source order, so only its own
     hover's background needs a rested value here, kept in its own line. */
  html.app .tabu-modal-choice:not(.is-new):hover{color:var(--muted); border-color:transparent;}
  html.app .tabu-modal-choice.is-new:hover{background:var(--surface-hover);}
  html.app .tabu-modal-suggest-item:hover{border-color:var(--border); background:var(--surface);}
  html.app .tabu-picker-item:hover{background:none;}
  html.app .qcard:hover{background:transparent;}
  html.app .qaction:hover{color:var(--muted);}
  /* .up and .voted are permanent states (color:text always), not part of the
     hover bug: excluding them leaves only the idle arrow's own hover to reset. */
  html.app .vote-btn:hover:not(.up):not(.voted){color:var(--arrow-idle);}
  html.app .vote-col:has(.voted) .vote-btn:not(.voted):hover{color:var(--arrow-idle);}
  html.app .tag:hover{color:var(--muted);}
  html.app .chip:hover{background:var(--surface);}
  html.app .tab-badge:hover{opacity:1; text-decoration:none;}
  html.app .place-badge:hover{color:var(--meta-strong);}
  html.app .filter-tab:not(.active):hover{color:var(--control-idle);}
  html.app .icon-btn:hover{background:var(--surface);}
  html.app .topbar-right .icon-btn:hover{background:none;}
  html.app .action-btn:hover{color:var(--muted);}
  html.app .action-btn.report-btn:hover{color:var(--muted);}
  html.app .attach-row:hover{background:var(--surface); color:var(--muted);}
  html.app .attach-remove:hover{color:var(--muted);}
  html.app .cbox-tool:hover{background:none; color:var(--muted);}
  html.app .cbox-chip-x:hover, html.app .cbox-cancel:hover{color:var(--muted);}
  html.app .btn-primary:hover{background:var(--accent-fill);}
  html.app .btn-secondary:hover{background:var(--surface);}
  html.app .tabu-lightbox-close:hover{background:rgba(0,0,0,.5);}
  html.app .leaflet-control-zoom a:hover{background:var(--surface) !important;}
  html.app .leaflet-control-locate a:hover{background:var(--surface) !important;}
  /* .bottom-nav a carries no :hover rule of its own, and .tab-card-join's
     (communities.html) sits inside @media(hover:hover), which a finger
     never matches, so there is nothing to reset for them. */
}

/* 16b. touch-action: manipulation drops the double-tap-to-zoom delay on
   the controls a thumb actually presses. Excluded by :not(), not answered
   with a second auto rule afterwards: a .leaflet-container touch-action
   comes from leaflet.css's own .leaflet-container.leaflet-touch-drag(.leaflet-touch-zoom)
   rules, a .swipe-grab's from swipe.css (touch-action:none, the drag
   handle), and .tabu-lightbox's drag-to-dismiss needs its container's own
   touch-action:none (style.css) to reach the touch before the browser
   does. A same-specificity auto rule can outrank or lose to any of those
   depending on which of Leaflet's classes are present, so the only rule
   that is always right is to never match these elements at all. The tap
   flash goes with it, same list: the shell already clears it on body, this
   covers controls that could otherwise carry their own. */
html.app :is(a, button, [role=button], .vote-btn, .qaction, .bottom-nav a, .tabu-sheet-row, input[type=submit]):not(.swipe-root *):not(.leaflet-container *):not(.tabu-lightbox *):not(.swipe-grab){
  touch-action:manipulation; -webkit-tap-highlight-color:transparent;
}

/* 16c. selection and callouts on chrome, not on anything read or copied:
   post titles, bodies, comments and every input/textarea keep the normal
   long-press menu. */
html.shell .tabu-sheet,
html.shell .tabu-modal-actions,
html.shell .tabu-modal-choices,
html.shell .bottom-nav,
html.shell .app-back,
html.shell .app-share,
html.shell .tab-card-join,
html.shell .filter-tab,
html.shell .chip,
html.shell .tag,
html.shell .btn-primary,
html.shell .btn-secondary,
html.shell .qcard .author-row{
  -webkit-user-select:none; user-select:none; -webkit-touch-callout:none;
}
/* .tabu-sheet carries the search field (16c above), and user-select:none
   is inherited: without this, the text someone just typed there could not
   be selected or copied. */
html.app .tabu-sheet input{
  -webkit-user-select:text; user-select:text;
}

/* 16d. focus rings that WebKit can leave after a tap, on controls a mouse
   still needs them on: .tabu-sheet-into and .pw-toggle keep their own
   :focus-visible rule in style.css untouched. */
html.app :is(button, a, .tabu-sheet-row, .tabu-modal-choice, .tab-card-join):focus:not(:focus-visible){
  outline:none;
}

/* 16e. overscroll: a fixed sheet or dropdown should not chain its rubber
   band to the page behind it. .search-suggest (style.css) and
   .swipe-thread-scroll (swipe.css) already carry this; only the two
   without it are added here. */
html.app .tabu-sheet-list,
html.app .tabu-modal-backdrop{
  overscroll-behavior:contain;
}

/* ---- 17. in the native shell a tab tap is instant and the page stays alive,
   so the pressed colour would stay lit after the shell cancels the web
   navigation mid-gesture: two tabs lit at once. No press state on the bar. ---- */
/* no state at all on a tab that is not the current one: the page you leave
   keeps the tapped tab pressed, hovered or focused when the shell hides it
   mid-gesture, and that tab stayed lit when you came back */
html.app .bottom-nav a:not(.active):not(.bottom-nav-add),
html.app .bottom-nav a:not(.active):not(.bottom-nav-add):hover,
html.app .bottom-nav a:not(.active):not(.bottom-nav-add):active,
html.app .bottom-nav a:not(.active):not(.bottom-nav-add):focus,
html.app .bottom-nav a:not(.active):not(.bottom-nav-add):focus-visible{color:var(--control-idle) !important; outline:none;}
/* the plus keeps its own colours: snow on ink, ink on snow */
html.app .bottom-nav a.bottom-nav-add{outline:none;}
html.app .bottom-nav a:active svg{transform:none;}
html.app .bottom-nav a{-webkit-tap-highlight-color:transparent;}

/* ---- 18. a screen pushed by the native shell (html.shell-pushed, set by the
   shell before the page loads): the tab bar belongs to the five tab roots
   only, and the shell's stack is the way back ---- */
html.shell-pushed .bottom-nav{display:none !important;}
html.app.shell-pushed body{padding-bottom:env(safe-area-inset-bottom) !important;}
html.app.shell-pushed.page-question body{padding-bottom:calc(76px + env(safe-area-inset-bottom)) !important;}
html.app.shell-pushed .range{bottom:0;}

/* ---- 19. a theme change that arrives from another screen (Настройки, in
   the shell) jumps, it does not animate: the page was covered, its colour
   transition froze at the old colour and played on the way back, an old-theme
   flash. native.js sets html.theme-jump on the storage event and clears it
   two painted frames later. ---- */
html.app.theme-jump, html.app.theme-jump *{transition:none !important;}

/* ---- 20. in the APP (html.shell, never the phone website) nothing is
   selectable except text. A long press on a button, a bar or a card frame
   gave the web's selection handles; only the words of a post, an answer, a
   description and the fields keep selection. The website keeps the web's
   behaviour: it is a website. ---- */
html.shell, html.shell *{-webkit-user-select:none; user-select:none;}
html.shell .qtitle, html.shell .qsnippet, html.shell .q-body-text, html.shell .answer-txt,
html.shell .tab-card-desc, html.shell .tab-row-sub, html.shell .notif-title, html.shell .panel-post-body,
html.shell .swipe-thread .answer-txt, html.shell .profile-bio,
html.shell p, html.shell h1, html.shell h2, html.shell h3, html.shell li,
html.shell input, html.shell textarea, html.shell [contenteditable="true"]{-webkit-user-select:text; user-select:text;}

/* ---- 21. in the app a long press on a link is nothing: no callout, no
   preview. Buttons are links here. ---- */
html.shell a, html.shell [data-href], html.shell button{-webkit-touch-callout:none;}

/* ---- 22. the strip under the status bar is frosted (Instagram): whatever
   scrolls under the clock is blurred behind a wash of the page colour. Over
   a flat area it is invisible, the same colour as the page; app only, the
   browser has its own bar there. ---- */
html.shell[data-home-mode="map"] body::before, html.shell.page-map body::before{
  content:""; position:fixed; left:0; right:0; top:0; z-index:2000; pointer-events:none;
  /* runs 14px past the inset and fades out over that stretch: no cut-off line */
  height:calc(env(safe-area-inset-top) + 24px);
  background:color-mix(in srgb, var(--bg) 22%, transparent);
  -webkit-backdrop-filter:blur(5px); backdrop-filter:blur(5px);
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 30px), transparent);
  mask-image:linear-gradient(to bottom, #000 calc(100% - 30px), transparent);
}

/* ---- 23. a screen pushed in the app (post, theme, settings) keeps its top
   bar and lets the content show through it, frosted, as Instagram does inside
   a post; only the five tab roots hide their bars on scroll. The bar covers
   the status area itself, so the separate strip (22) is not drawn there. ---- */
html.shell.shell-pushed header{background:transparent;}
/* a pushed screen's bar (back, title, share) sits on a post's text all the
   way down; it gets a heavier blur than the tab roots' bar (10px vs 5px),
   still no tint. Decided 18 Sep 2026. */
html.shell.shell-pushed .app-frost{--frost-blur:10px;}
html.shell.shell-pushed .app-frost > i{opacity:1;}
html.shell.shell-pushed body::before{display:none;}
