/* Design tokens for the "ВСІМ СВОЇМ" mini app.
   Dark premium surface, one neon-pink brand accent, per-category colour moods
   that never compete with it. Every value below is referenced by name; no
   component invents a colour of its own. */
:root {
  /* All but black, and neutral.  It stood on a navy once, on the argument
     that light through dark glass reads as light only if the glass has a
     colour -- but at a quarter of a tint the navy was most of what showed and
     the light never carried.  This is two per cent of grey: enough to take
     the hardness off, too little to stand in the light's way. */
  --bg: #06070a;
  --surface: #15181d;
  --card: #1c2026;
  --card-hi: #232830;
  /* The quiet surface (owner, 09.10.2026): Home's models card, the search field and the words under every
     card's picture -- the dark theme's neutral card at night, a very light neutral in daylight. */
  --soft-surface: var(--card);
  --soft-surface-line: var(--line-strong);
  --line: rgba(255, 255, 255, .08);
  --line-strong: rgba(255, 255, 255, .14);

  --ink: #f6f7f9;
  --text: var(--ink);
  --ink-dim: #9aa3af;
  --text-dim: var(--ink-dim);
  /* Between the two: a menu label reads as a label rather than as a headline,
     without becoming secondary the way --text-dim would make it. */
  --ink-soft: #dfe3e9;
  --text-soft: var(--ink-soft);
  /* Raised off #6b7280, which fell to 4.2:1 once the page went near black --
     under the 4.5 a body of text needs.  This is 5.4. */
  --ink-faint: #7c8492;
  /* The quiet white a card writes its place in, and now the filters too:
     legible on glass without competing with what it labels. */
  --ink-quiet: rgba(255, 255, 255, .55);
  --text-faint: var(--ink-faint);

  /* The plate a card wears where a photograph would be.  Work and Online work
     never ask for a picture, and a grey square in a row of photographs reads
     as a card that failed to load rather than one that never had a picture.
     Dark on either ground on purpose: it is ours, and ours looks like this --
     which is also why it is not redefined per theme.  The listing itself still
     opens with no picture at all; this is a plate with our name on it, not a
     photograph of anything. */
  --blank-face:
    radial-gradient(120% 96% at 50% 4%, rgba(255, 63, 146, .3), transparent 68%),
    linear-gradient(155deg, #1b1622, #0f0d14);
  --blank-ink: rgba(255, 255, 255, .88);
  --blank-name: rgba(255, 255, 255, .66);

  /* Somebody else's colours, and the only ones here that are not ours.  A
     brand mark in the wrong colour is a different brand, and these are what
     people actually recognise them by -- the shape alone was never doing all
     the work.  One value for both themes: a brand does not have a dark
     version. */
  --brand-telegram: #2aabee;
  --brand-whatsapp: #25d366;
  --brand-viber: #7360f2;
  --brand-instagram: #e1306c;
  --brand-facebook: #1877f2;
  /* Between dim and faint: a placeholder has to be readable without
     competing with what someone has typed. */
  --ink-placeholder: #7e8794;
  --placeholder: var(--ink-placeholder);

  --accent: #ff3f92;
  --accent-hi: #ff6aa8;
  /* What a price is written in, everywhere one is written.  On the dark page
     it is the accent itself and nothing else -- that is what it has always
     been, and nothing here changes it. */
  --price-ink: var(--accent);
  --accent-deep: #d81f70;
  /* Type that sits on an accent-tinted surface: pink enough to belong to
     it, light enough to read as text rather than as another glow. */
  --accent-text: #ffd3e6;
  --accent-soft: rgba(255, 63, 146, .14);
  /* What a form says is wrong with a field.  The same family as the outline
     the field wears, a step quieter -- the outline has already said which
     field, and a form of fourteen questions lit in full accent reads as a
     form of fourteen mistakes.  Its own token because asking for a correction
     and warning that something cannot be undone are different things, and a
     screen that spends one colour on both has nothing left to say with. */
  --field-note: #ff8ab8;
  /* And the other half of that same sentence: warning that something cannot
     be undone.  The note above says why it is not the field note and not the
     brand pink -- asking for a correction, acting, and closing a door for
     good are three different things, and a screen that spends one colour on
     all three has nothing left to say with.  Red because every reader already
     knows it, and only ever on the last button in a sequence, never on the
     one that merely asks the question.
     #c62b30 carries white at 5.54, so the button reads on either ground; the
     face and the line are the same red thinned, which a light page and a dark
     one both take. */
  --undone: #c62b30;
  --undone-face: rgba(198, 43, 48, .13);
  --undone-line: rgba(198, 43, 48, .32);
  /* Lit for the night's card: 8.4 against #1c2026. */
  --undone-ink: #ff9fa2;
  /* A round action: the accent filling it, a shadow under it, and a white
     line off its shoulder.  It glowed for a while, and a glowing accent on a
     black page is the lighting of a nightclub rather than a mark on a
     button -- the colour says "press this", the light said something else. */
  --neon-accent:
    0 3px 10px rgba(0, 0, 0, .45),
    inset 0 1px 0 rgba(255, 255, 255, .4);
  /* Lit from its own shoulder rather than flat across. */
  --neon-face: linear-gradient(160deg, var(--accent-hi) 0%, var(--accent) 46%, var(--accent-deep) 100%);
  /* The avatar's ring: the one place the brand's two colours meet as light
     rather than as a hairline, so they are named where every other colour is. */
  --ring-blue: #2f7bff;
  --ring-violet: #7a4dff;
  /* Ratings are gold everywhere people read them; the brand pink would
     make a rating look like an action. */
  --star: #f6c344;
  /* Open, available, confirmed. Kept apart from the brand pink, which
     means "act" rather than "good". */
  --ok: #3ddc97;
  /* A failure said by a mark (the toast that reports one): warm red, never the accent. */
  --alarm: #e5484d;

  /* Category moods, from the reference: work blue, housing warm amber,
     services crimson, market teal, info cold blue, community violet. */
  --c-work-a: #2563eb; --c-work-b: #1e3a8a;
  --c-housing-a: #f97316; --c-housing-b: #9a3412;
  --c-services-a: #ec4899; --c-services-b: #9d174d;
  --c-market-a: #14b8a6; --c-market-b: #115e59;
  --c-info-a: #3b82f6; --c-info-b: #1e40af;
  --c-community-a: #a855f7; --c-community-b: #6b21a8;

  /* Blue-lavender glass: the search field, the bottom bar and the community
     row are made of it. Kept here so a change reaches all three. */
  --glass-1: rgba(118, 136, 255, .20);
  --glass-2: rgba(150, 122, 255, .15);
  --glass-3: rgba(96, 165, 255, .11);
  --glass-line: rgba(152, 140, 255, .26);

  /* The dark plate a badge or a glyph sits on when it lies over a
     photograph. One definition, so the two can never drift apart. */
  --plate: rgba(9, 11, 14, .72);
  --plate-line: rgba(255, 255, 255, .16);
  --plate-shadow: 0 2px 10px rgba(0, 0, 0, .45);

  /* Frosted glass: a light film over a translucent dark base, with a
     highlight along the top edge. One recipe, so every glass element is
     the same material rather than three near-misses. */
  --frost-film-a: rgba(255, 255, 255, .075);
  --frost-film-b: rgba(255, 255, 255, .022);
  --frost-base: rgba(30, 34, 42, .58);
  --frost-line: rgba(255, 255, 255, .15);
  --frost-hi: rgba(255, 255, 255, .13);
  /* The whole filter, not a length: written as `blur(var(--frost-blur))` it
     expands to `blur(blur(24px) saturate(1.7))`, which is not valid CSS and is
     thrown away in silence -- the glass then has a tint and no blur at all. */
  --frost-blur: blur(24px) saturate(1.7);

  /* A card's edge is lit rather than drawn: a cool highlight on one side, a
     breath of the brand pink on the other, and a dark translucent ground
     between them.  Kept as tokens so every glass card is lit by one lamp. */
  --edge-line: rgba(255, 255, 255, .20);
  /* The edge is lit, not coloured -- white, and only white.  It was blue at
     one end and pink at the other, which is a brand painted round a card
     rather than light falling on one. */
  --edge-cool: rgba(255, 255, 255, .42);
  --edge-warm: rgba(255, 255, 255, .40);
  /* The lit top edge of a card, in daylight rather than in grey. */
  --edge-hi: rgba(255, 255, 255, .19);

  /* ---- one raised pane, wherever a surface can be pressed --------------
     The bottom bar's own glass, exactly: the same dark plate under the same
     blur, with the same sheen down its face.  It was white over a lighter
     blur for a while, which is a different material standing next to the bar
     on every screen; there is one material now.
     Its edge is the ring below rather than the bar's inset line -- the bar is
     a capsule with nothing inside it, and a card has a photograph. */
  --pane-face: linear-gradient(180deg,
    rgba(255, 255, 255, .10) 0%, rgba(255, 255, 255, .02) 42%,
    rgba(255, 255, 255, 0) 72%);
  /* One plate under all the glass -- the panes and the bar alike.  Its
     density is the day theme's business too: at .42 over a light page the
     glass composites to a mid grey, which is the one ground that carries
     neither white writing nor dark. */
  --glass-plate: rgba(9, 11, 15, .42);
  /* The lit lip along the top and the sheen down the face: what makes the
     bar read as glass rather than as a tint.  One declaration, so the bar,
     the panels that open over the page and the strong actions cannot each
     drift into a glass of their own. */
  /* The hairline that rings a piece of glass.  Named so that one surface
     can wear it in the accent -- the search bar does -- without the rest
     of the glass having to be written out a second time to say so. */
  --glass-ring: rgba(255, 255, 255, .18);
  /* The sheen down the face, on its own.  A circle takes this and not the
     lip: a lip is the top edge of something that has a top edge, and on a
     round button the 1px band across the top of its box is clipped to a
     short straight chord laid over the curve -- which is exactly what it
     looks like. */
  --glass-face:
    linear-gradient(180deg, rgba(255, 255, 255, .10) 0%, rgba(255, 255, 255, .02) 42%,
                            rgba(255, 255, 255, 0) 72%);
  --glass-sheen:
    linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .25) 18%,
                           rgba(255, 255, 255, .46) 50%, rgba(255, 255, 255, .25) 82%,
                           rgba(255, 255, 255, 0) 100%) top / 100% 1px no-repeat,
    var(--glass-face);
  /* A strong action wears the bar's own glass and says its word the way the
     "НОВЕ" mark says it: the accent as the writing, a hairline of it round
     the edge, and no fill at all.
     The writing is the one part that has to turn over with the ground.  The
     plate flips from white-on-black to black-on-white, and on the light one
     the bright pink reads 1.75:1 -- so daylight gets a deep pink instead, at
     4.8.  The solid fill this replaced gave its white 3.3 either way. */
  --action-face: var(--glass-plate-soft);
  --action-line: rgba(255, 63, 146, .38);
  --action-ink: var(--accent-hi);
  /* Nothing at night: the bar's plate over a near-black page is already the
     brightest thing in that corner, and a halo round it would be a second
     glow beside the one on the plus in the middle of the bar. */
  --action-glow: 0 0 0 0 rgba(255, 63, 146, 0);
  /* The lighter plate the profile's own buttons stand on, and now the bar at
     the foot as well.  In the dark it is lifted with white rather than
     thinned with less black: the page behind is already near black, so
     letting more of it through makes a surface no lighter at all. */
  --glass-plate-soft: rgba(255, 255, 255, .07);
  /* The same plate thinner by a fifth, for the bar on Blink alone.  The two
     engines are only now drawing the same thing, and at the far heavier
     radius Blink needs to match WebKit the bar comes out the more solid of
     the two.  WebKit is right as it is and does not read this. */
  --glass-plate-bar: rgba(255, 255, 255, .056);
  --pane-fill: var(--glass-plate);
  --pane-blur: var(--glass-frost);
  /* Light in the order it falls on a raised thing: a lit lip along the top, a
     faint catch along the bottom, the inside of its own foot in shadow, and
     the pane standing off the page.  Without the third it is a flat sheet
     with a bright line on it. */
  --pane-lift:
    inset 0 1px 0 rgba(255, 255, 255, .32),
    inset 0 -1px 0 rgba(255, 255, 255, .05),
    inset 0 -13px 17px -15px rgba(0, 0, 0, .62),
    0 1px 2px rgba(0, 0, 0, .5),
    0 10px 22px rgba(0, 0, 0, .42);
  /* What a raised round thing and a floating one cast.  Named so that the day
     can have its own: a shadow is the one part of this palette that cannot be
     the same on both grounds. */
  --lift-round: 0 4px 14px rgba(0, 0, 0, .34);
  --lift-far: 0 10px 26px rgba(0, 0, 0, .42);
  --pane-press:
    inset 0 1px 0 rgba(255, 255, 255, .2),
    inset 0 -10px 14px -13px rgba(0, 0, 0, .55),
    0 1px 2px rgba(0, 0, 0, .46),
    0 4px 10px rgba(0, 0, 0, .36);
  /* Glass, not paint: the ground is thin enough that the room's light shows
     through it.  Made opaque, the card reads as a black rectangle with a
     drawn outline, which is the one thing it must not be.  At the density the
     profile's menu was settled at: that number used to live in the profile
     alone, and every other card was a third thinner than the one card anybody
     had actually looked at. */
  /* A plain grey.  It carried a blue in it, which on a near-black page read
     as a colour rather than as a surface. */
  --edge-ground: rgba(46, 47, 50, .60);
  /* The light that pools at the foot of the screen, brought a little closer
     under a card: the same two colours as the ambient, slightly brighter, so
     a card looks lit by the room rather than outlined on top of it. */
  --edge-pool-cool: rgba(52, 98, 205, .34);
  /* Depth under a card, in shadow rather than in pink light. */
  --edge-pool-warm: rgba(6, 8, 14, .34);
  --edge-lit-cool: rgba(255, 255, 255, .58);
  --edge-lit-warm: rgba(255, 255, 255, .70);

  --r-chip: 999px;
  --r-card: 16px;
  --r-tile: 20px;
  --r-sheet: 24px;

  /* How much of the phone the page leaves at its two sides.  Its own name
     rather than a spacing step, because everything that bleeds to the edge of
     the screen has to undo exactly this and nothing else. */
  --page-side: 10px;
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 20px; --s6: 24px; --s8: 32px;

  /* One height for every control in the profile's settings, and the air
     between them: stated once so a third control cannot arrive at a fourth
     size. */
  --settings-row: 50px;
  --settings-gap: 8px;
  --nav-h: 64px;
  /* The glass a floating surface is made of -- the bottom bar and the
     search field both.  A whole filter, not a length -- written as
     `blur(var(--nav-glass))` it would be invalid and silently dropped.
     Telegram's own controls are the reference, and the latest iOS uses the
     same material: a dark pane at less than half opacity over a blur heavy
     enough that what is behind it survives only as moving colour.  Not
     see-through -- frosted.  The saturation is the half of it people read as
     glass rather than as smoked plastic: real glass carries the colour of
     what is behind it through, stronger than it found it. */
  --glass-frost: blur(28px) saturate(1.14) brightness(.9);
  --safe-b: env(safe-area-inset-bottom, 0px);
  /* The right notch: a landscape iPhone puts one there, and the contact
     rail stands against that edge. */
  --safe-r: env(safe-area-inset-right, 0px);
  /* What the bottom bar actually keeps clear.  The home indicator is a few
     pixels inside a much taller inset, and spending the whole of it left the
     bar floating well above the edge of the phone.  The bar's glass still
     reaches the edge; only its buttons stop short of the indicator. */
  --nav-floor: max(2px, calc(var(--safe-b) - 24px));
  /* The bar floats: how far its underside stands off the floor, and how far
     its ends stand off the sides of the phone. */
  --nav-lift: 8px;
  --nav-inset: var(--page-side);
  --safe-t: env(safe-area-inset-top, 0px);
  /* Telegram full screen: the notch, and below it the band its own Close and ⋯ float in (lib/telegram.ts
     syncSafeArea writes both; 0 where there is no band).  Home's wordmark stands in the band. */
  --tg-device-t: 0px;
  --tg-band-h: 0px;

  --font: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Display",
          "Segoe UI", Roboto, system-ui, sans-serif;
  /* The wordmark only: Unbounded is drawn for Ukrainian Cyrillic. Both fall
     back to faces every device has, so a font that fails to load costs
     character, never legibility. */
  --font-brand: "Unbounded", "Montserrat", "Inter", -apple-system,
                BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
  --font-hand: "Caveat", "Segoe Script", "Bradley Hand", "Snell Roundhand", cursive;

  color-scheme: dark;
  /* Written on the app's own chrome -- the bar's labels -- which stands on
     glass rather than on the page and so flips with the ground under it. */
  --ink-chrome: #fff;
  --chrome-text: var(--ink-chrome);
}

/* ---------- daylight ---------------------------------------------------
   One switch, and only two things move under it: the ground the page stands
   on, and the colour of what is written on it.  The glass, the edges and the
   accent are the same objects in both -- glass over a light ground comes out
   light by itself, which is what glass is for and what a second set of card
   colours would have thrown away. */
:root[data-theme="day"] {
  /* The lit red is a glow on a white page rather than a sentence.  The deep
     one reads there: 6.0 on the daylight card.  The fill and the two washes
     are the same on both grounds -- only the ink has to change. */
  --undone-ink: #b3261e;
  /* The one green that has to be read as words rather than seen as a glow.
     #3ddc97 is lit for a dark page and washes out on a white one, where the
     same sentence has to carry on a light ground. */
  --ok: #0f8f5b;
  /* On a bright page the lit pink is a glow rather than a sentence, and this
     one has to be read.  The same deep pink daylight already writes actions
     in. */
  --field-note: #a81050;
  /* Daylight thins it instead: there the page behind is bright, so less
     plate means more of it. */
  --glass-plate-soft: rgba(9, 11, 15, .11);
  --glass-plate-bar: rgba(9, 11, 15, .088);
  /* Deep enough to read on that plate: the bright pink gives 1.75 there. */
  --action-ink: #a81050;
  /* And the plate itself is pink here rather than grey.  Over a light page a
     dark wash comes out the one neutral that says nothing at all, so daylight
     washes the action in the brand instead: #edcbdc, with the deep pink on it
     at 4.97.  A halo under it as well, which the night has no need of. */
  --action-face: rgba(255, 63, 146, .18);
  --action-glow: 0 8px 22px rgba(255, 63, 146, .28);
  color-scheme: light;
  --bg: #e9eaec;
  --soft-surface: rgba(244, 245, 247, .74);
  --soft-surface-line: rgba(20, 22, 27, .10);
  /* The day's own writing keeps names of its own, so a rule that wants it
     back on a pane can ask for it by name rather than by literal. */
  --page-ink: #14161b;
  --page-ink-soft: #23262d;
  --page-ink-dim: #4a505b;
  --page-ink-faint: #5b626d;
  --text: var(--page-ink);
  --text-soft: var(--page-ink-soft);
  --text-dim: var(--page-ink-dim);
  --text-faint: var(--page-ink-faint);
  --placeholder: #6b7280;
  --chrome-text: var(--page-ink);
  /* The density the app has always had.  It was raised to .60 to buy back
     the legibility a mid grey costs, and that darkened every card on every
     screen, which is not what anyone wanted to look at.  The glass stays what
     it was and the writing carries what it can: on a pane this thin even
     white reaches only 4:1, so the quiet greys are lifted nearly to it. */
  --ink-dim: #dbe0e7;
  --ink-faint: #ccd2da;
  /* And the shadows, which the glass above could keep and these could not.
     A shadow tuned for a dark page is invisible on it and a smear on a light
     one: at 42% black with a 22px blur, eight cards in a rail filled the gaps
     between themselves and laid a grey band under the whole row.  The same
     42% under the contact marks did the same along that strip.  Daylight
     needs a shadow that says "raised" and nothing else. */
  --pane-lift:
    inset 0 1px 0 rgba(255, 255, 255, .3),
    inset 0 -1px 0 rgba(255, 255, 255, .06),
    inset 0 -13px 17px -15px rgba(0, 0, 0, .3),
    0 1px 2px rgba(15, 20, 30, .10),
    0 6px 14px rgba(15, 20, 30, .10);
  --lift-round: 0 3px 10px rgba(15, 20, 30, .14);
  --lift-far: 0 8px 20px rgba(15, 20, 30, .16);
}

/* Glass is dark in both grounds, so the writing on it is light in both.  The
   day block above moves the page's type; this moves it back wherever it is
   standing on a pane rather than on the page.  The names are the same ones,
   so there is no second palette to keep in step -- only a second place the
   first one is asked for. */
/* `:where` rather than `:is`: this has to beat what an element inherits from
   the page and nothing else.  At `:is`'s specificity it also beat the rules
   components write for themselves -- a chip that turns white when chosen was
   given white writing to put on it. */
:root[data-theme="day"] :where(
  .gcard, .rowcard, .picker, .chatcard, .addrow, .draftbar,
  .bottomnav, .daylight, .themeswitch, .popover-panel, .sheet > section,
  .chip, .cta-ghost, .pastecard
) {
  --text: var(--ink);
  --text-soft: var(--ink-soft);
  --text-dim: var(--ink-dim);
  --text-faint: var(--ink-faint);
  --placeholder: var(--ink-placeholder);
  --chrome-text: var(--ink-chrome);
}

/* Where the two grounds meet on one element, each is named rather than left
   to inherit -- these are the handful the switch could not settle by itself. */

/* The header's two selectors and the search field, in the plain white of the
   switch beside them rather than the softer white the night writes with. */

/* No lit lip along the top of a capsule on a bright page.  The sheen is a
   1px white line across the top edge: on a dark ground it is the light
   catching the glass, and on a light one it is a white stripe drawn over
   grey.  Said on the page and on the bar, which is its sibling, so every
   capsule inside either one loses it together. */
:root[data-theme="day"] :is(.page, .bottomnav) { --glass-sheen: var(--glass-face); }

:root[data-theme="day"] .picker.compact .picker-text b,
:root[data-theme="day"] .picker.compact .picker-text i,
:root[data-theme="day"] .daylight,
:root[data-theme="day"] .searchbar input { color: var(--page-ink); }
/* The words the field suggests, not the ones a reader types: those were what
   the first pass moved, and they are the ones nobody was looking at. */
:root[data-theme="day"] .searchbar input::placeholder { color: var(--page-ink-dim); }

/* Who is selling used to be written in the light ink, because the card was
   counted among the dark glass surfaces.  Its wash is pale, so that writing
   was there to be squinted at; the card takes the page's own grey-black now,
   and the only thing on it that keeps the light ink is the count button,
   which has a dark face of its own.  See `.providercard` in `app.css`. */

/* Both hearts lie on a photograph, which is a ground of its own and does not
   turn over with the page. */
/* Both hearts lie on a photograph, which is a ground of its own and does not
   turn over with the page -- but only while they are empty.  A saved one is
   the accent in both grounds, and this rule used to take that away from it. */
:root[data-theme="day"] .heart:not([aria-pressed="true"]),
:root[data-theme="day"] .fav-fab:not(.on) { color: var(--ink); }

/* The profile's two selectors say what they are quietly; what they are set to
   is left as it is. */
:root[data-theme="day"] .profile-pickers .picker-text i,
:root[data-theme="day"] .themeswitch svg { color: var(--page-ink-dim); }

/* A section's chips are dark chips on a light page, so their writing is light
   -- but half a tone under the plain white, which at that size and against
   that little ground came out shouting. */
:root[data-theme="day"] .chip { color: var(--ink-soft); }

/* A form's chips are the one glass that keeps the page's own tint rather than
   a plate of its own, so on a light page they come out light -- and their
   writing goes dark with the page instead of staying white on it. */
:root[data-theme="day"] .form .chip { color: var(--page-ink); }
:root[data-theme="day"] .form .chip svg { color: var(--page-ink-dim); }


/* Chromium renders a given blur radius far lighter than WebKit does: the one
   panel that had to be settled twice in this app landed on 40 against 14,
   near three to one.  So the frost is stated again for the engine that needs
   more of it -- the bar, the panels cut from the same glass, and the strong
   actions.  A card is not among them: `--pane-blur` is pinned to the value it
   already had, which was chosen against what Android was actually showing.

   Asked of the root rather than of `@supports (-webkit-touch-callout)`, which
   Blink answers yes to as well -- so this block and the WebKit one below it
   were both applying on Android, and the later of the two won. */
:root[data-engine="blink"] {
  --glass-frost: blur(80px) saturate(1.14) brightness(.9);
  /* A card keeps the number Android has actually been rendering all this
     time -- the WebKit one, which was reaching it through the very leak this
     attribute closes.  Whatever a card should be on Blink, it is not this
     change's to decide: it is every card on every screen. */
  --pane-blur: blur(20px) saturate(1.14) brightness(.9);
}


/* ---------- motion tokens (Premium UI & Motion, owner 11.10.2026) ----------
   Every duration and curve the app moves by, in one place; styles/motion.css
   and lib/motion.ts read these and nothing else.  A reader who asked their
   phone for less motion gets no travel and no scale -- a fade of --m-fade at
   most -- whatever mode the app is in.  The `off` mode is not written here:
   with it, no rule of motion.css applies at all. */
:root {
  --m-press: 90ms;
  --m-state: 140ms;
  --m-content: 180ms;
  --m-sheet-in: 260ms;
  --m-sheet-out: 200ms;
  --m-shared: 300ms;
  --m-fade: 100ms;
  --e-in: cubic-bezier(.2, .8, .2, 1);
  --e-out: cubic-bezier(.4, 0, 1, 1);
  /* A button sinks to .98, a large card to .99; the new screen rises 6px into place; a card going from the saved
     list shrinks to .96; the heart beats to 1.25. */
  --press-scale: .98;
  --press-scale-card: .99;
  --m-rise: 6px;
  --leave-scale: .96;
  --pop-scale: 1.25;
}
@media (prefers-reduced-motion: reduce) {
  :root {
    --m-press: 0ms;
    --m-state: var(--m-fade);
    --m-content: var(--m-fade);
    --m-sheet-in: var(--m-fade);
    --m-sheet-out: var(--m-fade);
    --m-shared: 0ms;
    --press-scale: 1;
    --press-scale-card: 1;
    --m-rise: 0px;
    --leave-scale: 1;
    --pop-scale: 1;
  }
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body, #root { height: 100%; }
body {
  margin: 0; min-width: 320px;
  background: var(--bg); color: var(--text);
  font-family: var(--font);
  font-size: 15px; line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}
button { font: inherit; color: inherit; border: 0; background: none; cursor: pointer; }
img { display: block; max-width: 100%; }

/* ---------- shell ---------------------------------------------------- */
.app { min-height: 100%; display: flex; flex-direction: column; position: relative; }
/* Soft light through dark glass: a few wide, low-opacity pools rather than a
   gradient with edges.  Blue only, since the pools were the brand colour and
   a page lit in the brand colour is a page where the brand marks nothing.
   The accent is spent on what a reader can press instead. */
.app::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(46% 24% at 14% 12%, rgba(58, 104, 210, .30), transparent 68%),
    radial-gradient(40% 22% at 88% 40%, rgba(52, 96, 200, .24), transparent 70%),
    radial-gradient(66% 26% at 50% 106%, rgba(48, 92, 200, .30), transparent 74%),
    radial-gradient(52% 22% at 100% 92%, rgba(52, 96, 200, .22), transparent 72%);
}
.page {
  position: relative; z-index: 1; flex: 1;
  padding: max(var(--safe-t), var(--s4)) var(--page-side)
           calc(var(--nav-h) + var(--nav-floor) + var(--nav-lift) + var(--s4));
  max-width: 520px; width: 100%; margin: 0 auto;
}

/* ---------- one raised pane ------------------------------------------- */
/* Every surface a reader can press is cut from the same glass.  They were cut
   four different ways -- two opaque fills, a frost and a tinted ground -- and
   the profile still wore the oldest of them, which is how it came to look
   untouched while the catalogue did not.  Written as a list so that cannot
   happen again.

   The six tiles and `.infocard` are deliberately outside it: both carry a
   photograph, and glass over a photograph is a different problem. */
.picker,
.chatcard,
.draftbar {
  background: var(--pane-face), var(--pane-fill);
  backdrop-filter: var(--pane-blur);
  -webkit-backdrop-filter: var(--pane-blur);
  box-shadow: var(--pane-lift);
  /* For the ring below, and so the ring is the only edge any of them draws. */
  position: relative; border: 1px solid transparent;
  /* Stated here rather than left to the daylight block: a pane that has to be
     told its colour from outside is a pane whose own rules get outranked, and
     that is how a chip chosen on a light page ended up white on white. */
  color: var(--text);
}

/* The lit edge every pane wears, and the only one any of them wears.  It is
   masked to the border area: without that the gradient fills the pane.  The
   profile carried a second version of this in blue and pink, which is how it
   went on looking like the old app after the glass was shared. */
.picker::before,
.chatcard::before,
.draftbar::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; pointer-events: none; z-index: 2;
  background:
    /* Brightest along the lower edge, because that is where the light is. */
    radial-gradient(118% 86% at 50% 116%,
      var(--edge-lit-warm) 0%, var(--edge-lit-cool) 40%, transparent 74%),
    linear-gradient(138deg,
      var(--edge-cool) 0%, rgba(255, 255, 255, .22) 26%,
      rgba(255, 255, 255, .07) 50%, rgba(255, 255, 255, .24) 74%,
      var(--edge-warm) 100%);
  -webkit-mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  mask-composite: exclude;
}

/* ---------- a section's own glass -------------------------------------
   The glass is built from tokens, so a section only has to redefine them:
   every card, chip, panel and field inside it follows without knowing which
   section it is in. The tints match the tile colours on the home screen, so
   opening Work feels like opening the blue tile. */
/* One glass for the whole app.  Each section used to carry its own tint --
   blue for Work, orange for Housing, pink for Services -- matching the tiles
   on the home screen.  A list of cards should look the same whichever section
   it was opened from, so they all wear Work's blue now.  Written once rather
   than as six identical blocks, so it cannot drift apart again. */
[data-section] {
  --glass-1: rgba(88, 132, 255, .22); --glass-2: rgba(70, 110, 240, .15);
  --glass-3: rgba(120, 170, 255, .11); --glass-line: rgba(120, 160, 255, .28);
}


/* ---------- app bar ---------------------------------------------------- */
/* One row, two columns: the wordmark and the greeting on the left, the avatar
   and its chips on the right. Keeping the greeting out of this row made the
   row as tall as the avatar block and left a hole under the wordmark. */
/* The home screen starts a little below the safe area rather than against it:
   a fraction of that area rather than a fixed number, so it stays the same
   proportion on a phone whose notch is a different size.  Longhands only --
   a `margin` shorthand here would cancel this without looking like it. */
.appbar {
  /* The avatar column's foot -- its three controls -- level with the foot of «Що шукаємо сьогодні?» (owner,
     09.10.2026): the two columns meet at the bottom, whichever is the taller. */
  display: flex; align-items: flex-end; gap: var(--s3);
  /* Half the step it was down to Home's search (owner, 09.10.2026: 16 -> 8). */
  margin-top: calc(var(--safe-t) * .15); margin-bottom: var(--s2);
}
/* Full screen in Telegram (iOS, Android): its Close and its ⋯ float in a band of their own below the notch, and the
   band between them is the page's (owner, 09.10.2026).  The wordmark stands there, in the middle, at its own size
   and colours, and gives the page its row back -- the greeting starts that much higher.  Without the band (Telegram
   drawing its own bar above the page) it stays where it was. */
:root[data-tg-band="on"] .appbar { margin-top: var(--s2); }
:root[data-tg-band="on"] .appbar .brand {
  position: absolute; z-index: 2; left: 0; right: 0;
  top: var(--tg-device-t, 0px); height: var(--tg-band-h, 0px); margin: 0;
  flex-direction: row; align-items: center; justify-content: center; pointer-events: none;
}
:root[data-tg-band="on"] .appbar .brand span { display: none; }
/* Compact (owner, 09.10.2026): the greeting and the country on the left, the face on the right, centred on them;
   12px down to the search.  In Telegram's full screen the row starts just under its band. */
.appbar.is-compact { align-items: center; margin-bottom: var(--s3); }
:root[data-tg-band="on"] .appbar.is-compact { margin-top: 6px; }
.appbar.is-compact .greeting {
  display: flex; align-items: baseline; gap: 7px; min-width: 0; margin: 0;
  font-size: 23px; line-height: 1.2; white-space: nowrap;
}
.appbar.is-compact .greeting-words { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.appbar.is-compact .greeting .wave { flex: none; font-size: 20px; }
/* «🇷🇴 Румунія ⌄»: words of the page, 15px, neutral; 4px under the greeting, with a press taller than it looks. */
.picker.plain {
  display: inline-flex; align-items: center; gap: 6px; width: auto; max-width: 100%;
  margin: -2px 0 -6px; padding: 6px 8px 6px 0; border: 0; border-radius: 0; background: none; box-shadow: none;
  font: inherit; color: var(--text-dim); text-align: left;
}
.picker.plain::before, .picker.plain::after { display: none; }
/* Words of the page, not glass: no frost behind them, and the page's own ink in daylight (the glass list in
   tokens.css hands a .picker the light set). */
.picker.plain {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
:root[data-theme="day"] .picker.plain {
  --text: var(--page-ink); --text-dim: var(--page-ink-dim); --text-faint: var(--page-ink-faint);
}
.picker.plain .picker-flag { flex: none; font-size: 16px; line-height: 1; }
.picker.plain .picker-value {
  min-width: 0; font-size: 15px; font-weight: 500; line-height: 20px; color: inherit;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The caret says «a list opens here» (owner, 09.10.2026): the system chevron turned down, larger, in the words'
   own ink, close to them, its lines a little heavier. */
.picker.plain { gap: 4px; }
.picker.plain .picker-flag { margin-right: 2px; }
.picker.plain .picker-caret { color: var(--text-dim); margin-left: 1px; }
.picker.plain .picker-caret svg path { stroke-width: 2.3; }
.picker.plain.open .picker-caret { transform: rotate(-90deg); }
/* The face: 44px, round, a fine neutral line -- no coloured ring, no glow. */
.avatar.home-avatar {
  /* 53px (owner, 09.10.2026: a fifth larger), a little in from the right edge. */
  flex: none; width: 53px; height: 53px; margin-right: 4px; padding: 0; border-radius: 50%;
  border: 1px solid var(--line-strong); background: none; overflow: hidden;
}
:root[data-theme="day"] .avatar.home-avatar { border-color: rgba(20, 22, 27, .14); }
.avatar.home-avatar .avatar-face { font-size: 20px; }
.headtext { flex: 1; min-width: 0; }
.appbar .brand { display: flex; flex-direction: column; line-height: 1.1; margin-bottom: var(--s4); }
.appbar .brand b {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-brand); font-size: 13.5px; font-weight: 700; letter-spacing: .01em;
  /* "SVOIM" is written in whatever the page writes in: white on the dark
     ground, black on the light one -- the logo's white half. */
  color: var(--text);
}
/* "VSIM" is the lit half, in the brand's pink, as on the approved logo
   (owner 14.10.2026). */
.appbar .brand em {
  font-style: normal; color: var(--accent);
}
/* A hairline, so it signs the name rather than decorating it. */
.thinheart { flex: none; margin-bottom: 1px; }
.appbar .brand span { font-size: 10px; color: var(--text-faint); letter-spacing: .16em; text-transform: lowercase; }
/* The gap under the avatar is twice what it was: at six pixels the three
   controls read as hanging off the picture rather than standing on their own
   row, and they are not part of it -- one changes the theme and two change
   what the whole app shows. */
.identity { display: flex; flex-direction: column; align-items: center; gap: 12px; flex: none; }
/* The ring itself lives on .avatar-ring, shared with the profile; this only
   sets the size it takes here and the face's type at that size. */
/* A fifth larger, and the four pixels it used to be pushed down by are given
   back to it: the greeting column still sets the row's height. */
.avatar.avatar-ring {
  width: 62px; height: 62px; flex: none; padding: 2px; border: 0;
}
.avatar .avatar-face { font-size: 21px; }
.switch {
  display: inline-flex; align-items: center; gap: 4px;
  height: 24px; padding: 0 8px; border-radius: var(--r-chip);
  font-size: 10.5px; font-weight: 700; letter-spacing: .03em; color: var(--text-dim);
  background:
    linear-gradient(180deg, var(--frost-film-a) 0%, var(--frost-film-b) 100%),
    var(--frost-base);
  border: 1px solid var(--frost-line);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
}
.switch:active { color: var(--text); }
/* A named selector rather than two letters: the label says what changes and
   the value says what it is now, on the same glass as everything else. */
.picker {
  position: relative; display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: 14px; text-align: left; width: 100%;
  transition: border-color .14s ease;
}
.picker.open { border-color: rgba(255, 63, 146, .45); }
.picker-icon { flex: none; display: inline-flex; color: var(--text-faint); }
.picker-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.picker-text i {
  font-style: normal; font-size: 9.5px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-faint);
}
.picker-text b {
  font-size: 12.5px; font-weight: 650; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.picker-caret { flex: none; display: inline-flex; color: var(--text-faint); transform: rotate(90deg); }
.picker.open .picker-caret { transform: rotate(-90deg); }
.pickers { display: flex; flex-direction: column; gap: 6px; width: 148px; }
/* In the header the two selectors sit on one line and keep exactly the height
   the two switches had before them: the hero's height is not theirs to spend. */
.pickers.compact { flex-direction: row; width: auto; gap: 4px; }
.picker.compact {
  display: inline-flex; align-items: center; gap: 5px; width: auto;
  /* A fifth taller than the switches were: comfortable to hit, and still
     shorter than the greeting column that sets the row's height. */
  height: 30px; padding: 0 11px; border-radius: var(--r-chip);
}
.picker.compact.open { border-color: rgba(255, 63, 146, .5); }
.picker.compact .picker-text { flex-direction: row; align-items: center; gap: 4px; }
.picker.compact .picker-text i {
  font-size: 10.5px; letter-spacing: .04em; text-transform: none; color: var(--text-faint);
}
/* The value carries the same softened white it does in the profile: the two
   selectors are the same control in two places, and were reading as two. */
.picker.compact .picker-text b { font-size: 11.5px; font-weight: 700; letter-spacing: .03em; color: var(--text-soft); }
.picker.compact.open .picker-text b { color: var(--text); }
/* On the profile they are full-width rows, side by side. */
.profile-pickers { flex-direction: row; width: auto; gap: var(--settings-gap); margin: 0; }
/* Quiet next to the avatar and the menu: no leading icon, a small label, the
   value in full, and a caret that only hints. */
/* A fifth taller than the 42px they were, so the pair reads as part of the
   menu above rather than a strip beneath it -- and still short of the menu's
   own 60px row, which carries an icon and a chevron these do not. */
.profile-pickers .picker { flex: 1; min-width: 0; padding: 8px 12px; height: var(--settings-row); }
.profile-pickers .picker-icon { display: none; }
.profile-pickers .picker-text i { font-size: 9.5px; line-height: 1.1; }
/* The same softened white the three menu rows carry: a value that shone
   brighter than the labels above it read as the louder of the two, which is
   the wrong way round. */
.profile-pickers .picker-text b { font-size: 13px; line-height: 1.2; color: var(--text-soft); }
.profile-pickers .picker-caret { color: var(--text-faint); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- screen header (back · title · country) -------------------- */
.screenbar { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s4); }
/* One back button, whoever draws it.  Telegram's sits in the same corner, so
   where Telegram has one the app hides its own rather than stacking two --
   and the spacer that balanced it goes with it, or the title is centred in a
   row with a hole on one side and sits 24px off the middle of the screen. */
.app[data-native-back="true"] .backbtn,
.app[data-native-back="true"] .screenbar .barspacer { display: none; }
.screenbar .titles { flex: 1; text-align: center; }
.screenbar h1 { margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -.01em; }
.screenbar .titles span { font-size: 12px; color: var(--text-dim); }
/* A request's page in Telegram's full screen (owner, 10.10.2026): «Шукають моделей» and its city stand in the band
   Telegram's own Back and ⋯ float in, centred between them and clear of both, and give the page its row back -- the
   picture starts right under the band.  Without the band (Telegram drawing its own bar) the row stays in the page. */
:root[data-tg-band="on"] .screenbar.mreq-bar {
  position: absolute; z-index: 2; left: 0; right: 0;
  top: var(--tg-device-t, 0px); height: var(--tg-band-h, 0px); margin: 0; padding: 0 96px;
  justify-content: center; pointer-events: none;
}
:root[data-tg-band="on"] .screenbar.mreq-bar :is(.backbtn, .barspacer) { display: none; }
:root[data-tg-band="on"] .screenbar.mreq-bar + .mreq-sheet { margin-top: 6px; }
:root[data-tg-band="on"] .screenbar.mreq-bar .titles {
  flex: 0 1 auto; min-width: 0; display: flex; flex-direction: column; align-items: center; line-height: 1.2;
}
:root[data-tg-band="on"] .screenbar.mreq-bar :is(h1, .titles span) {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.iconbtn {
  width: 36px; height: 36px; flex: none; border-radius: 50%;
  display: grid; place-items: center; color: var(--text-dim);
  background: var(--card); border: 1px solid var(--line);
}
.iconbtn:active { background: var(--card-hi); }
/* Balances a header row against its back button, and draws nothing. */
.barspacer { width: 36px; height: 36px; flex: none; }

/* ---------- greeting + search ---------------------------------------- */
/* The one line on the screen that is addressed to a person, set in the brand's
   display face rather than the interface one. Not a decorative font — the same
   face as the wordmark, given room to read as typography. */
.greeting {
  margin: 0 0 5px; font-family: var(--font-brand);
  font-size: 25px; font-weight: 700; line-height: 1.14; letter-spacing: -.03em;
  text-wrap: balance;
}
.greeting .wave { font-family: var(--font); font-size: 21px; }
.greeting-sub { margin: 0; color: var(--text-dim); font-size: 13.5px; letter-spacing: 0; }
.name-fold {
  font: inherit; padding: 0; text-align: left;
  border-bottom: 1px dashed rgba(255, 255, 255, .28);
}
.name-fold[aria-expanded="true"] { border-bottom-color: transparent; }

/* Frosted glass: the page's own light shows through it instead of a flat fill
   sitting on top of the page. The button inside stays the accent — it is the
   one thing here that acts. */
/* The bottom bar's own glass, in a field: the same capsule, the same two
   panes, the same white edge.  Its filter cannot live on the element -- an
   element that filters its backdrop becomes a backdrop root, and the panes
   inside would filter their parent, which paints nothing -- so the element
   only holds the shape, and `isolation` keeps the panes from falling behind
   the rest of the page. */
.searchbar {
  /* Ringed in the accent rather than in white: the one piece of glass on the
     screen that is asking to be typed into.  The ring the pane already draws
     is recoloured rather than a second line laid outside it, which would have
     read as a doubled edge. */
  --glass-ring: var(--action-line);
  position: relative; isolation: isolate;
  display: flex; align-items: center; gap: var(--s2);
  /* One search on every screen (owner, 09.10.2026): Home's field -- 42px, a 34px button and 3px round it inside
     the brand's thin pink line, on the quiet surface Home's models card wears; no glass, no glow. */
  border-radius: 999px; padding: 3px 3px 3px 16px; margin-bottom: var(--s3);
  min-height: 42px;
  background: var(--soft-surface); border: 1px solid var(--action-line);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
}
.searchbar::before, .searchbar::after { display: none; }
:root[data-theme="day"] .searchbar { box-shadow: 0 1px 2px rgba(15, 20, 30, .05); }
.searchbar input {
  flex: 1; min-width: 0; border: 0; outline: 0; background: none;
  /* The words keep 16px (iOS zooms a page whose focused field is smaller) and give up only their own padding. */
  font: inherit; color: var(--text); padding: 0; line-height: 20px;
  /* iOS zooms the whole page when a focused field is smaller than 16px. */
  font-size: 16px;
}
.searchbar input::placeholder { color: var(--placeholder); }
/* The round button at the end of a search bar is lit, like the plus in the
   middle of the bottom bar: both are the one thing a person came to that bar
   to press.  Written once for the pair, so the two cannot drift. */
.searchbar .go, .searchgo {
  display: grid; place-items: center; border-radius: 50%; flex: none;
  color: #fff;
  background: var(--neon-face);
  box-shadow: var(--neon-accent);
}
.searchbar .go, .searchgo { width: 34px; height: 34px; }
.searchbar .go svg, .searchgo svg { width: 16px; height: 16px; }
/* Typed into: the line a shade stronger, nothing else moves. */
.searchbar:focus-within { border-color: var(--accent); }
/* Home's search is every screen's search now (owner, 09.10.2026): .searchbar above. */

/* ---------- one author's page (owner, 09.10.2026) ------------------------------------------------------------ */
/* Who they are: the listings' author card a size larger -- the face 64px, the name, kind · city, the stars (a press
   opens the reviews) -- then since when and the languages in its small rows. */
.authorcard { margin-bottom: var(--s3); }
.authorcard-head { align-items: flex-start; padding: 14px var(--s4) 8px; }
.provider-avatar.authorcard-face { width: 64px; height: 64px; font-size: 24px; }
.authorcard .provider-id > b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.authorcard-meta { display: inline-flex; align-items: center; gap: 3px; flex-wrap: wrap; font-size: 12.5px; color: var(--text-dim); }
.authorcard-meta svg { flex: none; }
.authorcard-rating { padding: 0; border: 0; background: none; font: inherit; font-size: 12.5px; cursor: pointer; }
.authorcard-facts { gap: 4px; border-top: 0; padding: 0 var(--s4) 12px calc(var(--s4) + 64px + 13px); }
.authorcard-facts .fact { align-items: flex-start; gap: 6px; font-size: 12px; line-height: 1.35; }
.authorcard-facts .fact svg { margin-top: 1.5px; }
/* The two tabs: the panel's own tabs (.promotabs), each with how many. */
.authortabs { margin-bottom: var(--s3); }
.authortabs .promotab { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.authortab-count { font-size: 11.5px; font-weight: 600; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.authorgroup { margin-bottom: var(--s4); }
.authorgroup-title {
  display: flex; align-items: baseline; gap: 6px; margin: 0 0 var(--s2);
  font-size: 15px; font-weight: 700; letter-spacing: -.01em;
}
.authorgroup .mreq-list { margin-bottom: 0; }
.authorgroup-more { width: 100%; margin-top: var(--s2); }
/* A review on an author's page says what it was about: the listing one press away, or that it is gone. */
.authorreviews-sum { display: flex; align-items: center; gap: 5px; margin: 0 0 var(--s2); font-size: 13px; color: var(--text-dim); }
.authorreviews-sum b { color: var(--text); font-weight: 700; }
.review-about { display: block; margin-top: 4px; font-size: 12px; line-height: 1.35; }
.linkbtn.review-about-go {
  display: inline-flex; align-items: center; gap: 3px; max-width: 100%; padding: 0;
  font-size: 12px; color: var(--text-dim); text-align: left;
}
.review-about-go b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 600; }
.review-about-go svg { flex: none; }
.review-about-gone { color: var(--text-faint); }
/* In daylight the review's date and its letter keep the page's ink on this light panel (the shared .review styles
   write them in the dark theme's quiet ink there) -- on the author's page only. */
:root[data-theme="day"] .authorreviews .review-date { color: var(--page-ink-faint); }
:root[data-theme="day"] .authorreviews .review-avatar { color: var(--ink); }

/* The request a link was about, in «Мої оголошення»: its frame lit for a moment, neutral (owner, 09.10.2026). */
.mineslot { border-radius: var(--r-card); transition: box-shadow .4s ease; }
.mineslot.is-lit { box-shadow: 0 0 0 2px var(--text-faint); }

/* ---------- category grid -------------------------------------------- */
.tiles { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3); margin-bottom: var(--s4); }
.tile {
  position: relative; overflow: hidden; text-align: left;
  border-radius: var(--r-tile); padding: 10px var(--s4) var(--s3);
  /* Sixteen per cent shorter than it was, to leave room for the rail beneath
     it.  Nothing is cut: this is the floor of a tile whose subtitle wraps to
     two lines, and a third line grows the tile rather than clipping it. */
  min-height: 104px;
  /* Two rows: the glyph is centred in whatever space is left above the label,
     rather than pinned to the top edge with the text crowding it. */
  display: grid; grid-template-rows: 1fr auto; row-gap: 6px;
  border: 1px solid var(--line);
  background-image: linear-gradient(150deg, var(--a) 0%, var(--b) 100%);
  box-shadow: 0 10px 26px rgba(0, 0, 0, .38);
  transition: transform .14s ease;
}
/* The approved photographs already carry each section's colour, so they get no
   wash over them -- only enough of a scrim at the foot of the tile to keep the
   label legible. */
.tile[data-photo] {
  background-size: cover; background-position: 72% 50%; background-repeat: no-repeat;
}
/* Daylight across the tile, and the shading the label stands on underneath
   it.  The shading used to start at a tenth of black at the very top, which
   is a tile in shadow from end to end; it starts at nothing now and gathers
   towards the foot, where the words actually are. */
.tile::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(124% 88% at 14% -12%,
      rgba(255, 255, 255, .22) 0%, rgba(255, 255, 255, .06) 36%, transparent 68%),
    linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, .32) 56%, rgba(0, 0, 0, .58) 100%);
}
/* The six on the home screen all carry a photograph, so this rule is the one
   that reaches them -- it replaces both of the layers above rather than adding
   to them.  No light laid over the picture: it has its own, and a white wash
   across it only greyed it.  What is left is the shading the label stands on,
   which stays out of the way until the foot. */
.tile[data-photo]::after {
  background: linear-gradient(180deg,
    rgba(13, 15, 18, 0) 0%, rgba(13, 15, 18, .12) 50%, rgba(13, 15, 18, .56) 100%);
}
.tile > * { position: relative; z-index: 1; }
.tile:active { transform: scale(.978); }
.tile .glyph {
  align-self: center; justify-self: start;
  width: 28px; height: 28px; border-radius: 9px;
  display: grid; place-items: center; color: #fff;
  background: rgba(9, 11, 14, .42);
  border: 1px solid rgba(255, 255, 255, .13);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
/* A column, not inline: the wrapper was left inline-level, so the label and
   its subtitle ran together on one line with no space between them. Scoped to
   .tile-text as well, because .tile span also matches the glyph. */
.tile-text { display: flex; flex-direction: column; gap: 2px; min-height: 34px; justify-content: flex-end; }
.tile-text b { font-size: 16px; font-weight: 700; letter-spacing: -.01em; line-height: 1.15; color: var(--ink); }
.tile-text > span { font-size: 11.5px; line-height: 1.2; color: rgba(255, 255, 255, .76); }
/* The count sits on a photograph, so it carries its own darkened, blurred
   plate: without one it disappeared into whatever was behind it. */
.tile em {
  position: absolute; top: var(--s3); right: var(--s3); z-index: 2;
  font-style: normal; font-size: 11px; font-weight: 700; line-height: 1;
  min-width: 22px; text-align: center; font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, .92); padding: 4px 7px; border-radius: var(--r-chip);
  background: rgba(9, 11, 14, .52);
  border: 1px solid rgba(255, 255, 255, .12);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.tile-work { --a: var(--c-work-a); --b: var(--c-work-b); }
.tile-housing { --a: var(--c-housing-a); --b: var(--c-housing-b); }
.tile-services { --a: var(--c-services-a); --b: var(--c-services-b); }
.tile-market { --a: var(--c-market-a); --b: var(--c-market-b); }
.tile-info { --a: var(--c-info-a); --b: var(--c-info-b); }
.tile-community { --a: var(--c-community-a); --b: var(--c-community-b); }

/* ---------- wide row card -------------------------------------------- */
/* ---------- what is newest here ------------------------------------- */
/* A rail between the six sections and the banner.  The card is the catalogue
   card at a third of the width -- same graphite, same glass, same blue-pink
   edge -- so the home screen introduces no second visual language. */
.rail-section { margin-bottom: var(--s4); }
.rail-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); margin-bottom: var(--s3);
}
.rail-title { margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -.015em; }
.rail-all {
  display: flex; align-items: center; gap: 3px; flex: none;
  font-size: 12.5px; color: var(--text-dim); padding: 4px 0;
}
.rail-all:active { color: var(--text); }
.rail {
  display: flex; gap: 9px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  /* The rail runs to both edges of the screen while the page keeps its
     margins, so the card at each end is cut by the screen rather than by a
     border a few pixels inside it. */
  margin: 0 calc(var(--page-side) * -1); padding: 2px var(--page-side);
}
.rail::-webkit-scrollbar { display: none; }
.rail .gcard { flex: 0 0 38%; scroll-snap-align: start; }
/* A swipe that starts on a card's words moves the row as smoothly as one on its picture (owner, 15.10.2026): no
   text selection or callout waiting on the finger, and the press does not move the card under a pan. */
.rail .gcard { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.rail .gcard:active { transform: none; }
.gcard.skeleton { height: 270px; }
/* The figure is the app's own pink, the same as on the listing it opens: a
   price is what a reader is looking for on a card, and white made it one more
   line of writing among the rest. */
.gcard-price {
  display: flex; align-items: flex-start; gap: 5px; font-style: normal;
  white-space: nowrap; color: var(--price-ink);
}
.gcard-amount {
  /* Smaller than the reference's 25-27px, because the card is 112px wide and
     the widest real price -- "від 150 RON" -- has to fit beside its currency
     without either wrapping, and a fifth smaller again by eye. */
  font-size: 17.5px; font-weight: 600; line-height: 21px; letter-spacing: -.02em;
  color: inherit; font-variant-numeric: tabular-nums;
}
/* Level with the top of the digits, not their middle.  Both boxes start at
   the same line with align-items: flex-start; what remains is the half-leading
   each carries, which differs by half a pixel between 17.5px and 11px -- so
   the nudge is 1px, not the 3px that read as centred. */
.gcard-currency {
  font-size: 11px; font-weight: 500; line-height: 14px; letter-spacing: .3px;
  color: inherit; opacity: .78; margin-top: 1px; white-space: nowrap; flex: none;
}
/* "від", and its like: read at the currency's size on the same top line, so
   the figure stays the figure and the row still fits the card. */
/* Whatever the words, the currency keeps its place beside the figure rather
   than being pushed onto a line of its own. */
.gcard-price { min-width: 0; }
/* And the figure says so too, rather than running under the card's edge: a
   price that had somehow grown long was cut there with nothing to show it. */
.gcard-amount { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A service whose price is «За домовленістю»: words, not a figure, so they are
   set the size a line of words is -- at the figure's size they would not fit
   the card -- in the price's own colour, and cut rather than wrapped. */
.gcard-words {
  min-width: 0; font-size: 12.5px; font-weight: 600; line-height: 16px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: inherit;
}

.rowcard {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  border-radius: var(--r-card); padding: var(--s3) var(--s4);
}
.rowcard:active { background: var(--card-hi); }
.rowcard .flag { width: 30px; height: 30px; flex: none; border-radius: 9px; display: grid; place-items: center; background: rgba(9, 11, 14, .42); border: 1px solid rgba(255, 255, 255, .10); }
.rowcard .body { flex: 1; min-width: 0; }
.rowcard b { display: block; font-size: 14px; font-weight: 650; }
.rowcard span { display: block; font-size: 12px; color: var(--text-dim); }
.rowcard .chev { width: 26px; height: 26px; flex: none; border-radius: 50%; display: grid; place-items: center; background: rgba(255, 255, 255, .06); color: var(--text-faint); }

/* ---------- filter chips --------------------------------------------- */
.chips { display: flex; gap: var(--s2); overflow-x: auto; margin-bottom: var(--s4); padding-bottom: 2px; scrollbar-width: none; }
.chips::-webkit-scrollbar { display: none; }
.chip {
  flex: none; border-radius: var(--r-chip); padding: 7px 14px;
  font-size: 13px; font-weight: 550; color: var(--text);
  white-space: nowrap;
  background:
    linear-gradient(115deg, var(--glass-1) 0%, var(--glass-2) 52%, var(--glass-3) 100%),
    rgba(24, 28, 40, .42);
  border: 1px solid var(--glass-line);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
}
/* Chosen: the chip turns white and its word goes dark grey.  Both are written
   here rather than read from the tokens, because this pair is an inversion of
   the surface scale and has to hold whichever ground the page is on.  It used
   to put white writing on that white -- the block that keeps a dark chip's
   writing light outranked this rule until that block gave up its weight. */
/* `:root` only to outweigh the daylight rules, which are scoped to the theme
   and would otherwise tie with this one and win on source order alone. */
:root .chip[aria-pressed="true"] {
  background: #f4f5f7; color: #14161a; border-color: transparent; font-weight: 650;
}

/* ---------- listing cards -------------------------------------------- */
.feed { display: flex; flex-direction: column; gap: var(--s3); }
.tag {
  font-size: 10.5px; color: var(--text-dim); background: var(--card-hi);
  border: 1px solid var(--line); border-radius: var(--r-chip); padding: 3px 8px;
}
.heart {
  position: absolute; top: 10px; right: 10px; width: 30px; height: 30px;
  border-radius: 50%; display: grid; place-items: center;
  color: var(--text-faint); background: rgba(0, 0, 0, .28);
}
.heart[aria-pressed="true"] { color: var(--accent); }

/* ---------- detail ---------------------------------------------------- */
.gallery { display: flex; gap: var(--s2); overflow-x: auto; margin-bottom: var(--s4); scrollbar-width: none; }
.gallery::-webkit-scrollbar { display: none; }
.gallery img, .gallery > span {
  width: 68%; max-width: 260px; flex: none; height: 190px;
  object-fit: cover; border-radius: var(--r-card); display: block;
}
.media-missing { background: var(--card); border: 1px solid var(--line); }
.headline { display: flex; flex-direction: column; gap: 5px; margin-bottom: var(--s3); }
.detail-title { margin: 0; font-size: 21px; font-weight: 800; letter-spacing: -.025em; line-height: 1.14; text-wrap: balance; }
/* A block, and the word over it.  Air above the word and little under it, so
   the eye takes the heading and what belongs to it as one thing -- which is
   what tells a reader where a block began and where it ended, without a rule
   drawn between them. */
.section { margin: 0 0 var(--s6); }
.section + .section { margin-top: var(--s6); }
/* The listing's side column (ListingDetail) is a plain box on a phone: the description after it keeps the gap it had
   from the block it followed. */
.detail-side.has-sections + .section { margin-top: var(--s6); }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); }
.linkbtn { font-size: 12.5px; color: var(--accent); padding: 0 0 var(--s3); }
/* Between where and what.  The city and the sections share a line now, and
   without this "Усі" sat close enough to the city to read as "all cities". */
.chips-wrap { flex-wrap: wrap; overflow: visible; }
/* Real headings rather than small grey labels: they were reading as system
   chrome instead of as the structure of the listing. */
.section h2 {
  margin: 0 0 var(--s3); font-size: 13px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint);
}
/* One heading here names a block the way a card's own label does, so it is
   written in the same ink rather than in the faint one the others take.  It
   stays a heading above its block -- same size, same weight, same spacing --
   and borrows nothing but the colour.  The rest of the headings on these
   screens name blocks that carry their own label inside, and are left alone. */
.section h2.as-label { color: var(--text); }
/* Graphite with a hairline. The section's colour is spent on the chip and on
   the cards in its feed; spending it on every surface here flattened the
   hierarchy instead of building one. */
.panel {
  border-radius: var(--r-card); overflow: hidden;
  border: 1px solid var(--line);
}
.panel .line { display: flex; align-items: center; gap: var(--s2); padding: 9px 12px; border-top: 1px solid var(--line); }
.panel .line:first-child { border-top: 0; }
.panel .cardlabel + .line { border-top: 0; }
/* And the price list's own rows, which are `.priceline` rather than `.line`:
   with the label above them the first row is no longer the first child, so
   without this its top border doubles the line under the label. */
.panel .cardlabel + .priceline { border-top: 0; }
/* And where a card carries its own heading, what is under it is secondary to
   it.  Only there: a panel with no heading of its own -- the guide's rows,
   the profile's -- is the only thing on its screen and stays as it is.  The
   price itself is untouched, because it has an accent of its own. */
.panel .cardlabel ~ .line b,
.panel .cardlabel ~ .priceline .priceline-name b { color: var(--text-dim); }
.panel .line b { flex: 1; min-width: 0; font-weight: 600; font-size: 13.5px; }
/* The value keeps its own line and gives way with three dots rather than
   being sliced by the panel's edge, which hides that anything was lost. */
.panel .line span {
  color: var(--text-dim); font-size: 12.5px; font-variant-numeric: tabular-nums;
  white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.prose { margin: 0; white-space: pre-wrap; font-size: 14px; color: var(--text-soft); }

/* ---------- buttons --------------------------------------------------- */
/* The three strong actions -- the one at the foot of a panel, the one that
   writes a listing, and the one that opens a chat -- are all said the way the
   "НОВЕ" mark is said: the accent as the writing on a plate, not a page of it
   under white.  One pair of tokens between them, so the three cannot drift. */
.cta {
  display: flex; align-items: center; justify-content: center; gap: var(--s2);
  width: 100%; border-radius: var(--r-chip); padding: 15px 20px;
  font-size: 15px; font-weight: 650; color: var(--action-ink);
  background: var(--glass-sheen), var(--action-face);
  border: 1px solid var(--action-line);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: var(--action-glow), var(--lift-far);
}
.cta:active { filter: brightness(.94); }
.cta[disabled] { opacity: .55; box-shadow: none; }
.cta-ghost { background: var(--card); border: 1px solid var(--line-strong); color: var(--text); box-shadow: none; }
/* And the other way from the glass: lit rather than tinted, with the writing
   in white on it.  The same lit face the plus in the bottom bar and the button
   at the end of the search field already wear, and the same white -- the
   accent is one object in both themes, so this reads alike in both.  A
   modifier beside its twin rather than a colour a screen gives itself. */
.cta-lit {
  background: var(--neon-face); border-color: transparent; color: #fff;
  box-shadow: var(--neon-accent);
}
/* Saving and writing sit together, but only one of them is a call to action:
   the heart is glass, the contact is the accent. */
.floating-cta {
  /* Wrapping, so the shown contact can take a whole row above the buttons. */
  position: fixed; z-index: 6; flex-wrap: wrap; row-gap: var(--s3);
  left: 50%; transform: translateX(-50%);
  width: min(520px, 100%);
  padding: 0 var(--s4);
  bottom: calc(var(--nav-h) + var(--nav-floor) + var(--nav-lift) + var(--s3));
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  pointer-events: none;
}
.floating-cta > * { pointer-events: auto; }
/* A quiet word under a field: what is being looked up, or why nothing was
   found.  Never an error -- none of this stops anybody publishing. */
.fieldnote {
  margin: 6px 2px 0; font-size: 12px; line-height: 1.35; color: var(--text-dim);
}
/* The places an address could mean.  Shown only when there is a choice worth
   making, and the one that will be used is lit. */
.addrpicks { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.addrpick {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 9px 12px; border-radius: var(--r-chip); text-align: left;
  font-size: 12.5px; line-height: 1.3; color: var(--text);
  background: var(--glass-sheen), var(--glass-plate-soft);
  border: 1px solid var(--edge-line);
}
.addrpick > span:last-child { min-width: 0; }
.addrpick-mark { display: grid; place-items: center; flex: none; color: var(--text-dim); }
.addrpick.on { color: var(--action-ink); border-color: var(--action-line); box-shadow: var(--action-glow); }
.addrpick.on .addrpick-mark { color: var(--action-ink); }
.addrfound { display: flex; gap: 6px; align-items: flex-start; }
.addrfound-mark { flex: none; display: grid; place-items: center; margin-top: 2px; color: var(--ok); }

/* Passing on and keeping, together at the near end of the bar. */
.ctaside {
  display: flex; align-items: center; gap: var(--s3); flex: none;
  transition: opacity .18s ease, transform .22s cubic-bezier(.2, .9, .3, 1),
              width .22s cubic-bezier(.2, .9, .3, 1);
}
/* Out of the way while the ways are out.  Three round buttons and a pill do
   not fit across a phone, and the pair is worth less for the second or two
   somebody is choosing how to reach an author. */
.ctaside.is-away {
  width: 0; opacity: 0; overflow: hidden;
  transform: translateX(-14px) scale(.86); pointer-events: none;
}
/* The ways an author gave, out of the button that asked for them and along
   the bar towards the thumb.  Right-aligned so the strip grows leftwards from
   the pill; it can shrink and scroll, because an author may have given more
   ways than a narrow phone has room for. */
.contactways {
  /* All the room the pair and the pill leave, so the strip ends against the
     pill it came out of rather than floating in the middle of the bar. */
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; justify-content: flex-end;
  gap: var(--s3); row-gap: var(--s2);
  /* Wrapping upwards rather than scrolling sideways.  A scroll container is
     its own backdrop root, and the glass on these circles then filters an
     empty one -- which painted the whole strip as a grey rectangle behind
     them.  Three ways is the most a form can write anyway; anything the bot
     wrote with more goes onto a second line above. */
  flex-wrap: wrap-reverse;
}
.contactway {
  /* How far down the queue this one is.  React sets it per way, counting from
     the pill, so the nearest circle leaves first. */
  --i: 0;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  flex: none; text-decoration: none;
  animation: way-out .24s cubic-bezier(.2, .9, .3, 1) both;
  animation-delay: calc(var(--i) * 45ms);
}
/* The mark on the app's own glass, round, so no lit lip: on a circle that 1px
   band is a straight chord laid across the curve. */
.contactway > i {
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--action-ink);
  background: var(--glass-face), var(--action-face);
  border: 1px solid var(--action-line);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: var(--action-glow), var(--lift-round);
}
.contactway:active > i { transform: translateY(1px) scale(.97); }
/* Each messenger in its own colour, here and on the form that writes them.
   The shape alone was never doing all the work: what people recognise these
   four by is green, purple, blue and pink, and a brand mark in the wrong
   colour is a different brand.  Only the four that have one -- a phone, an
   e-mail and "something else" are nobody's. */
.contactway.is-telegram > i, .chip.is-telegram > svg, .glassfield-icon.is-telegram { color: var(--brand-telegram); }
.contactway.is-whatsapp > i, .chip.is-whatsapp > svg, .glassfield-icon.is-whatsapp { color: var(--brand-whatsapp); }
.contactway.is-viber > i, .chip.is-viber > svg, .glassfield-icon.is-viber { color: var(--brand-viber); }
.contactway.is-instagram > i, .chip.is-instagram > svg, .glassfield-icon.is-instagram { color: var(--brand-instagram); }
.contactway.is-facebook > i, .chip.is-facebook > svg, .glassfield-icon.is-facebook { color: var(--brand-facebook); }
/* Named, because this app draws one line weight and carries nobody's logo:
   two messengers would otherwise be the same circle twice. */
.contactway > span {
  font-size: 10px; font-weight: 600; line-height: 1;
  color: var(--chrome-text); text-shadow: 0 1px 2px rgba(0, 0, 0, .45);
}
/* The same glass as the pill it stands opposite, and as the bar under both:
   a plate of its own left it darker on one side of the screen than the other,
   which is the whole of what a reader notices about a pair. */
.fav-fab {
  width: 50px; height: 50px; flex: none; border-radius: 50%;
  display: grid; place-items: center; color: var(--text);
  /* The bar's own plate rather than a strong action's: this is a toggle, not
     a call to act, and in daylight an action's plate is washed in the brand,
     which would have said "saved" before anybody had saved anything. */
  background: var(--glass-face), var(--glass-plate-soft);
  border: 1px solid var(--line-strong);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: var(--lift-round);
}
/* Saved is the accent, and a halo to say so: the one state of this button
   that is worth seeing from across the screen. */
.fav-fab.on {
  color: var(--accent); border-color: rgba(255, 63, 146, .34);
  box-shadow: var(--action-glow), var(--lift-round);
}
.fav-fab:active { filter: brightness(1.12); }
.pill-cta {
  display: inline-flex; align-items: center; gap: 8px;
  height: 50px; padding: 0 20px; border-radius: var(--r-chip);
  font-size: 14.5px; font-weight: 700; color: var(--action-ink);
  background: var(--glass-sheen), var(--action-face);
  border: 1px solid var(--action-line);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: var(--action-glow), 0 6px 18px rgba(0, 0, 0, .36);
}
.pill-cta:active { filter: brightness(.94); }
.pill-cta[disabled] { opacity: .6; box-shadow: none; }
/* Room for the pill, so the end of a listing can still be read. */
.page[data-cta] { padding-bottom: calc(var(--nav-h) + var(--nav-floor) + var(--nav-lift) + 84px); }

/* ---------- states ---------------------------------------------------- */
.empty { text-align: center; padding: var(--s8) var(--s4); color: var(--text-dim); }
.empty .mark { width: 56px; height: 56px; margin: 0 auto var(--s4); border-radius: 18px; display: grid; place-items: center; background: var(--card); border: 1px solid var(--line); color: var(--text-faint); }
.empty b { display: block; color: var(--text); font-size: 15px; margin-bottom: var(--s1); }
.empty p { margin: 0 0 var(--s4); font-size: 13px; }
.skeleton { border-radius: var(--r-card); background: linear-gradient(90deg, var(--card) 25%, var(--card-hi) 37%, var(--card) 63%); background-size: 400% 100%; animation: shimmer 1.3s ease infinite; }
.skeleton-listing { height: 142px; }
.skeleton-tile { height: 104px; border-radius: var(--r-tile); }
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton, .shot.loading { animation: none; } }

/* ---------- bottom navigation ---------------------------------------- */
/* A sheet of glass over the feed rather than a plate on top of it: what
   scrolls under stays just visible, and softens a little before it gets
   there.  Two things follow, and neither is optional.

   The bar itself carries no backdrop-filter.  An element that filters its
   backdrop becomes a backdrop root, and the two panes below would then be
   filtering their own parent, which paints nothing -- a flat tint, and no
   error to read.  The same trap closes the day anyone gives .bottomnav, .app
   or .frame a filter, an opacity below 1, a mask, a clip-path, a blend mode,
   or a will-change naming one of those.

   The pane sits at a negative z-index inside the bar's own stacking context:
   under the five buttons, which are in flow and so paint after it, and over
   the page, which is a stacking context two levels down. */
.bottomnav {
  /* A capsule that floats clear of the phone's edges, the shape Telegram's
     own controls take at the top of this same screen.  `margin: auto` against
     two fixed sides is what centres it once max-width bites on a tablet. */
  position: fixed; z-index: 5;
  left: var(--nav-inset); right: var(--nav-inset); margin: 0 auto; max-width: 460px;
  bottom: calc(var(--nav-floor) + var(--nav-lift));
  height: var(--nav-h);
  border-radius: 999px;
  display: grid; grid-template-columns: repeat(5, 1fr);
  align-items: center;
  /* The same plate the profile's own buttons stand on.  The material was
     always one rule -- the same sheen, the same frost, the same hairline --
     and only the plate under it differed, because the bar is a sibling of
     `.page` and never saw the profile's own value. */
  --glass-plate: var(--glass-plate-soft);
  /* No shadow under it, for now: a 28px dark halo between the capsule and the
     floor of the page read as another blurred layer down there. */
}
/* A phone keyboard is open (App.tsx marks the root while a field that takes
   words has focus on a touch screen): the bar would float up on top of the
   keyboard and cover the lines just above the field.  Gone until the field
   lets go -- `display`, not opacity, so the bar's glass stays untouched. */
:root[data-typing] .bottomnav { display: none; }
/* A block inside a screen -- a listing's facts, an event's, a price list --
   is one thing written once: the section's own glass, a plain hairline and a
   lit lip along the top.  It used to be the dark pane, which stood beside the
   event's blue block looking like a different app. */
.panel {
  background: var(--glass-1);
  border: 1px solid var(--edge-line);
  box-shadow: inset 0 1px 0 var(--edge-hi);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--text);
}

/* The pane: one sheet, filling the capsule.  It was two for a while -- a
   sharper one at the edge and a frosted one faded in towards the middle --
   and however softly the second was faded, the eye still found its outline
   and read the bar as two frames laid on one another. */
.bottomnav::after,
.keepcard::after,
.toast::after,
.voicetoast::after,
.voicepop::after,
.searchbar::after,
.gcard::after,
.rowcard::after,
.addrow::after,
.cardframe::after,
.profile-line::after,
.profile-pickers .picker::after,
.picker.compact::after,
.daylight::after,
.themeswitch::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none; border-radius: inherit;
  /* The lit edge and the sheen down the face, from the one declaration the
     panels and the strong actions read as well. */
  background: var(--glass-sheen), var(--glass-plate);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow:
    /* The outline, lit the whole way round rather than only across the top:
       a hairline at the edge and a little of its light bleeding inwards. */
    inset 0 0 0 1px var(--glass-ring),
    inset 0 0 14px rgba(255, 255, 255, .05),
    inset 0 -14px 22px -20px rgba(0, 0, 0, .7);
}
/* Chromium will not filter a backdrop from a pane sitting at a negative
   z-index, and WebKit will.  On iOS the bar is glass; on Android it was its
   plate and nothing else -- which at seven per cent white is nothing at all.
   So there, and only there, the filter goes on the bar itself, where Chromium
   does honour it, and comes off the pane.  Both at once would make the bar a
   backdrop root and leave the pane filtering its own parent, which paints
   nothing; that is the trap the note above is about, and this is the one
   arrangement that does not spring it. */
:root[data-engine="blink"] .bottomnav {
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  /* And a plate thinner again by a fifth.  The two engines are only now
     drawing the same thing at all, and at the far heavier radius Blink needs
     to match WebKit the bar comes out the more solid of the two.  iPhone is
     right as it is, so this is said where only Blink hears it. */
  --glass-plate: var(--glass-plate-bar);
}
:root[data-engine="blink"] .bottomnav::after {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* The same arrangement for the two plates that share the bar's material (owner, 12.10.2026). */
:root[data-engine="blink"] :is(.keepcard, .toast, .voicetoast, .voicepop) {
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  --glass-plate: var(--glass-plate-bar);
}
:root[data-engine="blink"] :is(.keepcard, .toast, .voicetoast, .voicepop)::after {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* The action at the foot of a filter, half again the size it is on WebKit.
   Blink's blur at the radius that matches WebKit's leaves a livelier ground
   under the panel, and at 14px the words were being read against what is
   behind the panel rather than against the panel.  iPhone is right as it is. */
:root[data-engine="blink"] .filter-apply { font-size: 18.2px; }
/* Nothing to see through: the pane goes back to being a plate, rather than
   leaving the icons standing on whatever photograph is under them. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bottomnav::after,
  .keepcard::after,
  .toast::after,
  .voicetoast::after,
  .voicepop::after,
  .searchbar::after,
  .profile-line::after,
  .profile-pickers .picker::after,
  .picker.compact::after,
  .daylight::after,
  .themeswitch::after { background: rgba(16, 19, 24, .94); }
}
.bottomnav button {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  /* White, like the plus in the middle.  The shadow stays: white on glass
     needs something under it when a pale photograph slides past. */
  gap: 4px; color: var(--chrome-text); font-size: 10px; line-height: 1; padding: 6px 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .45);
}
.bottomnav button i { display: grid; place-items: center; height: 22px; }
.bottomnav button[aria-current="page"] { color: var(--accent); }
.bottomnav .add i {
  width: 48px; height: 48px; border-radius: 50%; display: grid; place-items: center;
  /* Its layout height is the 22px the other four spend, the rest given back
     as margin, so all five labels sit on one line.  Then `top` lifts what is
     painted without moving what is measured: the ball rises out of the
     capsule and takes nothing with it. */
  margin: -13px 0; position: relative; top: -12px;
  background: var(--neon-face);
  /* Lit rather than merely coloured, and tightly: a wide glow under it
     washes the glass it sits on, which is what had to be taken away before. */
  box-shadow: var(--neon-accent);
}
.bottomnav .add[aria-current="page"] span { color: var(--accent); }

/* ---------- catalogue search and filters ------------------------------ */
/* The same glass as the home screen's search, a little shorter: this one
   sits above a list rather than opening a screen. */
.catalog-search .iconbtn { width: 30px; height: 30px; background: none; border: 0; color: var(--text-faint); }
/* Only where the date says so: a badge that is always on says nothing.
   The pill "На перевірці" wears in Мої оголошення, in pink: the same shape,
   the same size of word, the same hairline and faint wash of its own colour.
   It differs in one thing, because it sits on a photograph rather than on the
   app's own ground: its plate is the heart's, the same translucent black that
   button carries a few pixels to its right, so the two overlays on one
   picture read as one pair.  The word takes the lighter pink, which is what
   holds against a photograph. */
.newbadge {
  /* A seventh shallower than the state pill it copies, because the mark sits
     on a photograph, where a shorter pill takes less of it.  Its height is
     stated and the word centred in it by the layout -- held instead by
     padding and the font's own idea of a line, the word sat off centre and
     iOS and Android disagreed about where.
     Its top edge is the heart's top edge -- the same 8 from the top of the
     picture -- so the two start on one line.  It is the shorter of the pair,
     and hangs from that line rather than being stretched to the other's
     depth. */
  position: absolute; top: 8px; left: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  height: 17.5px; padding: 0 9px; border-radius: 999px;
  font-size: 10.5px; font-weight: 650; letter-spacing: .02em; line-height: 1;
  color: var(--accent-hi);
  border: 1px solid var(--action-line);
  /* Its own plate, lighter than an action's: a mark always stands on a
     photograph, and a photograph is dark enough on its own. */
  background: rgba(0, 0, 0, .38);
}

/* A filter grows out of the chip that carries it, rather than rising from the
   foot of the screen where the reader was not looking. */
.popover { position: fixed; inset: 0; z-index: 25; }
/* Only a light shade, and no blur at all: blurring the whole page took the
   chip the panel grew out of with it, and a dropdown that comes from nowhere
   is worse than one read against a busy page.  What needs putting out of
   focus is the strip beneath the panel itself, which the panel does. */
.popover-backdrop { position: absolute; inset: 0; background: rgba(6, 8, 11, .3); }
/* The bar's own glass, on the panels that open over the page: the same lit
   lip along the top, the same sheen down the face, the same frost and the
   same plate.  Written out rather than shared with the bar's pane, because
   that pane sits at a negative z-index inside its parent and these filter
   from themselves -- the arrangement differs, the material must not.  A
   guard keeps the two in step.

   The panel keeps a drop shadow the bar has no need of: the bar stands at the
   edge of the screen and these float in the middle of one. */
.popover-panel,
.sheet > section {
  color: var(--text);
  position: absolute; overflow-y: auto; padding: 6px;
  border-radius: 16px; border: 1px solid transparent;
  --glass-plate: var(--glass-plate-soft);
  background: var(--glass-sheen), var(--glass-plate);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow:
    inset 0 0 0 1px var(--glass-ring),
    inset 0 0 14px rgba(255, 255, 255, .05),
    inset 0 -14px 22px -20px rgba(0, 0, 0, .7),
    0 1px 3px rgba(3, 5, 12, .5), 0 16px 40px rgba(3, 5, 12, .46),
    0 10px 26px -8px var(--edge-pool-cool), 0 16px 34px -12px var(--edge-pool-warm);
}
/* The way in belongs to the panel that has a class to take it off again.  It
   was written into the shared rule above once, and the report panel -- which
   has no such class -- opened at nothing but a shadow on the page. */
.popover-panel {
  color: var(--text);
  opacity: 0; transform: scale(.92) translateY(-6px);
  transition: opacity .16s ease, transform .18s cubic-bezier(.2, .9, .3, 1);
}
.popover-panel.above { transform: scale(.92) translateY(6px); }
/* The panel grows out of the button it belongs to, and says so with a notch
   cut from the same glass and lit along the same edge. */
.popover-notch {
  position: absolute; width: 12px; height: 12px; pointer-events: none;
  /* Cut from the panel's glass, or it shows as a lighter tooth. */
  --glass-plate: var(--glass-plate-soft);
  background: var(--glass-plate);
  border-top: 1px solid var(--edge-cool); border-left: 1px solid var(--edge-cool);
  transform: rotate(45deg);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  opacity: 0; transition: opacity .16s ease;
}
.popover-notch.open { opacity: 1; }
.popover-notch.above {
  border-top: 0; border-left: 0;
  border-bottom: 1px solid var(--edge-warm); border-right: 1px solid var(--edge-warm);
}
.popover-panel.open, .popover-panel.above.open { opacity: 1; transform: scale(1) translateY(0); }
/* Thrown out of the pill: from behind it, small, into place. */
@keyframes way-out {
  from { opacity: 0; transform: translateX(34px) scale(.5); }
  to { opacity: 1; transform: none; }
}
@keyframes panel-rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .popover-panel { transition: none; }
  .sheet > section { animation: none; }
  .contactway { animation: none; }
  .ctaside { transition: none; }
}
/* No engine split any more: the panel is cut from the bar's glass, and the
   bar is one material on both.  What used to be settled twice here -- a
   heavier blur for Chromium against a lighter plate for WebKit -- was two
   numbers chasing the same look, and the look is now one recipe. */

/* The bar's glass, settled twice for the same reason the panel above is:
   WebKit blurs harder at the same radius, and the bar is the widest piece of
   glass in the app, so the difference shows most here. */
:root[data-engine="webkit"] {
  --glass-frost: blur(20px) saturate(1.14) brightness(.9);
}
/* One line each, a hairline between them, and the current one named in the
   brand colour rather than filled with it. */
.picker-list { border: 0; border-radius: 0; }
/* Three columns, the same in every row: the flag, the name, the tick.  The
   air is inside the row rather than around it, so nothing has to grow. */
.picker-list .sheet-option {
  min-height: 28px; padding: 0 9px; gap: 9px; background: none;
  border-bottom: 1px solid rgba(255, 255, 255, .07);
}
/* A flag is wider than it looks: its own box plus a little of its own,
   so the name never sits against it. */
.picker-list .info-emoji { flex: none; width: 20px; margin-right: 3px; text-align: center; }
.picker-list .sheet-option b { flex: 1; min-width: 0; }
.picker-list .sheet-option:last-child { border-bottom: 0; }
.picker-list .sheet-option b { font-size: 12.5px; font-weight: 600; }
.picker-list .info-emoji { font-size: 13px; }
.picker-list .sheet-option[aria-selected="true"] {
  background: rgba(255, 63, 146, .06);
}
.picker-list .sheet-option[aria-selected="true"] b { color: var(--accent); font-weight: 700; }
.picker-list .sheet-tick {
  flex: none; width: 14px; text-align: center; margin-left: 10px;
  color: var(--accent); font-size: 13px; font-weight: 700;
}
/* The countries coming soon (components/ComingCountries, owner 13.10.2026): one row under the running ones --
   the same glass, a hairline above it, the globe and the bell -- and a sheet where each has its own «Повідомити». */
.coming-more {
  display: flex; align-items: center; gap: 9px; width: 100%;
  margin-top: 4px; padding: 8px 9px; min-height: 40px;
  font: inherit; color: var(--text); text-align: left; cursor: pointer;
  background: rgba(255, 255, 255, .03);
  border: 1px solid rgba(255, 255, 255, .08); border-radius: 11px;
}
.coming-more:active { background: rgba(255, 255, 255, .07); }
.coming-more-icon, .coming-more-bell {
  flex: none; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
  color: var(--accent); background: rgba(255, 63, 146, .1);
}
.coming-more-bell { color: var(--text-dim); background: none; }
.coming-more-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.coming-more-text b { font-size: 12.5px; font-weight: 650; }
.coming-more-text i { font-style: normal; font-size: 11px; color: var(--text-dim); }

.coming-note { margin: 2px 4px 12px; font-size: 13px; line-height: 1.4; color: var(--text-dim); }
.coming-list { list-style: none; margin: 0; padding: 0; }
.coming-row {
  display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 4px;
  border-bottom: 1px solid rgba(255, 255, 255, .07);
}
.coming-row:last-child { border-bottom: 0; }
.coming-flag { flex: none; width: 22px; text-align: center; font-size: 17px; }
.coming-row b { flex: 1; min-width: 0; font-size: 14.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coming-notify {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  min-height: 32px; padding: 0 12px; border-radius: 999px;
  font: inherit; font-size: 12.5px; font-weight: 650; cursor: pointer;
  color: var(--accent); background: rgba(255, 63, 146, .1); border: 1px solid rgba(255, 63, 146, .35);
}
.coming-notify:active { background: rgba(255, 63, 146, .18); }
.coming-notify:disabled { cursor: default; }
.coming-notify.is-waiting {
  color: var(--text-dim); background: rgba(255, 255, 255, .04); border-color: rgba(255, 255, 255, .1);
}
/* ---------- one style for every filter -------------------------------- */
/* A filter panel has the room the specification gives it.  The country and
   language dropdowns are not filters and keep the tighter frame they were
   sized to, so this is asked for by name rather than taken from every panel. */
.popover-panel.titled { padding: 16px; }
/* The panel names itself, because it covers the chip it grew out of. */
.filterhead {
  display: flex; align-items: center; gap: 9px; margin-bottom: 12px;
}
.filterhead b { flex: 1; min-width: 0; font-size: 15px; font-weight: 650; }
.filterhead-icon {
  flex: none; width: 20px; height: 20px; display: grid; place-items: center;
  color: var(--accent);
}
.filterhead .iconbtn { flex: none; width: 24px; height: 24px; color: var(--text-faint); }

/* A list long enough to scroll is a list worth searching. */
.filter-find {
  display: flex; align-items: center; gap: 8px;
  height: 44px; padding: 0 12px; margin-bottom: 10px;
  border-radius: 12px; border: 1px solid var(--line);
  background: rgba(255, 255, 255, .04); color: var(--text-faint);
}
.filter-find input {
  flex: 1; min-width: 0; background: none; border: 0; outline: none;
  /* 16px: anything smaller and iOS zooms the page when the caret lands here. */
  font-size: 16px; color: var(--text);
}
.filter-find input::placeholder { color: var(--text-faint); }

.filter-list { display: flex; flex-direction: column; }
/* Every row the same height, whatever it holds: the ring, an optional symbol,
   the words, and the tick that only the chosen row shows. */
/* The specification drew a 48px row.  Against a real list of twelve
   categories it was so open that half of them fell off the screen, so the air
   inside it came down twice: to 34, and then by a further two fifths to 28.
   The text is 20 of those pixels; what was taken is the emptiness around it,
   not the words.  Every list that drops out of a chip is set here, so they
   cannot be tightened one at a time. */
.filter-option {
  display: flex; align-items: center; gap: 10px;
  min-height: 28px; padding: 0 10px; border-radius: 10px;
  background: none; text-align: left;
}
.filter-option b { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; }
/* The ring is the control; filled, it is the answer. */
.filter-radio {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  border: 1.5px solid var(--line-strong); box-sizing: border-box;
  transition: background .14s ease, border-color .14s ease;
}
.filter-option[aria-checked="true"] .filter-radio {
  border-color: transparent;
  background: linear-gradient(180deg, var(--accent-hi), var(--accent) 55%, var(--accent-deep));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4);
}
.filter-emoji { flex: none; width: 20px; text-align: center; font-size: 15px; }
.filter-glyph { flex: none; width: 20px; display: grid; place-items: center; color: var(--text-dim); }
.filter-tick { flex: none; width: 14px; text-align: center; color: var(--accent); font-size: 14px; font-weight: 700; opacity: 0; }
.filter-option[aria-checked="true"] { background: rgba(255, 63, 146, .08); }
.filter-option[aria-checked="true"] b { color: var(--accent); font-weight: 700; }
.filter-option[aria-checked="true"] .filter-glyph { color: var(--accent); }
.filter-option[aria-checked="true"] .filter-tick { opacity: 1; }

/* The action says what it will do, and how much of it. */
.filter-apply { height: 52px; padding: 0 20px; margin-top: 12px; font-size: 14px; }

/* ---------- boot / auth screens --------------------------------------- */
.boot { min-height: 100vh; display: grid; place-items: center; padding: var(--s8); text-align: center; }
.boot .inner { max-width: 320px; }
.boot h1 { margin: 0 0 var(--s2); font-size: 22px; letter-spacing: .04em; }
.boot p { margin: 0; color: var(--text-dim); font-size: 14px; }
/* The logo keeps the file's own white on the dark ground; on the day page it takes the ink. */
.boot .brandlogo { display: block; max-width: 100%; height: auto; margin: 0 auto var(--s4); color: #fff; }
:root[data-theme="day"] .boot .brandlogo { color: var(--page-ink); }
/* The one action that can mend an expired signature.  Spaced off the sentence
   above it, because that sentence is the explanation and this is the answer. */
.boot .cta { margin-top: var(--s5); }

/* ---------- cover (first entry) --------------------------------------- */
/* Full-bleed, as the reference panel is: the photograph is the panel, and the
   brand sits over its lower half rather than under a cropped band of it. */
.cover {
  position: relative; overflow: hidden;
  min-height: 100vh; min-height: var(--tg-viewport-height, 100vh);
  display: flex; flex-direction: column; justify-content: flex-end;
  background: var(--bg);
}
.cover-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .85; }
.cover::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(13, 15, 18, .28) 0%, rgba(13, 15, 18, .55) 38%,
    rgba(13, 15, 18, .93) 68%, var(--bg) 100%);
}
.cover-hand {
  position: absolute; z-index: 2; left: var(--s6); right: var(--s6);
  top: calc(var(--safe-t) + var(--s6)); margin: 0;
  font-family: var(--font-hand);
  font-size: 26px; font-weight: 600; line-height: 1.2; color: #fff;
  display: flex; align-items: center; gap: var(--s2);
  text-shadow: 0 2px 14px rgba(0, 0, 0, .55);
}
.cover-body { position: relative; z-index: 2; padding: 0 var(--s6) calc(var(--safe-b) + var(--s6)); }
.cover-logo {
  font-family: var(--font-brand);
  margin: 0 0 var(--s2); font-size: 38px; line-height: 1.06; font-weight: 700;
  letter-spacing: -.02em; text-transform: uppercase;
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s2);
  text-shadow: 0 4px 30px rgba(0, 0, 0, .6);
}
/* The slogan is written in the same hand as the line at the top. */
.cover-slogan {
  margin: 0 0 var(--s4); font-family: var(--font-hand);
  font-size: 24px; font-weight: 600; line-height: 1.2; color: #fff;
  text-shadow: 0 2px 16px rgba(0, 0, 0, .5);
}
.cover-directions { margin: 0 0 var(--s6); font-size: 13px; color: var(--text-faint); }
.cover-directions i { font-style: normal; color: var(--accent); }
.cover-pickers { display: flex; gap: var(--s2); margin-bottom: var(--s3); }
.picker {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: var(--s2);
  border-radius: var(--r-chip);
  padding: 11px var(--s3); font-size: 13.5px; color: var(--text);
}
.cover-options { list-style: none; margin: 0 0 var(--s3); padding: var(--s1); background: var(--card); border: 1px solid var(--line); border-radius: var(--r-card); }
.cover-options button {
  width: 100%; display: flex; align-items: center; gap: var(--s2);
  padding: 11px var(--s3); border-radius: 10px; font-size: 14px; color: var(--text);
}
.cover-options button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent-hi); }
.cover-footer { margin: var(--s6) 0 0; text-align: center; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--text-faint); }

/* ---------- profile ---------------------------------------------------- */
/* The one strong light on the screen: the avatar's ring.  Everything else
   here is lit at about half its strength, so the face is what the eye lands
   on first. */
/* The rhythm of this screen, set once here: the handwritten line under the
   name needs room to breathe before the menu, and every block below is kept
   the same distance from the one above it. */
.profile-head {
  display: flex; flex-direction: column; align-items: center;
  gap: 14px; margin: var(--s5) 0 var(--s6);
}
/* A column, so the switch below the two selectors keeps a gap of its own
   rather than sitting against them. */
.profile-settings { margin-top: 24px; display: flex; flex-direction: column; gap: var(--settings-gap); }
.profile-settings h2 + .pickers { margin-top: -4px; }
.profile-settings h2 {
  margin: 0 0 10px 2px; font-size: 12px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint);
}
.avatar-ring {
  position: relative; width: 104px; height: 104px; border-radius: 50%;
  padding: 2px; border: 0;
  background: conic-gradient(from 210deg,
    var(--ring-blue) 0deg, var(--ring-violet) 110deg, var(--accent) 210deg,
    var(--ring-violet) 300deg, var(--ring-blue) 360deg);
}
.avatar-face {
  display: grid; place-items: center; width: 100%; height: 100%;
  border-radius: 50%; overflow: hidden; background: var(--card-hi);
  font-size: 34px; font-weight: 700; color: var(--text-dim);
}
.avatar-face img, .avatar-face .skeleton, .avatar-face .media-missing {
  width: 100%; height: 100%; object-fit: cover; border-radius: 50%;
}
.avatar-face.busy { opacity: .6; }
/* Small enough to leave the face alone, on the same dark glass as the rest. */
.avatar-camera {
  position: absolute; right: -2px; bottom: 2px;
  width: 32px; height: 32px; border-radius: 50%;
  /* On its own dark plate over a photograph, so it keeps the night's white
     in both grounds rather than turning over with the page. */
  display: grid; place-items: center; color: var(--ink);
  background: rgba(12, 15, 22, .86); border: 1px solid var(--edge-line);
  box-shadow: 0 2px 10px rgba(3, 5, 12, .55);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.avatar-menu {
  position: absolute; top: calc(100% + 10px); left: 50%; transform: translateX(-50%);
  z-index: 6; display: flex; flex-direction: column; min-width: 190px;
  border-radius: 14px; overflow: hidden; border: 1px solid var(--edge-line);
  background: rgba(12, 15, 22, .92);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  box-shadow: 0 12px 30px rgba(3, 5, 12, .5);
}
.avatar-menu button {
  padding: 12px 14px; text-align: left; font-size: 13px; color: var(--text);
  border-bottom: 1px solid rgba(255, 255, 255, .07);
}
.avatar-menu button:last-child { border-bottom: 0; color: var(--text-dim); }
.avatar-problem { margin: 8px auto 0; max-width: 280px; text-align: center; }
.profile-head > b { font-size: 25px; font-weight: 700; letter-spacing: -.02em; }
/* The one line on this screen written by a hand rather than set in type. */
.profile-motto {
  font-family: var(--font-hand); font-size: 22px; line-height: 1;
  color: var(--text-dim); margin-top: -4px;
}

/* One card, three rows, lit along its edge at half the avatar's strength. */
/* The menu, the two selectors under it and the pair in the header are one
   frame written once: the same glass, the same edge, the same light beneath.
   Only their shape differs, which is below. */
/* The five things a profile is for are buttons, not rows in a card, and they
   wear the bar's glass: the same pane, the same white line along the top, the
   same lit outline.  Each carries it on its own ::after, which means none of
   them may filter its own backdrop -- an element that does becomes a backdrop
   root and its pane would filter it rather than the page. */
/* No lit lip on the header's own two: the theme switch and the country and
   language chips are small and sit against the page rather than over it, and
   on them the 1px white line across the top reads as a stripe drawn on,
   whichever ground they are on. */
.daylight, .themeswitch, .picker.compact { --glass-sheen: var(--glass-face); }

.profile-line,
.profile-pickers .picker,
.gcard,
.rowcard,
.addrow,
.cardframe,
.picker.compact {
  position: relative; isolation: isolate;
  background: none; box-shadow: none; border: 1px solid transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* Daylight alone thins the plate under a card, to the one the profile's own
   buttons stand on.  On a bright page the app's usual density came out a flat
   dark grey beside them; on the dark page it is right as it is, so this is
   said where only daylight hears it. */
:root[data-theme="day"] :is(.gcard, .rowcard, .addrow, .cardframe,
                            .searchbar, .picker.compact, .panel,
                            .daylight, .themeswitch, .providercard) {
  --glass-plate: var(--glass-plate-soft);
}
/* The panel round them goes: it was a card holding three rows, and they are
   five buttons. */
.profile-menu {
  display: flex; flex-direction: column; gap: 8px;
  background: none; box-shadow: none; border: 0; overflow: visible;
  /* And the filter with them.  It is a `.panel` by class, so the shared pane
     handed it a blur; with the pane's own background taken away, that blur
     had nothing to show but the page behind it, faintly displaced -- which
     read as a rectangle drawn round the three buttons. */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.profile-menu::before { display: none; }
.profile-line::before,
.profile-pickers .picker::before,
.picker.compact::before { display: none; }


/* Each keeps its own corner: the menu's 22px on a row this short would be a
   lozenge, and the header's chips are chips. */
.profile-pickers .picker::before { border-radius: 14px; }
.picker.compact::before { border-radius: var(--r-chip); }
/* Chosen, it says so in the accent -- over the shared edge, not instead of
   the shared frame. */
.profile-pickers .picker.open::before,
.picker.compact.open::before {
  background: linear-gradient(28deg,
    rgba(255, 63, 146, .55) 0%, rgba(255, 108, 186, .3) 50%, rgba(255, 63, 146, .5) 100%);
}
.profile-line {
  width: 100%; text-align: left; min-height: 60px; gap: var(--s3);
  padding: 0 var(--s4); border-radius: 18px;
}
.profile-menu .profile-line:last-child { border-bottom: 0; }
/* Named outright, so nothing behind a row can make it read as secondary. */
/* One class deeper than ".panel .line b", so no later rule of that shape can
   reach these three rows again. */
.panel.profile-menu .profile-line b {
  flex: 1; min-width: 0;
  font-size: 15.5px; font-weight: 650; color: var(--text-soft);
  opacity: 1; text-shadow: none;
}
.profile-line:active,
.profile-pickers .picker:active { transform: translateY(1px); }
.profile-line > svg:last-child { color: var(--text-faint); }
/* Lit the way the avatar is, at about half its strength: blue at one corner,
   pink at the other, and the icon itself left plain. */
.profile-glyph {
  position: relative;
  width: 42px; height: 42px; flex: none; border-radius: 13px;
  display: grid; place-items: center; color: var(--text);
  background: rgba(255, 255, 255, .045); border: 1px solid transparent;
  box-shadow:
    0 4px 12px -6px var(--edge-pool-cool),
    0 6px 14px -8px var(--edge-pool-warm),
    inset 0 1px 0 rgba(255, 255, 255, .07);
}
.profile-glyph::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; pointer-events: none;
  background: linear-gradient(140deg,
    rgba(90, 130, 255, .40) 0%, rgba(255, 255, 255, .07) 48%, rgba(255, 108, 186, .36) 100%);
  -webkit-mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  mask-composite: exclude;
}
/* The glyph itself is the brightest thing in its plate: pure white, and a
   hair thicker than the set's default so a thin line does not read as grey at
   this size.  The lighting stays on the plate around it. */
.profile-glyph svg { position: relative; color: #fff; }
.profile-glyph svg path { stroke-width: 1.9; }
/* A lit bead rather than a flat dot: the light falls on its top edge, the
   underside is in its own shade, and it throws a little of its colour onto
   whatever it sits on. */
/* The pill a new listing is marked with, carrying a number: a filled disc in
   the accent shouted the count of something nobody asked to be alerted to. */
.counter {
  font-style: normal; font-size: 10.5px; font-weight: 650; letter-spacing: .02em;
  min-width: 24px; height: 17.5px; padding: 0 7px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--accent-hi);
  background: rgba(0, 0, 0, .38);
  border: 1px solid rgba(255, 63, 146, .38);
}

/* ---------- swipe back ------------------------------------------------- */
.frame { flex: 1; display: flex; flex-direction: column; }
/* The page springs back from a pull past its end.  While the finger is down
   there is no transition, or the page would lag behind it; the class goes on
   release and the same rule carries it home. */
.frame { transition: transform .34s cubic-bezier(.22, 1, .36, 1); }
.frame.pulling { transition: none; }
/* The page travels by itself in exactly one case, and it is written down
   here rather than switched off in the other cases.  A swipe that is accepted
   must not be seen going anywhere -- the reader asked for the screen behind,
   not for a journey back to this one -- and the surest way to promise that is
   to give the page no transition to travel with unless a cancelled gesture
   has just asked for one.  Both attributes are set by the gesture itself, by
   hand, and never by a render.  See swipeBack.ts. */
.frame .page { transition: none; }
.frame[data-swiping] .page { will-change: transform; }
.frame[data-returning] .page { transition: transform .22s ease; }
@media (prefers-reduced-motion: reduce) { .frame[data-returning] .page { transition: none; } }

/* The light at the left edge while somebody is swiping back.  On WebKit the
   page does not move at all -- see swipeBack.ts -- so this is the only thing
   that answers the finger, and it grows with how near the gesture is to being
   enough.  Fixed, and nothing but opacity: it cannot move the page, and it
   cannot widen the document. */
.edgehint {
  position: fixed; z-index: 3; left: 0; top: 0; bottom: 0; width: 30px;
  opacity: 0; pointer-events: none;
  background: linear-gradient(90deg, var(--accent-soft), transparent);
  transition: opacity .2s ease;
}
/* While the finger is down it follows it, so there is nothing to catch up on. */
.edgehint[data-live] { transition: none; }
@media (prefers-reduced-motion: reduce) { .edgehint { transition: none; } }


/* ---------- listing card ---------------------------------------------- */
/* Full-bleed to the very top of the page: the photograph starts where
   Telegram's own header ends, with the two controls floating over it. */
.hero {
  position: relative;
  margin: calc(max(var(--safe-t), var(--s4)) * -1) calc(var(--page-side) * -1) var(--s4);
}
.hero-empty { margin-bottom: var(--s2); }
.hero-strip {
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; gap: 0;
}
.hero-strip::-webkit-scrollbar { display: none; }
/* The same anywhere on a slide: over a picture, a film or the marks laid on them. */
.hero-strip { overscroll-behavior-x: contain; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.hero-strip img, .hero-strip video { -webkit-user-drag: none; }
/* Nearer a square than a letterbox: a photograph of a person, a room or a
   thing on a table loses less of itself here, and the whole of it is one tap
   away in any case.  Taller than four-to-three, because what a photograph
   loses to a crop it loses off the bottom. */
.hero-photo {
  width: 100%; flex: none; aspect-ratio: 4 / 3.4; height: auto; object-fit: cover;
  scroll-snap-align: center; display: block; cursor: zoom-in;
}
/* Marks laid over the strip let a swipe through to it. */
.hero-count {
  position: absolute; left: var(--s4); bottom: var(--s3); pointer-events: none;
  font-size: 12px; font-weight: 650; color: #fff; padding: 4px 10px;
  border-radius: var(--r-chip); background: var(--plate);
  border: 1px solid var(--plate-line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.hero-dots { position: absolute; left: 0; right: 0; bottom: var(--s3); display: flex; justify-content: center; gap: 5px; pointer-events: none; }
.hero-dots i { width: 5px; height: 5px; border-radius: 50%; background: rgba(255, 255, 255, .38); }
.hero-dots i.on { background: var(--accent); width: 14px; border-radius: var(--r-chip); }

/* The whole photograph, nothing cropped, over everything else. */
.viewer {
  position: fixed; inset: 0; z-index: 40;
  /* Half the plate it had: the free space around a photograph is where the
     page it came from should still be, out of focus behind it. */
  background: rgba(4, 6, 10, .48);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex; flex-direction: column;
}
.viewer-strip {
  flex: 1; display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; align-items: center;
}
.viewer-strip::-webkit-scrollbar { display: none; }
.viewer-strip img, .viewer-strip .skeleton, .viewer-strip .media-missing {
  width: 100vw; flex: none; height: 78vh; object-fit: contain;
  scroll-snap-align: center;
}
/* Over the strip rather than above it.  Taking a row of its own, the bar
   pushed the photograph down by half its own height -- and an album that does
   not sit in the middle of the screen reads as one that slipped. */
.viewer-bar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(var(--safe-t) + var(--s3)) var(--s4) var(--s3);
}
.viewer-count { font-size: 13px; font-weight: 650; color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* One line of address (owner, 15.10.2026): the section, the city, and the moment it was published pushed to
   the far end; the category sits under it as grey chips (DetailMeta). */
.detail-top { display: flex; align-items: center; gap: 8px; margin-bottom: var(--s2); min-width: 0; }
.detail-top .section-chip { flex: none; }
.detail-top .detail-place { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; }
.detail-top + .factchips { margin-top: -2px; }
.section-chip { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.detail-path .section-chip:first-child { flex: none; }
.detail-path > svg { flex: none; color: var(--text-faint); }
.detail-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; min-width: 0; }
.detail-meta .detail-place + .detail-when::before { content: "·"; margin-right: 8px; color: var(--text-faint); }
.section-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; color: var(--text);
  padding: 6px 12px; border-radius: var(--r-chip);
  background: linear-gradient(115deg, var(--glass-1), var(--glass-2));
  border: 1px solid var(--glass-line);
}
.detail-when { font-size: 12px; color: var(--text-faint); margin-left: auto; flex: none; white-space: nowrap; }
/* The category inside the section: the same capsule, quieter, so the pair
   reads as one line of address rather than as two labels. */
.section-chip.is-sub {
  background: rgba(125, 133, 146, .3);
  border-color: rgba(125, 133, 146, .6);
  color: var(--text-soft);
}
/* Between the name and the price, with a breath of air under it: the state
   is part of what the thing is, and the price is the answer to it. */
.detail-state { align-self: start; margin: 2px 0 6px; }

.detail-place {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  font-size: 12px; color: var(--text-dim);
}
/* The figure and its currency, set the way a card sets them: the currency
   small and lifted to the top of the digits rather than sitting on their
   baseline at the same size, where it read as part of the number. */
.detail-price {
  margin: 0; display: flex; align-items: flex-start; gap: 6px;
  color: var(--price-ink); white-space: nowrap;
  /* It may not run off the side of the screen: a price that had somehow
     grown long -- two currencies written after one figure -- was cut by the
     edge of the page with nothing to say it had been. */
  max-width: 100%; min-width: 0;
}
.detail-amount {
  font-size: 26px; font-weight: 800; line-height: 1;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.detail-currency {
  font-size: 12.5px; font-weight: 700; line-height: 1;
  /* The figure's own colour, quieter -- not a second pink of its own, which
     on a bright page read as two different prices. */
  letter-spacing: .04em; color: inherit; opacity: .8;
  /* Level with the top of the digits: half the difference between the two
     cap heights, which is where the eye puts it. */
  margin-top: 3px;
}
/* «За домовленістю» under the title: the price's colour and weight, at the
   size of a line of words rather than of a figure. */
.detail-words {
  font-size: 17px; font-weight: 700; line-height: 1.2; letter-spacing: -.01em;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.detail-meta { margin: 1px 0 0; display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3); font-size: 12.5px; color: var(--text-dim); }
/* One rating, stated once: the figure, a single star, the count. */
.detail-meta > span { display: inline-flex; align-items: center; gap: 5px; }
/* The same: a strip of facts that continues past the screen rather than
   ending inside it. */
.factchips {
  display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none;
  margin: 0 calc(var(--page-side) * -1) var(--s3);
  padding: 0 var(--page-side);
}
.factchips::-webkit-scrollbar { display: none; }
/* A fact about a listing is the same kind of label as its category, so it is
   the same badge: a grey outline and nothing behind it.  It used to be a
   filled black pill, which on a screen of blue blocks read as a fourth kind
   of thing with no meaning of its own. */
.factchip {
  flex: none; white-space: nowrap;
  /* The same ink as any other writing on the page.  A subcategory is the
     answer to the section above it -- "Послуги", then "Послуги для тварин" --
     and the faded set made the answer look like an aside about the question. */
  font-size: 10.5px; font-weight: 600; color: var(--text);
  padding: 5px 10px; border-radius: var(--r-chip);
  background: rgba(125, 133, 146, .3);
  border: 1px solid rgba(125, 133, 146, .6);
}

/* The block that answers "who am I dealing with". Its levels are separated
   rather than run together: who they are and how they are rated, then what
   they are and how long they have been here, then what they speak. */
/* The author's own card: the section's glass like every other block, and a
   shade deeper than the panels around it, because it is the one people decide
   on.  It used to be two greys of its own, which stood out as the last dark
   thing on a screen that had gone blue. */
.providercard {
  /* The writing on it is the page's own ink -- grey-black -- rather than the
     light set the dark surfaces round it use.  The card is a pale blue wash,
     and light writing on it was there to be squinted at rather than read.
     Taken out of the list in `tokens.css` that hands these surfaces the light
     ink, so this is one decision in one place rather than an override
     fighting it. */
  color: var(--text);
  margin-bottom: var(--s4); border-radius: var(--r-card); overflow: hidden;
  border: 1px solid var(--edge-line);
  /* A blue wash over the same glass: the panels around it are one layer and
     this is two, so it reads as the block a reader is deciding on rather than
     as another fact about the listing.

     Flat, not a gradient.  It used to deepen towards the foot, which meant
     the word at the top of the card and the rows under it stood on two
     different shades -- so the head read as a band of its own, and the three
     blocks on a listing could not be made to match however the label itself
     was styled.  One wash, top to bottom; the only thing between the word and
     what it names is the line.
     Written as a gradient with one colour at both ends because a plain colour
     cannot be a layer above another: only the last layer of a background may
     be a colour, and the glass underneath is that last layer. */
  background:
    linear-gradient(180deg, rgba(54, 91, 195, .30) 0%, rgba(54, 91, 195, .30) 100%),
    var(--glass-1);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 8px 22px -14px rgba(0, 0, 0, .45);
}
/* The word a card is about, written inside it with a fine dark line under
   it: a heading over a card floats away from what it names, and these two
   cards are the ones a reader stops at. */
.cardlabel {
  margin: 0; padding: 11px var(--s4) 9px;
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  /* The darker of the two.  A card's heading says what the rows under it are
     about, so it is read first and written strongest; it used to be the
     fainter of the pair, which put the hierarchy the wrong way up. */
  color: var(--text);
  /* The card is one colour top to bottom, and the only thing that separates
     the word from what it names is the line under it.  A band of its own was
     tried and taken back out: it made the head read as a second surface
     stacked on the card rather than as part of it. */
  border-bottom: 1px solid rgba(0, 0, 0, .28);
}
/* A card label that carries one fact on its right: how far away the address
   under it is.  The label keeps its place on the left and the fact sits at
   the end of the same line, where it is read before the street rather than
   after it.

   The label will not shrink and the fact will: on a narrow phone a long
   distance wraps within its own half instead of pushing "АДРЕСА" off the
   line or sitting on top of it. */
.cardlabel-with-aside {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
}
.cardlabel-with-aside > span {
  flex: 0 1 auto; min-width: 0; text-align: right;
  font-size: 11.5px; font-weight: 600; letter-spacing: .02em; text-transform: none;
  color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.provider-head { display: flex; align-items: center; gap: 13px; padding: 14px var(--s4); }
.provider-id { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.provider-id > b { font-size: 16px; font-weight: 700; letter-spacing: -.01em; }
/* How long they have been here, under their name. */
.provider-id > i { font-style: normal; font-size: 11.5px; color: var(--text-faint); }
.provider-rating { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; color: var(--text-dim); }
.provider-rating b { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
/* A count that leads somewhere is underlined, which is the one mark a word
   can carry that says so without becoming a button. */
.tolink {
  color: inherit; font: inherit; padding: 0;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--ink-quiet); text-decoration-thickness: 1px;
}
.tolink:active { color: var(--accent); }
.provider-rating.quiet { color: var(--text-faint); font-size: 12px; }
.provider-facts {
  display: flex; flex-direction: column; gap: 7px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--line);
}
.provider-facts .fact {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--text-dim); min-width: 0;
}
.provider-facts .fact svg { flex: none; color: var(--text-faint); }
.provider { display: flex; align-items: center; gap: 11px; padding: 11px 12px; }
.provider-more {
  /* One line: stacked, the count and the word it counts stood a row apart
     and made the card taller than anything in it. */
  flex: none; display: flex; align-items: baseline; gap: 5px;
  padding: 7px 10px; border-radius: 12px;
  background: rgba(9, 11, 14, .38); border: 1px solid var(--line);
  /* The one thing on this card that keeps the light writing: it has a dark
     face of its own, so it takes the ink the other dark surfaces take rather
     than the card's grey-black, which would be unreadable on it. */
  color: var(--ink);
}
.provider-more b { font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums; }
.provider-more > span { font-size: 11px; color: var(--ink-faint); letter-spacing: .02em; }
.provider-more svg { align-self: center; color: var(--ink-faint); }
.provider-more.one { opacity: .72; }
button.provider-more:active { background: rgba(255, 255, 255, .06); }
/* What else the author has: a plain list, a line each, no pictures.  Without
   rules of its own it rendered as one run of titles and prices with nothing
   between them, because the row styles it borrowed belong to `.panel`. */
.provider-others { border-top: 1px solid var(--line); }
.otherline {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 11px 12px; border-top: 1px solid var(--line);
}
.provider-others .otherline:first-child { border-top: 0; }
.otherline b {
  flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.otherline > span {
  flex: none; font-size: 12.5px; color: var(--text-dim);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.otherline svg { flex: none; color: var(--text-faint); }
.otherline:active { background: rgba(255, 255, 255, .05); }
/* The one author card (AuthorCard, owner 09.10.2026): the face and the name open their page, the rating its
   reviews.  No picture: the first letter shows in the light ink the dark plate takes (as .provider-more). */
.providercard .provider-avatar { padding: 0; cursor: pointer; font-family: inherit; color: var(--ink); }
.providercard .provider-face.media-missing { display: none; }
.provider-name {
  min-width: 0; max-width: 100%; padding: 0; border: 0; background: none; font: inherit; color: inherit;
  text-align: left;
}
.provider-name b {
  display: block; font-size: 16px; font-weight: 700; letter-spacing: -.01em; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
button.provider-rating {
  padding: 0; border: 0; background: none; font-family: inherit; text-align: left; flex-wrap: wrap; row-gap: 0;
}
/* The contact rail floats at the screen's right edge, over whatever is there: with it, the card keeps that edge
   clear -- the name, the count and the rows stop short of it, however the page is scrolled. */
/* The head on a grid: the face down the left; the name and «N оголошення» on one line; the rating under both, so
   «★ 4.3 · 7 відгуків» keeps its one line beside the rail on a 360px phone.  The count is centred on the name and
   lent 7px above and below, so the head stays the face's height -- the card no taller than it was. */
.authorcard-one .provider-head {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-rows: auto auto;
  column-gap: 12px; row-gap: 8px; align-items: center; align-content: center;
}
.authorcard-one .provider-avatar { grid-area: 1 / 1 / 3 / 2; }
.authorcard-one .provider-id { display: contents; }
.authorcard-one .provider-name { grid-area: 1 / 2 / 2 / 3; }
.authorcard-one .provider-rating {
  grid-area: 2 / 2 / 3 / 3; justify-self: start; flex-wrap: wrap; column-gap: 8px; row-gap: 2px;
}
/* «N оголошення» centred in the card's top half -- between its top edge and the line (owner, 10.10.2026), however
   long the name or many the reviews. */
.authorcard-one .provider-more { grid-area: 1 / 3 / 3 / 4; align-self: center; }
/* The reviews: the words underlined in their own colour, the app's thin chevron after them, on one line. */
.provider-reviews { display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; color: var(--text-dim); }
.provider-reviews-words {
  text-decoration: underline; text-decoration-color: currentColor; text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.provider-reviews svg { flex: none; color: currentColor; }
/* Only read (a guest on the site, AuthorCard): nothing on it looks like a way somewhere. */
.authorcard-one.is-still .provider-avatar { cursor: default; }
.authorcard-one.is-still .provider-reviews-words { text-decoration: none; }
.provider-avatar {
  position: relative; overflow: hidden;
  width: 46px; height: 46px; flex: none; border-radius: 50%;
  display: grid; place-items: center; font-size: 19px; font-weight: 700; color: var(--text);
  background: var(--plate); border: 1px solid var(--plate-line);
}
/* The picture lies over the letter: where there is none, nothing is drawn and
   the letter is what shows. */
.provider-face {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}

.prose.clamped { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.section .linkbtn { padding: var(--s2) 0 0; }

.trustbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
  padding: 9px 0; border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--text-faint);
}
.trustbar > span { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* A word in the accent reads as a word.  Where the reader is meant to press
   it -- opening a week's hours, and nothing else so far -- it is given an
   outline to press. */
.linkbtn.framed {
  padding: 5px 11px; border-radius: var(--r-chip);
  border: 1px solid rgba(255, 63, 146, .42);
  background: rgba(255, 63, 146, .10);
  font-weight: 600;
}
.linkbtn.framed:active { background: rgba(255, 63, 146, .18); }
.linkbtn.danger { color: var(--accent); flex: none; padding: 0; }

/* Reporting a listing: one form, wherever the listing is being read. */
.report-why { margin: 0 0 var(--s3); font-size: 13.5px; color: var(--text-dim); }
.report-words { margin-top: var(--s3); }
/* Quiet, and last: the reader has already decided by the time they read it. */
.report-note {
  margin: var(--s3) 0; font-size: 11.5px; line-height: 1.35; color: var(--text-faint);
}
/* Sending a report is not the app's own call to action, so it does not wear
   the accent's glow -- it is a plain, deliberate button. */
.cta-danger {
  background: var(--card); border: 1px solid var(--line-strong);
  color: var(--accent); box-shadow: none;
}
.report-done { text-align: center; padding: var(--s4) 0 var(--s2); }
.report-done .mark { margin: 0 auto var(--s3); }
.report-done b { display: block; font-size: 15px; margin-bottom: var(--s1); }
.report-done p { margin: 0 0 var(--s4); font-size: 13px; color: var(--text-dim); }
.iconbtn.on { color: var(--accent); }

/* ---------- add ------------------------------------------------------- */
/* A list of things a person can publish, not a form: creating stays in the
   bot, which already owns photo upload, moderation and the group mirror. */
.addlist { display: flex; flex-direction: column; gap: 10px; }
/* Not an outline: five layers that only together read as an edge — a
   hairline border, a tight shadow that separates the card from the ground, a
   wide soft glow, an inner highlight along the top, and a gradient ring that
   carries the cool light into pink as it crosses the card. */
.addrow {
  position: relative;
  display: flex; align-items: center; gap: 14px; text-align: left;
  padding: 14px; border-radius: 18px;
  box-shadow:
    /* the tight one: it is what stops the card floating */
    0 1px 3px rgba(3, 5, 12, .5),
    0 10px 26px rgba(3, 5, 12, .34),
    /* the room's own light, pooling under the card the way it pools at the
       foot of the screen — the same two colours, a little closer */
    0 10px 26px -8px var(--edge-pool-cool),
    0 16px 34px -12px var(--edge-pool-warm),
    inset 0 1px 0 var(--edge-hi),
    inset 0 -1px 0 rgba(255, 255, 255, .025);
  transition: transform .14s ease;
}
/* The ring: one pixel of gradient, masked so only the border area survives.
   The lower edge is the lit one, because that is where the light is. */
.addrow:active { transform: scale(.985); }
.addrow-icon {
  position: relative;
  flex: none; width: 42px; height: 42px; border-radius: 14px;
  display: grid; place-items: center; color: rgba(255, 255, 255, .94);
  border: 1px solid var(--edge-line);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .02) 70%),
    rgba(255, 255, 255, .022);
  box-shadow:
    0 1px 2px rgba(3, 5, 12, .4),
    inset 0 1px 0 rgba(255, 255, 255, .10);
}
.addrow-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.addrow-text b { font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.addrow-text > span { font-size: 12px; line-height: 1.3; color: var(--text-dim); }
.addrow-go {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; color: var(--accent-hi);
  background: rgba(255, 63, 146, .07); border: 1px solid rgba(255, 63, 146, .48);
}
/* «Вставити готовий пост» stands first, outlined in the pink of the round arrows beside it: an outline, not a
   glow or a fill (owner, 15.10.2026). */
.addrow.pasterow { border-color: rgba(255, 63, 146, .48); }

/* ---------- composing a listing --------------------------------------- */
/* One screen instead of one question per message: every field visible, in
   any order, on the same dark glass the rest of the product is made of. */
/* Telegram's close and menu buttons float over the top-right corner in full
   screen, and this header puts its own control there.  It starts below them
   rather than under them. */
.composehead {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s3); margin: 2px 0 var(--s4);
}
.composehead-text { min-width: 0; }
.composehead h1 {
  margin: 0 0 4px; font-size: 27px; font-weight: 800; letter-spacing: -.03em; line-height: 1.08;
}
.composehead p {
  display: flex; align-items: center; gap: 7px;
  margin: 0; font-size: 13.5px; line-height: 1.3; color: var(--text-dim);
}
.composehead-heart { flex: none; display: inline-flex; color: var(--accent); }
/* The section a listing belongs to, named once and quietly. */
.sectionpill {
  flex: none; display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 15px; border-radius: 999px; font-size: 13.5px; font-weight: 600;
  color: var(--accent-text); background: rgba(255, 63, 146, .12);
  border: 1px solid rgba(255, 63, 146, .55);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
}
/* The mark that says the pill is a way back rather than a label.  On its own
   plate, a shade darker than the pill it sits in -- given the pill's own
   ground it disappeared into it, which is the whole of what it is for.  It
   takes the pill's right padding as its own, so the pill does not grow. */
.sectionpill-edit {
  display: grid; place-items: center; flex: none;
  width: 21px; height: 21px; border-radius: 50%;
  margin: -3px -8px -3px -1px;
  background: rgba(0, 0, 0, .22);
  border: 1px solid rgba(255, 63, 146, .30);
}

.form { display: flex; flex-direction: column; gap: var(--s4); padding-bottom: 28px; }
.field { display: flex; flex-direction: column; gap: 10px; }
.field > label {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 13.5px; font-weight: 700; letter-spacing: -.01em;
}
.field > label i { font-style: normal; font-size: 11.5px; font-weight: 500; color: var(--text-faint); }
/* The count sits at the far end of the label row, the way the reference has it. */
#field-photos > label { justify-content: space-between; }
/* The directions hint is a whole sentence (owner, 10.10.2026): its own line under the label, not squeezed beside it. */
#field-categories > label { flex-direction: column; align-items: flex-start; gap: 3px; }

/* ---- glass fields ---- */
.glassfield {
  position: relative; display: flex; align-items: center; gap: 11px;
  padding: 0 4px 0 14px; border-radius: 16px; min-height: 54px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
}
.glassfield.tall { align-items: flex-start; padding: 14px 4px 26px 14px; min-height: 118px; }
.glassfield-icon { flex: none; display: inline-flex; color: var(--text-faint); }
.glassfield.tall .glassfield-icon { margin-top: 2px; }
.glassfield input, .glassfield textarea {
  flex: 1; min-width: 0; padding: 14px 12px 14px 0; border: 0; background: none;
  /* 16px keeps iOS from zooming the page when the caret lands here. */
  font: inherit; font-size: 16px; color: var(--text); outline: none;
}
.glassfield textarea { padding: 0 12px 0 0; resize: none; line-height: 1.45; min-height: 84px; }
.glassfield input::placeholder, .glassfield textarea::placeholder { color: var(--placeholder); }
.glassfield:focus-within { border-color: rgba(255, 63, 146, .45); }
.glassfield-suffix {
  flex: none; align-self: stretch; display: grid; place-items: center;
  padding: 0 17px; font-size: 14px; font-weight: 700; color: var(--text);
  border-left: 1px solid var(--glass-line);
}
/* The currency is a choice, not a label: the country's own is already made,
   and the two that travel are one tap away beside it. */
.glassfield-suffix.money {
  display: flex; align-items: center; gap: 2px; padding: 0 8px 0 10px;
}
.glassfield-suffix.money button {
  padding: 5px 7px; border-radius: 9px; font-size: 11.5px; font-weight: 700;
  color: var(--text-faint);
}
.glassfield-suffix.money button.on {
  color: #fff; background: rgba(255, 63, 146, .16);
  box-shadow: inset 0 0 0 1px rgba(255, 63, 146, .45);
}
.glassfield-count {
  position: absolute; right: 14px; bottom: 9px;
  font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums;
}
/* The ring goes round what has to be answered.  A row of chips that only says
   which shelf is open has nothing missing from it -- one of them is always
   chosen -- and ringing it in the accent told somebody to fix a thing that was
   already right.  `data-nav` is a row you move through, not one you answer. */
.field[data-invalid] .glassfield,
.field[data-invalid] .chips:not([data-nav]),
.field[data-invalid] .shots {
  outline: 1px solid rgba(255, 63, 146, .55); outline-offset: 4px; border-radius: 18px;
}
/* The count is the one number that says how much work is left, so it turns
   with the ring rather than staying the same quiet grey beside it. */
.field[data-invalid] .glassfield-count { color: rgba(255, 63, 146, .95); font-weight: 600; }

/* ---- chips ----
   Scoped to the form.  A filter row elsewhere scrolls sideways on purpose;
   a set of choices in a form must not, because a choice you have to drag to
   find is a choice most people never see. */
.form .chips {
  display: flex; flex-wrap: wrap; gap: 9px;
  overflow: visible; margin-bottom: 0; padding-bottom: 0;
}
.form .chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 15px; border-radius: 999px; font-size: 13.5px; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--glass-line);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
  transition: transform .12s ease;
}
.form .chip svg { color: var(--text-dim); }
.form .chip:active { transform: scale(.97); }
/* Inside a form a chosen chip is pink, not white: that is what every other
   creation screen does, and the white the base rule paints is for filters.
   Both spellings are answered -- the class the older forms use and the
   aria-pressed the newer ones do -- because the one thing that must not
   happen is the same choice looking different on two screens. */
.form .chip.on, .form .chip[aria-pressed="true"] {
  color: #fff; background: rgba(255, 63, 146, .16); border-color: rgba(255, 63, 146, .7);
}
.form .chip.on svg, .form .chip[aria-pressed="true"] svg { color: var(--accent-hi); }

/* ---- photographs ---- */
/* The row scrolls sideways instead of dividing the width between five slots.
   Every size here is a fixed pixel: nothing negotiates, so nothing can be
   stretched by a long label or a photograph. */
.shots {
  display: flex; gap: 9px; flex: none;
  height: 108px; min-height: 108px; max-height: 108px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}
.shots::-webkit-scrollbar { display: none; }
.shot {
  position: relative; flex: 0 0 92px;
  height: 108px; min-height: 108px; max-height: 108px;
  scroll-snap-align: start;
  border-radius: 16px; overflow: hidden;
  display: grid; place-items: center; color: var(--text-faint);
}
.shot-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The first slot is the invitation; the rest are only places for what follows. */
.shot.lead {
  /* Wider than the slots that follow, because it carries words. */
  flex: 0 0 132px; gap: 3px; align-content: center; padding: 8px;
  color: var(--text); background: rgba(255, 63, 146, .09);
  border: 1px solid rgba(255, 63, 146, .55);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
}
.shot.lead b { font-size: 12.5px; font-weight: 600; }
.shot.lead i { font-style: normal; font-size: 10.5px; color: var(--text-faint); }
.shot.lead:disabled { opacity: .45; }
.shot-plus {
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  color: var(--accent-hi); border: 1px solid rgba(255, 63, 146, .6);
  background: rgba(255, 63, 146, .10);
}
/* The empty slots belong to the invitation beside them, so they are outlined
   in the same pink -- quieter, because they are where photographs will go and
   not the thing to press. */
.shot.slot {
  border: 1px dashed rgba(255, 63, 146, .30);
  background: rgba(255, 63, 146, .035);
}
/* A filled slot is the photograph and nothing else: the tint it used to carry
   sat under the picture and tinged whatever showed through its edges. */
.shot.slot.filled {
  border-style: solid; border-color: var(--glass-line); background: none;
  color: var(--text-dim);
}
.shot.slot.filled { cursor: pointer; }
/* No scrim over the photograph at all: the badge carries its own plate, so
   nothing has to be darkened for ten pixels of type to read. */
.shot-mark {
  position: absolute; left: 6px; bottom: 6px; z-index: 1;
  /* The catalogue's own "НОВЕ" pill, at the same measurements, so a badge on
     a photograph means one thing wherever it appears. */
  display: inline-flex; align-items: center; height: 17.5px;
  padding: 0 9px; border-radius: 999px;
  font-size: 10.5px; font-weight: 650; letter-spacing: .02em; line-height: 1;
  color: var(--ink); background: rgba(0, 0, 0, .38);
  border: 1px solid rgba(255, 255, 255, .22);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.shot.slot.cover { border-color: rgba(255, 63, 146, .55); }
/* The cover wears the pill the catalogue marks a new listing with: the same
   shape, the same plate, the same pink word, so one badge means one thing
   wherever it appears on a photograph. */
.shot.slot.cover .shot-mark {
  color: var(--accent-hi);
  background: rgba(0, 0, 0, .38);
  border: 1px solid rgba(255, 63, 146, .38);
  box-shadow: none;
}

.shot.loading {
  background: linear-gradient(90deg, var(--card) 25%, var(--card-hi) 37%, var(--card) 63%);
  background-size: 400% 100%; animation: shimmer 1.3s ease infinite;
}
.shot-drop {
  position: absolute; top: 5px; right: 5px; z-index: 1;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  /* White on its own dark plate over a photograph, in both grounds: it does
     not turn over with the page, and at the page's dark it vanished. */
  color: var(--ink); background: var(--plate); border: 1px solid var(--plate-line);
}

/* ---- a listing's one video (services/listing_video.py) ---- */
/* The invitation wears the photographs' own pink, at the row's full width: one film, not a row of slots. */
.videoslot { display: flex; flex-direction: column; gap: 9px; }
/* A quiet row, not a card: a video is an extra (owner, 15.10.2026) -- no accent, no fill, a thin neutral line. */
.field-video { margin-top: -6px; }
.videoslot-add {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 8px 12px;
  border-radius: 12px; text-align: left; color: var(--text-dim);
  background: transparent; border: 1px dashed var(--glass-line);
}
.videoslot-add:disabled { opacity: .8; }
.videoslot-words { display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 8px; }
.videoslot-words b { font-size: 13.5px; font-weight: 550; color: var(--text-dim); }
.videoslot-words i { font-style: normal; font-size: 12px; color: var(--text-faint); }
.videoslot-bar {
  position: absolute; left: 0; bottom: 0; height: 3px; border-radius: 0 3px 3px 0;
  background: var(--accent); transition: width .2s ease;
}
.videoslot-film {
  border-radius: 16px; overflow: hidden; background: rgba(0, 0, 0, 1); border: 1px solid var(--glass-line);
  aspect-ratio: 16 / 10; max-height: 320px; display: grid;
}
.videoslot-film video { width: 100%; height: 100%; object-fit: contain; background: rgba(0, 0, 0, 1); }
.videoslot-kept {
  display: flex; align-items: center; gap: 10px; min-height: 56px; padding: 0 14px;
  border-radius: 16px; color: var(--text-dim); background: var(--glass-1); border: 1px solid var(--glass-line);
}
.videoslot-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.videoslot-state { font-size: 12.5px; color: var(--text-dim); }
.videoslot-state.bad { color: var(--undone-ink); }
.videoslot-actions { display: flex; gap: 6px; flex: none; }
.videoslot-actions .chip { display: inline-flex; align-items: center; gap: 4px; }

/* A listing's video as the carousel's last slide (HeroStrip): the photographs' frame, the film whole inside it,
   one round mark in the middle and a thin line of progress along the bottom -- nothing at the edges, where
   Telegram's buttons and the contact circles float. */
.hero-video { position: relative; cursor: pointer; background: rgba(0, 0, 0, 1); overflow: hidden; }
.hero-video video { width: 100%; height: 100%; object-fit: contain; display: block; background: rgba(0, 0, 0, 1); }
.hero-play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 64px; height: 64px; border-radius: 50%; display: grid; place-items: center;
  color: rgba(255, 255, 255, 1); background: var(--plate); border: 1px solid var(--plate-line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  pointer-events: none;
}
.hero-progress {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: rgba(255, 255, 255, .18); pointer-events: none;
}
.hero-progress i { display: block; height: 100%; background: var(--accent); }
/* The mark a card wears when its listing has a video: the catalogue's pill, with a play mark in it. */
.video-pill {
  position: absolute; right: 8px; bottom: 8px; z-index: 2;
  display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 8px; border-radius: 999px;
  font-size: 11px; font-weight: 650; line-height: 1; color: var(--ink);
  background: rgba(0, 0, 0, .45); border: 1px solid rgba(255, 255, 255, .22);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.pricerow { display: flex; align-items: center; gap: 8px; }
.pricerow .glassfield { flex: 1; min-height: 48px; padding-left: 14px; }
.pricerow .glassfield.narrow { flex: none; width: 96px; }
.pricerow .glassfield input { padding: 12px 12px 12px 0; }
.rowdrop {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--text-dim); background: var(--glass-1); border: 1px solid var(--glass-line);
}
.form .addrowbtn { align-self: flex-start; }
/* One way of being reached, with its own kind, its own field and its own note. */
.contactrow { display: flex; flex-direction: column; gap: 10px; }
.form .chip.drop { padding: 10px 12px; color: var(--text-faint); }
/* The one row of a price list that is wrong, rather than the list. */
.pricerow { flex-wrap: wrap; }
.pricerow.wrong .glassfield { outline: 1px solid rgba(255, 63, 146, .55); outline-offset: 3px; border-radius: 16px; }
.pricerow .fieldsay { flex: 1 0 100%; }
.fieldnote { margin: 0; font-size: 11.5px; line-height: 1.4; color: var(--text-faint); }
/* A note that belongs to the box above it rather than standing on its own.
   The field spaces its parts ten apart, which is right between two things and
   too much between a box and the word about it -- so this one is pulled back
   by the smallest step there is, and sits six under the field it is about.
   Nothing else about it moves: same size, same ink, same box. */
.fieldnote.tied { margin-top: calc(var(--s1) * -1); }
/* «Необов'язково» right under a field's name, before the box. */
.fieldnote.underlabel { margin: -4px 0 2px; }
/* What went wrong with the whole form rather than with one field: a refusal
   from the server, a connection that was not there.  The same ink as a field
   note, because they are one system said at two sizes. */
.formerror { margin: 0; font-size: 12.5px; color: var(--field-note); }
.mark.ok { color: var(--ok); }

/* ---- the one strong action on the screen ---- */
.publish {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  margin-top: 6px; padding: 17px 20px; border-radius: 999px; width: 100%;
  color: var(--action-ink); background: var(--glass-sheen), var(--action-face);
  border: 1px solid var(--action-line);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: var(--action-glow), var(--lift-far);
  transition: transform .12s ease, filter .12s ease;
}
.publish b { flex: 1; text-align: center; font-size: 16px; font-weight: 700; letter-spacing: -.01em; }

/* ---- how tall a form is ----
   Every capsule on a create screen stands 15% shorter than it used to.  The
   forms are long -- a flat asks fourteen questions -- and a screen that holds
   four of them instead of three is the difference between reading a form and
   scrolling one.

   Heights only: no word is made smaller, because the writing is not what was
   taking up the room.  And the two whose height is the whole point keep it --
   the photograph row, and the box somebody writes a paragraph of description
   into. */
.form .glassfield:not(.tall) { min-height: 46px; }
.form .glassfield:not(.tall) input { padding-top: 12px; padding-bottom: 12px; }
.form .chip { padding-top: 8.5px; padding-bottom: 8.5px; }
.form .chip.drop { padding: 8.5px 12px; }
.form .addrpick { padding-top: 7.5px; padding-bottom: 7.5px; }
.form .publish { padding-top: 14.5px; padding-bottom: 14.5px; }
.publish:active { transform: scale(.985); }
.publish:disabled { filter: saturate(.5) brightness(.8); }

/* ---------- ratings and reviews --------------------------------------- */
/* The stars that are not lit still have to be counted: at the faintest grey
   the palette holds they were four invisible ones beside a gold one. */
.stars { display: inline-flex; gap: 1px; color: var(--ink-quiet); }
.stars svg.on { color: var(--star); }
.rating { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-dim); }
.rating b { color: var(--text); font-weight: 700; }

.review { display: flex; gap: 10px; padding: 12px; border-top: 1px solid var(--line); }
.review:first-child { border-top: 0; }
.review-avatar {
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  display: grid; place-items: center; font-size: 14px; font-weight: 700; color: var(--text-dim);
  background: var(--plate); border: 1px solid var(--plate-line);
}
.review-body { flex: 1; min-width: 0; }
.review-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); }
.review-head b { font-size: 13.5px; font-weight: 650; color: var(--text); }
.review-date { font-size: 11.5px; color: var(--ink-quiet); }
.review-body p { margin: 4px 0 0; font-size: 13px; line-height: 1.55; color: var(--text-dim); }

/* The owner's one answer, indented under what it answers, so it reads as a
   reply rather than as a second opinion. */
.review-reply {
  display: block; margin: 8px 0 0; padding: 8px 10px;
  border-left: 2px solid var(--line-strong); border-radius: 0 10px 10px 0;
  background: var(--edge-ground);
}
.review-reply > b { font-size: 11.5px; font-weight: 700; color: var(--ink-quiet); }
.review-reply > p { margin: 2px 0 0; }
.review-acts { display: flex; align-items: center; gap: var(--s3); margin-top: 6px; }
/* The section's link button carries bottom padding for the row it usually
   sits in alone.  Here it stands beside another one, and the two were sitting
   two pixels apart. */
.review-acts .linkbtn { padding: 0; }
.review-answer { display: block; margin-top: 8px; }
.review-answer textarea {
  width: 100%; min-height: 60px; padding: 9px 11px; border-radius: 12px;
  /* 16px: anything smaller and iOS zooms the page when the caret lands here. */
  font: inherit; font-size: 16px; color: var(--text);
  background: var(--edge-ground); border: 1px solid var(--edge-line); resize: vertical;
}
/* What somebody who has not dealt with this provider sees instead of a form.
   No instructions: they do not need to know which button the app was watching
   for, they need the way to start. */
.reviewgate {
  padding: var(--s4); display: flex; flex-direction: column; gap: var(--s3);
  align-items: flex-start;
}
.reviewgate > p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text-dim); }
/* The line that follows this card is a different thought -- "no reviews yet"
   answers the heading, while the card answers the reader -- and run together
   they read as one paragraph that contradicts itself. */
.reviewgate + .quiet { margin-top: var(--s3); }
/* An address somebody wrote in their own description.  Underlined rather than
   only coloured: colour alone is not a link to a reader who cannot see it,
   and these are the lines people are meant to act on. */
.prose-link { color: var(--accent-hi); text-decoration: underline; text-underline-offset: 2px; word-break: break-word; }
.reviewform { padding: var(--s4); display: flex; flex-direction: column; gap: var(--s3); }
.starpick { display: flex; gap: var(--s1); }
.starpick button { padding: 2px; }
.reviewform textarea {
  width: 100%; resize: vertical; border-radius: 12px; padding: var(--s3);
  font: inherit; font-size: 16px; color: var(--text);
  background: rgba(9, 11, 14, .45); border: 1px solid var(--line-strong); outline: 0;
}
.reviewform-actions { display: flex; gap: var(--s2); }
.notice { margin: 0 0 var(--s3); font-size: 13px; color: var(--accent-hi); }
/* Said once, to one person, and quietly: it is the instruction they asked for
   rather than news.  Three lines in the accent read as something having gone
   wrong, which is the opposite of what this says. */
.notice.quiet {
  padding: 9px 12px; border-radius: 12px; line-height: 1.45;
  color: var(--text-dim); background: var(--edge-ground);
  border: 1px solid var(--edge-line);
}
.quiet { margin: 0; font-size: 13px; color: var(--text-faint); }
/* A draft offered back.  It sits at the top of the form it belongs to, says
   its piece in two lines and offers the two answers there are -- and is not
   there at all in the ordinary case, where the draft is simply put back. */
/* A draft offered back, cut from the same glass as every other card.  It is
   in the pane list above rather than dressed up here: a card that paints its
   own ground is a card that looks like a different app the moment the glass
   changes, which is what this one did. */
.draftbar {
  display: grid; gap: var(--s2);
  margin: 0 0 var(--s4);
  border-radius: var(--r-card); padding: var(--s3) var(--s4);
}
.draftbar b { font-size: 14px; font-weight: 650; }
.draftbar p { margin: 0; font-size: 12.5px; line-height: 1.4; color: var(--text-dim); }
/* The app's two buttons, side by side and a size down: inside a card they are
   an answer to a question, not the call to action of a whole screen. */
.draftbar-acts { display: flex; gap: var(--s2); margin-top: 2px; }
.draftbar-acts .cta { padding: 11px 16px; font-size: 13.5px; }
/* A photograph the app still has and can no longer draw: the file id is kept
   and will be published, but the file the person chose went with the page. */
.shot-held { display: grid; place-items: center; width: 100%; height: 100%; color: var(--text-faint); }

/* Telegram already draws the app's name above the page, so a listing needs
   only its two controls, not a second title bar. */
.detailbar {
  position: absolute; z-index: 2; left: var(--s4); right: var(--s4);
  top: calc(var(--safe-t) + var(--s3));
  display: flex; align-items: center;
}
/* Down in the corner of the picture, where it is out of the way of anything
   Telegram floats over the top of it. */
.hero-save {
  position: absolute; z-index: 2; right: var(--s4); bottom: var(--s3);
}
.hero-empty .detailbar { position: static; padding: calc(var(--safe-t) + var(--s3)) 0 var(--s2); }
/* A hero with no picture has no corner to pin anything to: without this the
   heart floated in the empty space where the photograph would have been. */
.hero-empty .hero-save { display: none; }
.detailbar .spacer { flex: 1; }
/* Every one of these stands on a photograph: the hero's back arrow, the heart
   in its corner, the close button over a picture opened full screen.  So it
   keeps the night's white and its white hairline in both grounds, the way the
   avatar's camera badge does -- `--text` turns over with the page, and in
   daylight it left a near-black glyph on a near-black plate. */
.iconbtn.plated {
  background: var(--plate); border-color: var(--edge-line);
  backdrop-filter: blur(10px); color: var(--ink);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: var(--plate-shadow);
}
.iconbtn.plated.on { color: var(--accent); }
/* Enough shade at the top for white controls to hold on a bright photograph. */
.hero-strip + .hero-shade { }
.hero::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 108px; z-index: 1;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(9, 11, 14, .62), transparent);
}
.hero-empty::before { display: none; }


/* ---------- price rows ------------------------------------------------- */
.priceline {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: 10px 12px; border-top: 1px solid var(--line);
}
.priceline:first-child { border-top: 0; }
.priceline-name { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.priceline-name b { font-size: 13.5px; font-weight: 600; line-height: 1.3; }
/* Every price in the app is written the same way -- the figure in the house
   pink, the currency small and level with the top of the digits -- and this
   list was the one place that set it as plain dark text. */
.priceline-amount {
  flex: none; display: flex; align-items: flex-start; gap: 5px;
  color: var(--price-ink); white-space: nowrap; padding-top: 1px;
}
.priceline-amount .detail-amount { font-size: 17px; letter-spacing: -.02em; }
.priceline-amount .detail-currency { font-size: 10.5px; margin-top: 2px; }

/* ---------- hours ------------------------------------------------------ */
/* Collapsed, this row is today: the hours opposite "відкрито" are as much
   today's as the word is, and were being quieted by the rule that dims the
   second half of every panel row. */
.hours-today b,
.hours-today > span:not(.dotstate) { color: var(--text); font-weight: 650; }
/* Opened, the first row is today and the six under it are the rest of the
   week: the line between them is drawn heavier than the ones that separate
   one ordinary day from the next.  On the row below, because these rows are
   parted by the top border of the one under -- setting a bottom border on
   today drew nothing at all.  Cut into the panel rather than laid on it: a
   lighter line on glass reads as one more edge, a darker one as a division. */
.hours-week .hours-today + .line { border-top: 2px solid rgba(0, 0, 0, .38); }
/* And today's row is sunk into the panel rather than merely divided from it:
   the week below reads as the list, and this reads as the answer above it. */
.hours-week .hours-today { background: rgba(0, 0, 0, .2); }
.hours-today .dotstate {
  width: 7px; height: 7px; flex: none; border-radius: 50%;
  background: var(--text-faint);
}
.hours-today .dotstate.open { background: var(--ok); box-shadow: 0 0 0 3px rgba(61, 220, 151, .16); }
/* The week's opening hours: today is named, the other six are quiet.  Scoped
   to that list — written unscoped it dimmed the label of every second and
   third row in every panel in the app, including the profile's menu. */
.hours-week .line b { color: var(--text-dim); }
.hours-week .line.now b,
.hours-week .line.now span { color: var(--text); font-weight: 650; }
.panel .line:first-child b { color: var(--text); }

/* ---------- information ------------------------------------------------ */
.info-line { width: 100%; text-align: left; }
.info-line:active { background: rgba(255, 255, 255, .05); }
.info-emoji { width: 26px; flex: none; text-align: center; font-size: 16px; line-height: 1; }
.info-line > span { font-variant-numeric: tabular-nums; }
.article h2 { margin: 0 0 var(--s3); font-size: 20px; font-weight: 700; letter-spacing: -.02em; line-height: 1.2; text-wrap: balance; }
.article .prose { font-size: 14.5px; line-height: 1.6; }
.article .linkbtn { display: inline-block; margin-top: var(--s4); }

/* A telephone number and an email inside an article: the same words, drawn as
   something that can be pressed.  A button rather than a link, because what
   the press does is copy -- a dialler does nothing on a desktop and nothing in
   some Telegram clients, while a copy works everywhere and is what somebody
   is about to do with the number anyway. */
.prose-phone, .prose-email {
  display: inline; padding: 0; border: 0; background: none;
  font: inherit; color: var(--accent-hi);
  border-bottom: 1px dashed color-mix(in oklab, var(--accent-hi) 55%, transparent);
}
.prose-phone:active, .prose-email:active { opacity: .6; }
/* Said once, quietly, and then out of the way.  It keeps its height whether
   or not it is saying anything, so the article does not jump when it does.
   Set in its own small frame: a bare green sentence is read as a colour on a
   dark page and as nothing at all on a light one. */
.prose-said { min-height: 26px; margin-top: var(--s2); }
.prose-said span {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 13px; font-weight: 600; color: var(--ok);
  background: color-mix(in oklab, var(--ok) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--ok) 34%, transparent);
}

.article-updated { margin: var(--s4) 0 0; font-size: 12.5px; color: var(--text-faint); }
/* Second to everything above it on purpose: a way to help, not a warning
   about what is being read.  No fill, no accent, no weight. */
.article-report {
  margin-top: var(--s4); padding: 0; border: 0; background: none;
  font-size: 12.5px; color: var(--text-dim); text-decoration: underline;
  text-underline-offset: 3px;
}
.article-thanks { margin: var(--s4) 0 0; font-size: 12.5px; color: var(--ok); }

/* What has been said under an article.  Quieter than the article: somebody
   came here to read it, and the talk is what follows, not what competes. */
.comments { margin-top: var(--s6); display: flex; flex-direction: column; gap: 10px; }
.comments h3 { margin: 0; font-size: 14px; font-weight: 650; }
.comments-none { margin: 0; font-size: 12.5px; color: var(--text-faint); }
.comments-said { margin: 0; font-size: 12.5px; color: var(--text-dim); }
.comment {
  padding: 10px 12px; border-radius: 12px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.comment b { display: block; font-size: 12.5px; color: var(--text-dim); }
.comment p { margin: 3px 0 0; font-size: 13.5px; line-height: 1.45; white-space: pre-wrap; }
.comment.mine { border-color: var(--action-line); }
/* An answer hangs under what it answers, one level and no deeper: a tree
   under a guide article is a forum nobody asked for. */
.comment.reply { margin-left: var(--s5); border-left: 2px solid var(--glass-line); }
.comment-gone { margin: 0; font-size: 12.5px; font-style: italic; color: var(--text-faint); }
.comment-acts { display: flex; gap: var(--s3); margin-top: 6px; }
.comment-acts button {
  padding: 0; border: 0; background: none;
  font-size: 12px; color: var(--text-dim); text-decoration: underline;
  text-underline-offset: 3px;
}
.comment-answering {
  display: flex; align-items: center; gap: 6px; margin: 0;
  font-size: 12.5px; color: var(--text-dim);
}
.comment-answering button { padding: 0 4px; border: 0; background: none; color: var(--text-faint); font-size: 15px; }
.comment-write { display: flex; flex-direction: column; gap: 8px; }
.comment-write textarea, .sheet-text {
  width: 100%; padding: 10px 12px; border-radius: 12px; resize: vertical;
  /* 16px: anything smaller and iOS zooms the page when the caret lands here. */
  font: inherit; font-size: 16px; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.sheet-title { display: block; margin-bottom: var(--s3); font-size: 15px; }
.sheet-list { display: flex; flex-direction: column; gap: 6px; }
.sheet-text { margin-bottom: var(--s3); }

/* Narrowing a long section by its own words.  Borrowed from the filter
   panels, which is where a reader has already met this box. */
.info-find { margin-bottom: var(--s3); }

/* ---------- help -------------------------------------------------------- */
/* A page of plain answers rather than a menu of them: every row is one thing
   to know, and the number in front says how far down the list the reader is.
   The rows are taller than a menu's and hang from their top, because two of
   these run to three lines and a menu row centres one. */
.panel .helprow { align-items: flex-start; gap: 10px; padding: 11px 12px; }
.helprow > i {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  font-style: normal; font-size: 11px; font-weight: 700; line-height: 1;
  color: var(--action-ink);
  background: var(--action-face);
  border: 1px solid var(--action-line);
}
.helprow > p { margin: 0; flex: 1; min-width: 0; font-size: 13.5px; line-height: 1.45; color: var(--text-soft); }
.helpabout { margin: 0; font-size: 13px; line-height: 1.55; color: var(--text-dim); }

/* ---------- bottom sheet ----------------------------------------------- */
/* A panel that floats clear of the bottom bar, not a sheet glued to the edge
   of the screen.  It used to sit against that edge, and because the page is
   its own stacking context the bar -- five layers below it on paper -- drew
   over its foot anyway.  Standing it above the bar settles that without
   touching anything's z-index, and it takes the filters' glass while it is
   there, so a panel that opens over a listing looks like every other. */
.sheet {
  position: fixed; inset: 0; z-index: 20;
  display: flex; align-items: flex-end; justify-content: center;
  padding: var(--s4);
  padding-bottom: calc(var(--nav-h) + var(--nav-floor) + var(--nav-lift) + var(--s3));
}
.sheet-backdrop { position: absolute; inset: 0; background: rgba(6, 8, 11, .3); }
.sheet > section {
  position: relative; width: 100%; max-width: 380px;
  max-height: 100%; overflow-y: auto;
  padding: 16px; border-radius: 16px;
  /* Its own way in, run by the browser rather than waiting on a class it
     never receives. */
  animation: panel-rise .18s cubic-bezier(.2, .9, .3, 1) both;
  border: 1px solid transparent;
  box-shadow:
    inset 0 0 0 1px var(--glass-ring),
    inset 0 0 14px rgba(255, 255, 255, .05),
    inset 0 -14px 22px -20px rgba(0, 0, 0, .7),
    0 1px 3px rgba(3, 5, 12, .5), 0 18px 44px rgba(3, 5, 12, .48),
    0 10px 26px -8px var(--edge-pool-cool), 0 16px 34px -12px var(--edge-pool-warm);
}
/* The list of a dropdown, and the rows in it.  These are what the country and
   language pickers are built from as well, so they live here rather than with
   the panel that happened to be rewritten last: without them the rows fall
   back to inline-block and flow two to a line. */
.sheet-list { display: flex; flex-direction: column; border-radius: var(--r-card); overflow: hidden; border: 1px solid var(--line); }
.sheet-option {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%; text-align: left; background: var(--card);
}
.sheet-option:active { background: var(--card-hi); }
.sheet-option[aria-pressed="true"] b { color: var(--accent-hi); }
.sheet-option[disabled] { opacity: .55; }
.sheet-tick { color: var(--accent); font-weight: 700; }

/* ---------- the catalogue, two to a row -------------------------------- */
/* The horizontal card showed one listing per screen-width.  This shows four,
   with a picture large enough to be the reason a reader stops at one -- and
   the columns follow the width rather than a phone model, so a tablet gets
   more of them instead of two very wide cards. */
/* Two to a row on a phone, whatever the phone.  Fitting by width let a wide
   Android viewport take three while an iPhone took two, so the same catalogue
   read as two different screens. */
/* `minmax(0, 1fr)` rather than `1fr`, which is `minmax(auto, 1fr)`: a column
   cannot then be narrower than its widest item's min-content, and one card
   holding a longer word than the rest made its whole column wider than the
   others.  A card clips its own words -- every line in it is `nowrap` with an
   ellipsis -- so nothing is lost by letting the column decide the width.
   Plain `.gcard` grids escaped it only by accident: `overflow: hidden` zeroes
   an item's automatic minimum size, and "Мої оголошення" wraps each card in a
   `.mineslot` that has no `overflow` of its own. */
.cardgrid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 13px 12px; margin-bottom: var(--s4);
}
/* A tablet has the room for a third; below that the count is not negotiable. */
@media (min-width: 600px) { .cardgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.gcard {
  position: relative; text-align: left; overflow: hidden;
  /* The picture keeps the 155px it had; the words below take what is left,
     which is a third less than they used to.  Left to its content, each card
     split wherever its own title ended. */
  height: 270px;
  display: flex; flex-direction: column;
  border-radius: 22px; padding: 0;
  border: 1px solid transparent;
}
/* Pressed: it sinks into its own shadow rather than merely brightening. */
.gcard { transition: transform .13s ease, box-shadow .13s ease; }
.gcard:active {
  transform: translateY(1px) scale(.994);
  box-shadow: var(--pane-press);
}
/* The top half of every card, so a row has one horizon and the words
   below can never outgrow the picture above. */
.gcard-media {
  position: relative; z-index: 0; flex: 0 0 155px; overflow: hidden;
  background: linear-gradient(150deg, var(--card-hi), #12151a);
  display: grid; place-items: center; color: var(--text-faint);
}
.gcard-photo, .gcard-media .skeleton { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The plate a card wears instead of a photograph: the section's own mark and
   the name, over the brand's own ground.  Dark in both themes on purpose --
   it is our picture, and our picture looks like this. */
.gcard-blank {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; color: var(--blank-ink);
  background: var(--blank-face);
}
.gcard-blank > b {
  font-size: 9px; font-weight: 800; letter-spacing: .17em; color: var(--blank-name);
}
/* White on a photograph, where the app's faint grey disappears. */
.gcard .gcard-heart {
  top: 8px; right: 8px; width: 32px; height: 32px; z-index: 1;
  color: #fff; background: rgba(0, 0, 0, .38);
}
.gcard .gcard-heart[aria-pressed="true"] { color: var(--accent); }
/* The body fills what the picture leaves, so the foot of every card in a row
   sits on the same line however much is above it. */
.gcard-body {
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; padding: 8px 12px 9px;
  /* Where the chip's own words begin: its border, its padding, its icon and
     the gap after it.  Everything below the chip lines up with those words
     rather than with the pill's edge -- the eye follows the text, not the
     border drawn around it. */
  --chip-words: 26px;
  /* What a row with an icon of its own already spends before its words. */
  --row-icon: 17px;
  /* The quiet surface Home's models card wears (owner, 09.10.2026, a DEV trial): one token under the words of every
     card in every catalogue -- the picture above, the size, the shape and everything on it untouched. */
  background: var(--soft-surface);
}
/* In daylight that surface is light, so the words on it take the page's own inks, as Home's models card writes
   them: the name in the soft one, the rest in the dim one; the price keeps its accent, the chip its grey plate. */
:root[data-theme="day"] .gcard-body {
  --text: var(--page-ink); --text-soft: var(--page-ink-soft);
  --text-dim: var(--page-ink-dim); --text-faint: var(--page-ink-faint);
  --ink-quiet: var(--page-ink-dim);
}
:root[data-theme="day"] .gcard-body .gcard-title { color: var(--page-ink-soft); }
/* One line, cut with an ellipsis.  Two reserved lines were the tallest thing
   in a half that is now a third smaller, and a name that wrapped pushed the
   price out of the card. */
.gcard-title {
  font-size: 14.5px; font-weight: 550; line-height: 17px;
  /* White, but not the hard white it was: at .92 the name was the brightest
     thing on a card and shouted over the price it stands above. */
  color: rgba(255, 255, 255, .8);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A pill, not a fill: it says which kind of thing this is in a mixed
   catalogue, and nothing louder than that. */
.gcard-chip {
  display: inline-flex; align-items: center; gap: 5px; align-self: flex-start;
  margin-top: 3px; padding: 2px 8px; border-radius: 999px; max-width: 100%;
  font-size: 11px; color: var(--text-dim);
  border: 1px solid var(--line); background: rgba(255, 255, 255, .04);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gcard-chip svg { flex: none; }
/* Straight under the chip, not pinned to the foot of the card.  Pinned, a
   catalogue with no ratings at all -- market, work, housing -- left a hole
   between the category and the city on every single card. */
.gcard-foot { padding-top: 3px; display: flex; flex-direction: column; }
.gcard-where {
  display: flex; align-items: center; gap: 5px;
  padding-left: calc(var(--chip-words) - var(--row-icon));
  font-size: 12px; font-weight: 400; line-height: 15px;
  color: var(--ink-quiet);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gcard-where svg { flex: none; }
.gcard-rating {
  display: flex; align-items: center; gap: 5px; margin-top: 2px;
  padding-left: calc(var(--chip-words) - var(--row-icon));
  font-size: 11.5px; line-height: 15px; color: var(--text-dim);
}
.gcard-rating .stars { display: inline-flex; color: var(--star); }
.gcard-rating b { color: var(--text); font-weight: 650; }
/* Indented like the rows above it, because it carries an icon like they do. */
.gcard-price { margin-top: 2px; padding-left: calc(var(--chip-words) - var(--row-icon)); }
/* A rail card is a quarter narrower than a grid one, and on a 360px Android a
   price like "від 150 RON" ran past its edge.  There the rows line up with
   each other at the card's own margin instead of with the chip's words: the
   nine pixels that costs is exactly what the price was missing.  Every phone
   from 375px up keeps the chip alignment. */
@media (max-width: 374px) { .rail .gcard-body { --chip-words: var(--row-icon); } }
/* Against the digits rather than the top of their line box, which is where
   flex-start would leave it. */
.gcard-price > svg { flex: none; margin-top: 5px; color: inherit; opacity: .7; }
/* Level with the middle of the words, as it is with the digits above. */
.gcard-price.is-words > svg { margin-top: 2px; }
/* A rail card leaves the words 87-91px beside the coin mark, and «За домовленістю» needs 101 at the grid's size: it
   read «За домовлені…» on the home rail at 393px, and «По договорённости» is longer still.  There the words go without
   the mark, a size smaller -- 90px, and 103 for the Russian, against 104-108 -- and may take a second line rather than
   be cut, whatever a font does to their width. */
.rail .gcard-price.is-words > svg { display: none; }
.rail .gcard-words {
  font-size: 11px; line-height: 14px; white-space: normal;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
}

/* ---------- one's own listings ---------------------------------------- */
/* The card is the catalogue's; what a person needs here and nowhere else is
   the state it is in and a way to take it down. */
/* The card, with its state and its way out laid on it rather than stacked
   under it: a column of card, badge, reason and button read as four things
   about one listing instead of as one listing. */
.mineslot { position: relative; display: flex; flex-direction: column; gap: 6px; }
/* An event of one's own (owner, 10.10.2026): the quiet surface every card's words stand on -- the whole frame, the
   picture untouched -- its glass panes off; the marks in a column down the right edge, one under another. */
/* Every event card -- the catalogue's, upcoming and past, and one's own (owner, 10.10.2026) -- and every card of the
   «Шукають моделей» catalogue stand on the quiet surface the other catalogues' cards do, their glass panes off; the
   picture untouched.  Home's models card is its own (.mspot-card). */
:is(.cardframe.is-event, .cardframe.mreq-card) { background: var(--soft-surface); border: 1px solid var(--soft-surface-line); }
:is(.cardframe.is-event, .cardframe.mreq-card)::before, :is(.cardframe.is-event, .cardframe.mreq-card)::after { display: none; }
:root[data-theme="day"] :is(.cardframe.is-event, .cardframe.mreq-card) { --ink-quiet: var(--page-ink-dim); }
.cardframe.is-mine .eventcard-side { padding: var(--s3) 10px var(--s3) 0; }
.cardframe.is-mine .eventcard-side .mineacts { flex-direction: column; gap: 6px; }
/* The card's foot (GridCard's `footer`, `.gcard-tail`) on the same surface as its words (owner, 10.10.2026) --
   one card, not a light top over a grey foot -- and between them one fine neutral line. */
.mineslot .gcard-tail { background: var(--soft-surface); }
.mineslot .gcard-tail .minefoot { border-top-color: var(--soft-surface-line); }
/* In daylight that surface is light: the figures and the marks take the page's own inks, as the words above
   them do; the marks keep their grey plate, and the one that is on keeps its accent frame. */
:root[data-theme="day"] :is(.mineslot .gcard-tail, .cardframe.is-mine .eventcard-side) {
  --ink-quiet: var(--page-ink-dim);
}
:root[data-theme="day"] :is(.mineslot .gcard-tail, .cardframe.is-mine .eventcard-side) .markbtn.onshot:not(.on) {
  color: var(--page-ink-soft);
}
/* The card is the catalogue's card and grows by one thing only: its foot.
   `flex: 1` would set a basis of zero, which overrides a height in a flex
   column -- the card was squashed that way once, to make room for what stood
   beneath it -- so `1 0 auto`: never below its own height, and up to the
   height of its row, so the two cards of a row stand equally tall. */
.mineslot .gcard { height: auto; flex: 1 0 auto; }
/* The words keep the room their own rows take -- name, category, city, price,
   each one line at its catalogue size, some 88px -- and a breath of about ten
   under the price, so the price never sits on the hairline.  Not the
   catalogue's whole 115: that half keeps a line for a rating this screen never
   draws, and here it stood as an empty band between the price and the foot.
   Fixed rather than fitted, so every foot in the grid starts on one line,
   however much or little a listing says above it. */
.mineslot .gcard-body { flex: 0 0 100px; }
/* A listing with the model's category advice above it takes the whole row:
   the advice is a sentence and three answers, and a half-width column cannot
   hold a sentence. */
.mineslot.with-advice { grid-column: 1 / -1; }
/* A card's own foot, under its words (GridCard's `footer`).  It takes the
   height the row leaves over, and keeps its row at the top of that. */
.gcard-tail { flex: 1 0 auto; display: flex; flex-direction: column; }
/* The author's foot, inside the card: the views on the left, the buttons on
   the right -- the listing's statistics first -- one row as tall as a button
   under a hairline that ends the words.  They used to be a line under the
   card -- which in a grid of two could be read as the next card's -- and
   buttons laid over the card's corner, where a third one covered the price's
   currency.  Now nothing is laid over anything: the foot has its own room
   under the price.
   Kept compact so the views and four buttons (statistics, edit, pause,
   delete) share one row in a card two to a row, up to four digits -- «9999»
   -- at 360, 375 and 393 alike, with room left over for the iPhone's digits,
   a fifth wider than the ones a desktop draws; five digits fit from 412 (and
   at 393 with Android's).  A wrapped foot made its card, and the card beside
   it, a line taller.  Only where it truly cannot fit -- five buttons, a 320px
   phone, a figure past that -- the buttons take a second line under the
   figure, never the price's. */
.minefoot {
  display: flex; flex-wrap: wrap; align-items: center; align-content: flex-start;
  justify-content: space-between; gap: 4px;
  margin: 0 5px 0 8px; padding: 6px 0 8px;
  border-top: 1px solid var(--line);
}
/* The views, «[eye] 24», on one line that never breaks.  (It held «📞» beside
   it until the rest of the figures moved under the statistics button.)  In
   the card's own quiet ink, the city's: this card is dark in both themes, and
   the page's dim ink is not written for it.  The mark is the app's own line
   eye, in the same ink -- the colour emoji it replaces was the one colour in
   the foot, and each phone's emoji face sat it on the baseline its own way.
   On the buttons' own line: the group is a button's height and centred in the
   row, and the mark and the figure are boxes of their own centred in it.
   A little in from the card's edge rather than flush with the hairline: some
   seven pixels, where the card has them; on a narrow phone (360) it gives the
   room back to the buttons, so the row there is still one row. */
.minestats {
  flex: 0 0 auto; display: flex; align-items: center; align-self: center; gap: 5px;
  height: 28px; padding: 0; padding-left: clamp(3px, (100vw - 351px) / 3, 7px);
  white-space: nowrap; font-size: 11px; line-height: 1; font-weight: 500;
  color: var(--ink-quiet); font-variant-numeric: tabular-nums; text-align: left;
}
.minestats > span { display: inline-flex; align-items: center; gap: 3px; white-space: nowrap; }
.minestats svg { flex: none; }
.minestats b { font-weight: inherit; line-height: 1; }
/* A phone narrower than 381px (360, 375) leaves a card some 150px: a hair
   less air -- the figure half a pixel smaller, the foot two pixels nearer the
   card's edges, the eye a pixel nearer its figure -- keeps «9999» and four
   buttons in one row there too.  The buttons themselves keep their size. */
@media (max-width: 380px) {
  .minefoot { margin: 0 3px 0 4px; column-gap: 2px; }
  .minestats { font-size: 10.5px; gap: 4px; padding-left: clamp(0px, (100vw - 360px) / 3, 5px); }
  .minestats > span { gap: 2px; }
}
/* A card turned to its figures (CardFigures, laid in GridCard's `face`): the
   particulars under the name keep their room, out of sight, and the figures
   are laid over that room -- so the card is exactly as tall either way and
   the grid never moves.  The name stays where it was, with room at its end
   for the cross. */
.gcard.is-faced .gcard-body { position: relative; }
.gcard.is-faced .gcard-chip, .gcard.is-faced .gcard-foot { visibility: hidden; }
.gcard.is-faced .gcard-title { padding-right: 18px; }
/* The layer is the whole body, and only what is drawn on it takes a press:
   the name under it still opens the listing, as it does on any card.  It
   comes in with a short fade, and with none for whoever asks for none. */
.cardface { position: absolute; inset: 0; pointer-events: none; animation: cardface-in .14s ease-out both; }
.cardface > * { pointer-events: auto; }
@keyframes cardface-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .cardface { animation: none; } }
/* The app's own cross, small and quiet, at the end of the name's line: no
   plate of its own, a press as large as the line allows. */
.cardface-close {
  position: absolute; top: 4px; right: 6px; width: 24px; height: 24px;
  display: grid; place-items: center; padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--text-faint);
}
.cardface-close:active { background: rgba(255, 255, 255, .08); }
/* Where the chip, the place and the price were: three lines, each a name on
   the left behind its icon -- the city's own indent, the chip's own ink -- and
   the number on the right, the brightest thing on the line.  One to a line
   because three names side by side do not fit half a phone at a size anybody
   reads; a long number takes its room from the name, never the other way.
   No wider than a card two to a row ever is, so on a card a whole row wide --
   one with the model's advice above it -- a number stays by its name. */
.cardface-room {
  position: absolute; left: 12px; right: 12px; top: 30px; bottom: 6px; margin: 0; max-width: 260px;
  display: flex; flex-direction: column; justify-content: space-between;
}
.cardface-line { display: flex; align-items: center; gap: 8px; height: 21px; min-width: 0; }
.cardface-line dt {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 6px;
  font-size: 12px; line-height: 15px; color: var(--text-dim); white-space: nowrap;
}
.cardface-line dt span { overflow: hidden; text-overflow: ellipsis; }
.cardface-line dt svg { flex: none; color: var(--text-faint); }
.cardface-line dd {
  flex: 0 0 auto; margin: 0; font-size: 15px; line-height: 21px; font-weight: 650; color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em; white-space: nowrap;
}
/* Before the figures arrive, a quiet bar where each number will stand. */
.cardface-wait { display: block; width: 18px; height: 8px; border-radius: 4px; background: currentColor; opacity: .18; }
/* And where they could not be read: what happened, and the way to try again,
   in the same room -- the plate of the buttons in the foot. */
.cardface-failed { justify-content: flex-start; align-items: flex-start; gap: 8px; }
.cardface-failed > span { margin-top: 2px; font-size: 12px; line-height: 15px; color: var(--text-dim); }
.cardface-retry {
  height: 26px; padding: 0 11px; border-radius: 999px;
  font-size: 12px; font-weight: 550; color: var(--text);
  border: 1px solid rgba(125, 133, 146, .6); background: rgba(125, 133, 146, .3);
}

/* ---------- the model's category advice to an author -------------------- */
/* A note, not an alarm: the listing is live, and this is one suggestion about
   where it is filed.  The same plate as a card, a hair lighter, and the three
   answers in the app's own buttons -- the first of them the one we recommend. */
.catsuggest {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px; border-radius: var(--r-card);
  background: var(--glass-1); border: 1px solid var(--glass-line);
  text-align: left;
}
.catsuggest-title {
  display: flex; gap: 7px; align-items: flex-start; margin: 0;
  font-size: 13.5px; font-weight: 650; line-height: 1.35; color: var(--text);
}
.catsuggest-title svg { flex: none; margin-top: 2px; color: var(--accent-hi); }
.catsuggest-pair {
  display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; margin: 0;
  font-size: 13px; line-height: 1.4;
}
.catsuggest-pair dt { color: var(--text-faint); }
.catsuggest-pair dd { margin: 0; color: var(--text); word-break: break-word; }
.catsuggest-why, .catsuggest-ask { margin: 0; font-size: 12.5px; line-height: 1.4; color: var(--text-dim); }
.catsuggest-acts { display: flex; flex-wrap: wrap; gap: 6px; }
.catsuggest-acts .cta { flex: 1 1 auto; min-height: 38px; padding: 8px 12px; font-size: 13px; }
.catsuggest-pick { display: flex; flex-direction: column; gap: 8px; }
.catsuggest-said { margin: 0; font-size: 12.5px; line-height: 1.4; color: var(--accent-hi); }
/* On the screen that says «Опубліковано» the note stands between the words and
   the button, in the width of the words. */
.empty .catsuggest { width: 100%; max-width: 420px; margin: 4px 0 6px; }
/* On the photograph, in the corner a listing's own badge would take. */
/* A badge standing on a photograph, wherever one stands on a photograph: a
   listing's state, an event's, and the kind of thing an event is.  Light
   writing on a dark plate, because what is behind it is a picture and no
   theme can say what colour that is. */
.shotmark {
  position: absolute; top: 8px; left: 8px; z-index: 3;
  display: inline-flex; align-items: center; height: 17.5px;
  padding: 0 9px; border-radius: 999px; max-width: calc(100% - 16px);
  font-size: 10.5px; font-weight: 650; letter-spacing: .02em; line-height: 1;
  color: var(--ink); background: rgba(0, 0, 0, .38);
  border: 1px solid rgba(255, 255, 255, .22);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The owner's own corner: where the state goes, not what it looks like.  The
   badge inside draws its own plate, and a plate around a plate was two pills,
   one inside the other -- which nobody noticed while only a refused listing
   had one, and every card has one now. */
/* Everything there is to say about where a listing stands, in one corner and
   in one column: the state first, and under it whatever is waiting to be read
   about it.  A column of one looks exactly like the single badge it was. */
.minestatus {
  position: absolute; top: 8px; left: 8px; z-index: 3;
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 4px;
  max-width: calc(100% - 16px);
}
/* And when it was refused, the same place in the colour a refusal wears. */
.minerevision.is-refused { color: var(--accent-hi); border-color: var(--action-line); }

/* A change waiting to be read: the same pill as the state above it, a size
   down and a shade lighter, because it is the second thing a card says and not
   the first.  It stood in the foot of the card until now, where it shared the
   room with the owner's own buttons and pushed against them; the foot is
   theirs. */
.minerevision {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px; border-radius: 999px; max-width: 100%;
  font-size: 9.5px; font-weight: 600; letter-spacing: .02em;
  color: var(--star); border: 1px solid var(--star);
  background: rgba(0, 0, 0, .5);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* With a reason under it the pill becomes a note: the same frame, the same
   plate, a second line in a lighter hand. */
.minestatus.with-reason {
  gap: 2px; height: auto; padding: 5px 9px 6px; border-radius: 12px;
  background: rgba(0, 0, 0, .58);
}
.minestatus.with-reason i {
  font-style: normal; font-size: 9.5px; font-weight: 500; letter-spacing: 0;
  line-height: 1.25; color: var(--ink-quiet);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Waiting is neutral, refused is the one state that needs answering, and
   taken down is quiet: colour says which without the reader reading. */
/* The owner's buttons, at the right of the card's foot -- in its flow, not
   laid over the card: the price has the whole width of the words above them,
   whatever its length, its currency, or the number of buttons a state has.
   Their own plate, because a grey mark on a grey card is not a mark.  Each
   keeps its whole 28px to be pressed, and the plate drawn in it is 26 of
   them: the clear pixel round each plate is the two-pixel gap between two,
   so the four stand in 112px rather than 118 -- the six pixels that, with the
   statistics a fourth button on every live card, put any figure past two
   digits (at 360) or three (at 393) on a second line.  The last one stands a
   pixel past the hairline's end; its plate does not. */
.mineacts {
  display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 0;
  margin-left: auto; margin-right: -1px;
}
/* Marks rather than words: two labelled pills are wider than the card they
   stand in on a 320px screen, and on any screen they ran into the price. */
/* The same mark, standing on a photograph: light writing on a dark plate,
   because what is behind it is a picture and no theme can say what colour
   that is. */
.markbtn.onshot {
  width: 28px; height: 28px;
  color: var(--ink); background: rgba(0, 0, 0, .46);
  border-color: rgba(255, 255, 255, .16);
}
/* In the card's foot the owner's buttons no longer stand on a photograph, and
   the dark plate made for one read as three heavy black holes in the card.
   They wear the plate of the category chip on the same card instead -- the
   grey wash and grey outline of `.tagpill, .gcard-chip.tagmark`, one grey for
   both themes, as the chip is -- round, at their full size, with the light
   ink they always had so the marks stay plain to see. */
.mineacts .markbtn.onshot {
  border-color: transparent;
  background: rgba(125, 133, 146, .3); background-clip: padding-box;
  box-shadow: inset 0 0 0 1px rgba(125, 133, 146, .6);
}
/* The bars while their card shows its figures: the accent's own frame and
   mark -- the ones a kept heart wears -- so the press that turns the card back
   is plain to find, and nothing louder than that. */
.mineacts .markbtn.onshot.on {
  color: var(--accent-hi); background-color: var(--action-face);
  box-shadow: inset 0 0 0 1px var(--action-line);
}
.minenotice {
  margin: 0 0 var(--s3); padding: 10px 12px; border-radius: 12px;
  font-size: 12.5px; color: var(--text-dim);
  /* Outlined in the accent: it answers something the reader just did, and a
     neutral hairline made it read as one more row on the page. */
  border: 1px solid rgba(255, 63, 146, .45); background: rgba(255, 255, 255, .04);
}
/* Two actions in a confirmation, and the second is the way out. */
.sheet .cta-ghost.filter-apply { margin-top: 8px; }

/* ---------- the country's own chat ------------------------------------ */
/* One card, because there is one thing here: the group this country's people
   already talk in.  The catalogue card's glass, at a size that suits a single
   destination. */
/* The one chat that is ours: the same frame a partner's row wears, on a much
   darker ground.  It had no surface of its own and borrowed whatever was
   behind it, which on a light page was grey.  Deliberately dark in both
   themes, so every colour on it is written out here rather than read from the
   surface scale -- a dark ground needs light writing whichever theme the rest
   of the page is in. */
.chatcard {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  padding: var(--s4); margin-bottom: var(--s3);
  border-radius: var(--r-card);
  /* Lit in the app's own accent, like the circle standing in it: this is the
     one row on the screen that is ours. */
  border: 1px solid var(--action-line);
  /* Halfway between a partner's glass and the near-black it was: dark enough
     to be the one row that is ours, light enough not to be a hole. */
  background: linear-gradient(rgba(5, 8, 16, .42), rgba(5, 8, 16, .42)), var(--glass-1);
  box-shadow:
    0 0 0 1px rgba(255, 63, 146, .12),
    0 10px 26px -14px rgba(255, 63, 146, .5),
    0 1px 3px rgba(3, 5, 12, .3),
    0 12px 28px -18px rgba(0, 0, 0, .6),
    inset 0 1px 0 rgba(255, 255, 255, .12);
}
.chatcard:active { filter: brightness(1.1); }
.chatcard-flag { flex: none; font-size: 30px; line-height: 1; }
.chatcard-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.chatcard-text b { font-size: 15px; font-weight: 650; color: #fff; }
/* The handle, so where the tap leads is clear before it is taken. */
.chatcard-text i { font-style: normal; font-size: 12.5px; color: rgba(255, 255, 255, .66); }
/* The circle a contact wears: the app's own pink-lit glass, with Telegram's
   own blue on the plane standing in it. */
.chatcard-go {
  flex: none; width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--brand-telegram);
  background: var(--glass-face), var(--action-face);
  border: 1px solid var(--action-line);
  -webkit-backdrop-filter: var(--glass-frost);
  backdrop-filter: var(--glass-frost);
  box-shadow: var(--action-glow), var(--lift-round);
}
.chatnote { margin: 0 0 var(--s4); font-size: 12.5px; line-height: 1.4; color: var(--text-faint); }

/* A heading over a list, used wherever a screen holds more than one list and
   the reader has to be told which is which. */
.listhead {
  margin: var(--s5) 0 var(--s3); font-size: 11px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-faint);
}
.listhead:first-child { margin-top: var(--s2); }
/* A second group of things to add needs air above its heading.  Its heading
   is the first child of its own block, so the rule above shrank the gap to
   nothing and the word sat under the buttons of the group before it. */
.addgroup + .addgroup { margin-top: var(--s6); }

/* Two or three readings of one list.  A row of pills rather than a segmented
   control: the same shape the filters use, so it is already familiar. */
.tabs {
  display: flex; gap: var(--s2); margin-bottom: var(--s4);
  overflow-x: auto; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  flex: 0 0 auto; min-height: 34px; padding: 0 14px;
  border-radius: 999px; border: 1px solid var(--edge-line);
  background: var(--glass-1); color: var(--text-dim);
  font-size: 13px; font-weight: 600;
}
.tab.on {
  border-color: var(--action-line); color: var(--action-ink);
  background: var(--glass-sheen), var(--action-face);
}
.tab:active { filter: brightness(1.12); }

/* A partner's card: a mark, what they are, and what they publish. */
/* Thirteen of these stack down a screen, so they are kept tight: the row is
   a name and a number, and the air around it was doing nothing. */
.partnerlist { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--s4); }
.partnercard {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  padding: 8px var(--s4); border-radius: var(--r-card);
  border: 1px solid var(--edge-line); background: var(--glass-1);
}
.partnercard:active { filter: brightness(1.1); }
.partnercard-mark {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; color: var(--text-dim);
  background: var(--glass-2);
}
/* Telegram's own blue, because that is the mark people know it by -- from
   the same token the contact marks read, so the two cannot drift. */
.partnercard-mark.is-telegram { color: var(--brand-telegram); }
.partnercard-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.partnercard-text b { font-size: 14px; font-weight: 650; color: var(--text-soft); }
.partnercard-text i { font-style: normal; font-size: 12px; color: var(--text-dim); }
.partnercard-go { flex: none; color: var(--text-faint); }

/* ---------- events ----------------------------------------------------- */
/* A card is a frame: what it says, and -- where it is the reader's own -- a
   column of marks down its outer edge.  The same hand as the event's own
   sheet, so a list and the thing it opens look like one place. */
.cardframe {
  display: flex; flex-direction: column;
  border-radius: 18px; overflow: hidden;
}
/* With marks, the card and the column stand side by side. */
.cardframe.has-side { display: grid; grid-template-columns: minmax(0, 1fr) auto; }

/* Marks, not words: three labels of different lengths read as a sentence
   somebody left under the card, and they cost it a whole line. */
.cardside {
  display: flex; flex-direction: column; justify-content: center; gap: 6px;
  /* Three 34px buttons and two 6px gaps come to 114, and the card beside them
     clips anything past its own height -- which is why that height is now the
     taller of the two rather than a figure the column did not fit in. */
  padding: var(--s3) var(--s3) var(--s3) 0;
}
.cardacts {
  display: flex; align-items: center; justify-content: flex-end; gap: 6px;
  padding: 7px 9px; border-top: 1px solid var(--edge-line);
  background: var(--glass-2);
}
.cardacts-ask {
  flex: 1; min-width: 0; font-size: 11.5px; line-height: 1.3; color: var(--text-dim);
}
.markbtn {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--text-soft); background: var(--glass-2);
  border: 1px solid var(--edge-line);
}
.markbtn:active { filter: brightness(1.15); transform: translateY(1px); }
/* One thing only: the press that actually does the irreversible act -- the
   tick a card swaps to once it has asked.  Not the bin that asks: a card is a
   row of controls, and a colour spent before the question is a colour nobody
   reads by the time it is put. */
.markbtn.danger { color: var(--accent-hi); }
/* Kept: the heart fills and the circle takes the accent's own frame. */
.markbtn.on { color: var(--accent-hi); border-color: var(--action-line); background: var(--action-face); }
.markbtn.big { width: 52px; height: auto; border-radius: var(--r-chip); }
/* The one loud action and the heart beside it. */
.eventacts-pair { display: flex; align-items: stretch; gap: var(--s2); }
.eventacts-pair .cta { flex: 1; min-width: 0; }

.eventlist { display: flex; flex-direction: column; gap: var(--s3); margin-bottom: var(--s4); }
.eventcard {
  display: block; width: 100%; text-align: left; padding: var(--s4);
  background: none; border: 0;
}
/* The picture first and the words beside it -- and the picture runs to the
   frame's own edges rather than sitting in a margin, so the card reads as one
   object rather than a photograph placed on a panel. */
.eventcard.has-shot {
  /* Wide enough to see what the photograph is of.  At 96px against a card a
     hundred tall it was a vertical slice out of the middle of a landscape
     picture, which is not a picture of anything. */
  display: grid; grid-template-columns: 132px minmax(0, 1fr); padding: 0;
  align-items: stretch;
}
/* One height for every card in the list, whatever each one holds -- and tall
   enough for the one that holds the most, which is a card with no picture:
   everything the badge would have said on a photograph is said in the words
   instead, and at 124 the last of it was cut off. */
.eventcard { min-height: 140px; overflow: hidden; }
.eventcard:active { filter: brightness(1.1); }
.eventcard-shot {
  position: relative; display: block; width: 132px; align-self: stretch;
  background: var(--glass-2);
}
/* The section's own cover, where an author attached none.  It used to be held
   back from the edges over a plate of its own -- a square icon floating in a
   frame -- which on a phone read as a card with side margins and a foreign
   background.  Now it fills the shot exactly as a photograph does: to the
   frame's edges, cropped rather than squeezed (`object-fit: cover` on a
   square picture in a near-square box crops almost nothing), with the card's
   own outer rounding kept by the frame that clips it.  The open event has
   no fallback at all any more: without a photograph of their own the sheet
   starts on the words. */
.eventcard-img.is-fallback { object-fit: cover; padding: 0; background: none; opacity: 1; }

/* Out of the flow: the picture fills the height the words set and adds
   nothing of its own to it. */
.eventcard-img {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%; object-fit: cover;
}
/* Centred along the foot of the picture rather than pinned to a corner: on a
   card this small the corner belongs to the picture. */
.eventcard-shot .shotmark {
  top: auto; bottom: 7px; left: 50%; transform: translateX(-50%);
  max-width: calc(100% - 14px);
}
.eventcard.has-shot .eventcard-text { padding: var(--s4) var(--s4) var(--s4) var(--s3); }
.eventcard-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* The badge that names what a thing is -- a category, a kind of event, a
   reason somebody is writing.  One rule, because it is one idea: three of
   these in three sections written three ways is how an app stops looking
   like one app. */
/* The badge that names what a thing is -- a category, a kind of event, a
   reason somebody is writing.  One rule and one colour, because it is one
   idea: three of these in three sections written three ways is how an app
   stops looking like one app. */
.tagpill, .gcard-chip.tagmark {
  align-self: start; padding: 2px 9px; border-radius: 999px;
  /* A plain grey outline and a faint grey wash behind it: the badge is a
     label, and a lit edge read as something to press -- but with nothing at
     all behind it the words sat straight on the photograph or the glass and
     could not be read.  Written as a grey of its own, because the hairline
     tokens are white at low opacity, which on a dark ground is a light line
     and not a grey one. */
  /* Enough plate to read on: at a sixth the words still sat half on whatever
     was behind them.  The writing goes up to the app's own soft white with
     it, so the label reads at a glance rather than being deciphered. */
  border: 1px solid rgba(125, 133, 146, .6);
  background: rgba(125, 133, 146, .3);
  color: var(--text-soft);
  font-size: 10.5px; font-weight: 700; letter-spacing: .03em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* The same pill, said quietly: a state rather than a name. */
.tagpill.quiet { border-color: var(--edge-line); color: var(--text-dim); }

/* Where a thing stands.  One badge for the six states the app knows, so a
   listing, an event and a post cannot each invent their own.  Quiet by
   default: a state is a fact about a card, not the point of it. */
.statemark {
  align-self: start; padding: 2px 9px; border-radius: 999px;
  border: 1px solid rgba(125, 133, 146, .6);
  background: rgba(125, 133, 146, .3);
  color: var(--text-soft);
  font-size: 10.5px; font-weight: 650; letter-spacing: .02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* Four tones, not eight colours: a state either wants something of the reader
   or it does not, and a badge for every shade of "nothing to do here" is a
   rainbow that makes the one badge that matters harder to find.  Every colour
   is the design system's own token.
     good    -- it is live, nothing to do
     waiting -- somebody else has it, or its time ran out
     bad     -- it was refused, and there is something to fix
     quiet   -- paused, finished, archived, a draft: no colour at all */
.statemark.is-good { color: var(--ok); border-color: var(--ok); }
.statemark.is-waiting { color: var(--star); border-color: var(--star); }
.statemark.is-bad { color: var(--accent-hi); border-color: var(--action-line); }
.statemark.is-quiet { color: var(--text-soft); }

/* Two badges side by side -- where it is in time, and whether it is on the
   board -- because one word cannot answer both questions. */
.eventcard-marks { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* In daylight the quiet tone is near-black, which is right on the page and
   wrong here: this badge stands on its own dark plate, and the page's ink
   disappeared into it.  The cards that sit on dark glass are given the light
   ink by name in tokens.css; this one is not one of them -- it stands beside
   a card rather than inside it -- so it asks for the same ink itself.  Soft
   white rather than the brightest: it is a quiet state, and the badge should
   read as a note on the picture, not as a headline over it. */
:root[data-theme="day"] .statemark.onshot { --text-soft: var(--ink-soft); }

/* On a photograph it keeps its colour and takes a dark plate under it. */
.statemark.onshot {
  background: rgba(0, 0, 0, .5); border-color: rgba(255, 255, 255, .2);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

/* One type scale for every row-shaped card: the name, the quiet lines under
   it, and the small print.  An event and a person are different things and
   the same object on a screen, so they are set the same way. */
/* The same weight and tone a profile row's name carries: these are the same
   object -- a row in a list of rows -- and there is no reason for two. */
.eventcard-text > b, .personcard-body > b {
  font-size: 15.5px; font-weight: 650; line-height: 1.25; letter-spacing: -.01em;
  color: var(--text-soft);
  /* One line, like the name on a person's card: two of them made one card in
     the list taller than its neighbours, which is the thing a list of rows
     must never do. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.eventcard-line, .personcard-line {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--text-dim);
}
.eventcard-state { font-size: 11.5px; color: var(--text-faint); }
.eventcard-state.rejected { color: var(--accent-hi); }
.eventcard-why { font-size: 11.5px; line-height: 1.35; color: var(--text-dim); }

/* ---------- one event --------------------------------------------------- */
/* A sheet rather than a page of loose paragraphs: a head that carries the
   picture and says what kind of thing this is, a framed panel of the four
   facts a reader comes back to, the words, and only then anything to press.
   Every block sits on the same rails, so the eye lands in the same places on
   an event with a photograph and one without. */
.eventsheet { display: flex; flex-direction: column; gap: var(--s4); }

.eventhero {
  position: relative; overflow: hidden;
  border-radius: 22px; border: 1px solid var(--edge-line);
  background: var(--glass-2);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 10px 28px -14px rgba(0, 0, 0, .55);
}
.eventhero-img { display: block; width: 100%; max-height: 260px; object-fit: cover; }
/* The marks an event without a photograph carries above its title: what it
   is, and whether it is on or over.  Inline, no plate, no height of their
   own beyond the pills -- the sheet must start on the words. */
.eventmarks { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.eventhero-over { left: auto; right: 8px; }
/* The same badge the list draws, put in the corner the photograph leaves. */
.eventhero-when { position: absolute; top: 8px; right: 8px; z-index: 3; }

.eventtitle {
  margin: 0; font-size: 23px; font-weight: 800; letter-spacing: -.028em;
  line-height: 1.14; text-wrap: balance; color: var(--text);
}

/* The four facts, framed, one to a line, each with its own mark. */
.eventfacts { display: flex; flex-direction: column; margin: 0; border-radius: 22px; }
.eventfact {
  display: grid; grid-template-columns: 30px 92px minmax(0, 1fr);
  align-items: center; gap: var(--s2);
  padding: 11px var(--s4);
}
.eventfact + .eventfact { border-top: 1px solid var(--edge-line); }
.eventfact-mark {
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--action-ink); background: var(--glass-2);
  border: 1px solid var(--edge-line);
}
/* The label a fact stands under: bold, and a grey of its own rather than the
   palette's faintest.  Inside a pane the writing scale flips to near-white --
   right over a photograph, unreadable on a light blue block. */
.eventfact dt {
  font-size: 12.5px; font-weight: 700; color: rgba(125, 133, 146, 1);
}
.eventfact dd {
  margin: 0; font-size: 13.5px; line-height: 1.35; color: var(--text-soft);
  white-space: pre-line;
}
.eventfact-away {
  display: block; margin-top: 2px; font-style: normal;
  font-size: 12px; color: var(--action-ink);
}
.eventfact-ask { display: block; margin-top: 3px; padding: 0; font-size: 12px; }

.eventtext {
  margin: 0; padding: 0 2px; font-size: 14.5px; line-height: 1.6;
  color: var(--text-soft); white-space: pre-wrap;
}

/* Booking: its own framed block, because it is the one thing on the screen
   that has to be done before the day rather than on it. */
.eventbook { padding: var(--s4); border-radius: 22px; border-color: var(--action-line); }
.eventbook h3 {
  margin: 0; font-size: 11px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--action-ink);
}
.eventbook p { margin: 5px 0 var(--s3); font-size: 12.5px; color: var(--text-dim); }
.eventbook .contactways {
  position: static; justify-content: flex-start; flex-wrap: wrap; gap: var(--s4);
}

/* One shape, one row: everything left to do about an event. */
.eventmarks { display: flex; align-items: center; gap: var(--s3); }
.eventmarks .markbtn { width: 44px; height: 44px; }
/* The second action is quieter than the first: one screen, one loud button. */
.cta.quiet {
  color: var(--text-soft); background: var(--glass-2);
  border-color: var(--edge-line); box-shadow: none;
}

/* Its author's own two.  Pills with marks rather than two links of different
   lengths sitting at different heights. */
.eventown { display: flex; flex-wrap: wrap; gap: var(--s2); }
.ownbtn {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 36px; padding: 0 14px; border-radius: 999px;
  border: 1px solid var(--edge-line); background: var(--glass-1);
  color: var(--text-soft); font-size: 13px; font-weight: 600;
}
.ownbtn:active { filter: brightness(1.12); }
.ownbtn svg { color: var(--text-faint); }
.ownbtn.danger { color: var(--accent-hi); border-color: var(--action-line); }
.ownbtn.danger svg { color: var(--accent-hi); }

/* ---------- finding your people ---------------------------------------- */
.peoplelist { display: flex; flex-direction: column; gap: var(--s3); margin-bottom: var(--s4); }
/* A picture, and everything else beside it in one column: who, where, what
   they said, what they are looking for, and how to reach them.  No tray under
   the card and no second block -- a card this small reads as one thing or as
   none. */
.peoplelist { display: flex; flex-direction: column; gap: var(--s3); margin-bottom: var(--s4); }
/* One height for every card in the list, whatever each one holds: a tall
   photograph or a long sentence used to stretch its own card and nothing
   else's, which made a list of them look like a mistake.
   Every line inside is held to one line, so the height is the same for all of
   them without being written down -- and written down it was nine pixels
   short of what a card with no picture holds, which cut the row of contacts
   in half and pushed the strip under it off the frame. */
.personcard {
  display: grid; grid-template-columns: minmax(0, 1fr);
  /* The same air above and below the words as a card with a picture gives
     them, so the two read as one kind of object. */
  padding: 12px var(--s4); min-height: 148px;
}
.personcard.has-face {
  grid-template-columns: 116px minmax(0, 1fr); padding: 0; align-items: stretch;
}
.personcard.has-face .personcard-body { padding: 12px var(--s4) 12px var(--s3); }
.personcard-facebtn {
  padding: 0; border: 0; background: none; overflow: hidden;
  display: block; width: 116px; align-self: stretch; position: relative;
  background: var(--glass-2);
}
.personcard-facebtn:active { filter: brightness(1.1); }
/* The same: it fills the card, it does not set its height. */
.personcard-face {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%; object-fit: cover;
}
.personcard-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }

/* The name, and the heart at the far end of its own line. */
.personcard-head { display: flex; align-items: flex-start; gap: var(--s2); }
.personcard-head > b {
  flex: 1; min-width: 0;
  font-size: 15.5px; font-weight: 650; letter-spacing: -.01em; line-height: 1.25;
  color: var(--text-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A heart in a card's corner, drawn like the one a listing wears: the mark
   alone, no plate under it. */
.heartbtn {
  flex: none; width: 28px; height: 28px; margin: -3px -4px 0 0;
  display: grid; place-items: center; border: 0; background: none;
  color: var(--text-faint);
}
.heartbtn.on { color: var(--accent); }
.heartbtn:active { transform: scale(.94); }

/* Where and what kind, on one quiet line: two facts that place the person
   without competing with their name. */
.personcard-meta {
  margin: 0; font-size: 12px; line-height: 1.35; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Who is asking: quieter than what they are asking for, never more than one
   line, so every card in the list is the same height. */
.personcard-about {
  margin: 3px 0 0; font-size: 13px; line-height: 1.4; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* What they are looking for: the line the card exists for. */
.personcard-wants {
  display: flex; align-items: baseline; gap: 6px; min-width: 0;
  margin: 2px 0 0; font-size: 13.5px; line-height: 1.35; font-weight: 600;
  color: var(--text);
}
.personcard-wants svg { flex: none; color: var(--accent); transform: translateY(1px); }
.personcard-wants { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The ways to reach them, and passing the card on at the far end. */
.personcard-ways {
  display: flex; align-items: center; gap: var(--s2); margin-top: 8px;
}
.personcard-ways .contactways { flex: 1 1 auto; justify-content: flex-start; }
.personcard-ways .markbtn { margin-left: auto; }
.markbtn.small { width: 28px; height: 28px; }

/* Marks alone, at the size a card can spare. */
.contactways.compact { gap: var(--s2); row-gap: var(--s2); }
.contactways.compact .contactway > i { width: 32px; height: 32px; }
.contactways.compact .contactway > i svg { width: 16px; height: 16px; }

/* A call to action that sits under a list rather than over the screen. */
.cta.wide { margin: var(--s3) 0 var(--s5); }
.cta.on { color: var(--text-soft); background: var(--glass-2); border-color: var(--edge-line); }

/* Where an offer to work with us stands, in the panel's own list. */
.partnerstate { font-size: 11px; font-weight: 650; color: var(--text-faint); }
.partnerstate.is-agreed { color: var(--ok); }
.partnerstate.is-declined { color: var(--accent-hi); }
.partnerstate.is-new { color: var(--star); }

/* A line at the foot of a screen that says what the place is for.  Centred,
   and with a hairline above it holding it away from the list of doors: it is
   the one thing on the screen that is not asking to be pressed, and it should
   read as a close rather than as a last item. */
.slogan {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  margin: var(--s8) var(--s2) var(--s6); padding-top: var(--s6);
  text-align: center; text-wrap: balance;
  border-top: 1px solid var(--edge-line);
}
.slogan-flag { display: grid; place-items: center; margin-bottom: 3px; }
.slogan b {
  font-size: 15px; font-weight: 700; letter-spacing: -.01em; color: var(--text-soft);
}
.slogan span { font-size: 12.5px; color: var(--text-faint); }

/* ---------- information hub -------------------------------------------
   Compact editorial cards, not hero tiles: one wide card opens the section,
   pairs carry the everyday topics, and the two closing cards go wide again. */
.infogrid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.infocard {
  position: relative; overflow: hidden; text-align: left;
  grid-column: span 1; min-height: 134px;
  border-radius: 20px; padding: 14px;
  display: flex; flex-direction: column; justify-content: space-between; gap: 12px;
  /* A hairline that is lit rather than drawn: the edge should be felt at the
     top and almost gone at the foot, the way glass catches a room. */
  border: 1px solid rgba(255, 255, 255, .09);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
  background: var(--card) center/cover no-repeat;
  transition: transform .14s ease;
}
.infocard.wide {
  grid-column: span 2; min-height: 96px;
  flex-direction: row; align-items: center; gap: 14px;
}
/* The card that opens the section: no icon, because its own eyebrow carries
   the emphasis, and a taller frame so the photograph has somewhere to be. */
.infocard.lead { min-height: 128px; align-items: stretch; }
/* Neutral glass over the photograph: it darkens where the type sits and does
   nothing else.  A coloured tint pulled seven scenes into one tonality, but it
   also took each picture's own colour away, which is the thing worth keeping. */
.infocard[data-art]::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(102deg,
      rgba(9, 10, 12, .62) 0%, rgba(9, 10, 12, .32) 48%, rgba(9, 10, 12, .06) 100%),
    linear-gradient(180deg, rgba(9, 10, 12, 0) 0%, rgba(9, 10, 12, .34) 100%),
    /* The matte film: even, colourless, barely there. */
    linear-gradient(180deg, rgba(255, 255, 255, .06) 0%, rgba(255, 255, 255, .05) 100%);
}
.infocard > * { position: relative; z-index: 1; }
.infocard[data-art] .infocard-text { text-shadow: 0 1px 10px rgba(6, 8, 12, .6); }
.infocard:active { transform: scale(.985); }
.infocard-icon {
  flex: none; width: 40px; height: 40px; border-radius: 13px;
  display: grid; place-items: center; color: rgba(255, 255, 255, .94);
  background: rgba(255, 255, 255, .07); border: 1px solid rgba(255, 255, 255, .15);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.infocard-body { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-width: 0; }
.infocard.wide .infocard-body { flex: 1; }
.infocard-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; color: var(--ink); }
.infocard-eyebrow {
  font-size: 9.5px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(255, 255, 255, .58); margin-bottom: 1px;
}
.infocard-text b { font-size: 15px; font-weight: 700; letter-spacing: -.01em; line-height: 1.2; }
.infocard.wide .infocard-text b { font-size: 16px; }
.infocard.lead .infocard-text b { font-size: 19px; }
.infocard-text > span { font-size: 11.5px; line-height: 1.3; color: rgba(255, 255, 255, .66); }
.infocard.lead .infocard-text > span { font-size: 12.5px; }
/* The arrow is outlined in the brand pink, never filled with it: on a card
   this quiet a solid dot would be the loudest thing on the screen. */
.infocard-go {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; color: var(--accent-hi);
  background: rgba(255, 63, 146, .07); border: 1px solid rgba(255, 63, 146, .48);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.infocard.wide .infocard-go { width: 36px; height: 36px; }

/* The brand block at the foot: two lines, the first in the same hand as the
   line under the name, the second plain because it is an instruction. */
/* The brand's own line closes every screen that carries it; the rule is
   `.slogan`, written once, near the community's own styles. */

/* The daylight switch sits in the row of selectors and is the width of one
   glyph: it has nothing to say until it is pressed.  Not a `.picker`, though
   it stands beside two -- it opens no list, and the tests that count the
   selectors on that row would have counted it as a third. */
.daylight {
  position: relative; isolation: isolate;
  flex: none; width: 34px; height: 30px; border-radius: var(--r-chip);
  display: inline-flex; align-items: center; justify-content: center;
  /* The white the two selectors beside it are lettered in, so the three read
     as one row rather than as two things and a fainter one. */
  color: var(--text-soft); border: 1px solid transparent;
}
.daylight::before { display: none; }
.daylight:active { transform: translateY(1px); }

/* The daylight switch again, spelled out this time: in the settings it stands
   among named things and a bare glyph would be the one unlabelled control on
   the screen. */
.themeswitch {
  position: relative; isolation: isolate;
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  /* The corners the two selectors above it turn, not corners of its own.
     They take theirs from the cover screen's rule, which reaches them by
     name; this reads the same token rather than the number it resolves to. */
  height: var(--settings-row); padding: 0 12px; border-radius: var(--r-chip);
  border: 1px solid transparent; text-align: left;
  /* The words the two selectors above it set their values in: same size,
     same weight, same white. */
  font-size: 13px; line-height: 1.2; font-weight: 650; color: var(--text-soft);
}
.themeswitch::before { display: none; }
/* And the mark in the grey those selectors name themselves in. */
.themeswitch svg { flex: none; color: var(--text-faint); }
.themeswitch:active { transform: translateY(1px); }

/* Daylight only: a plate under every screen.  It is the thing that was once
   under the profile's three buttons by accident -- a filter with no ground of
   its own, so what shows is the page behind it, blurred and a tenth darker --
   and it turned out to be what the light theme wanted underneath it.

   Its backdrop is the two fixed layers below it and nothing else: the page's
   own content paints above it, so scrolling does not make it redraw. */
:root[data-theme="day"] .page::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  backdrop-filter: var(--pane-blur);
  -webkit-backdrop-filter: var(--pane-blur);
}

/* The profile's own glass is thinner than the rest of the app's.  On that
   screen the panes are the screen -- five buttons, two selectors and a card --
   and at the usual density they read as a wall rather than as things standing
   on one.

   In daylight it is thinned, since there the page behind is bright and less
   plate means more of it.  In the dark it is not thinned at all any more: the
   buttons stood lighter than every card in the app, and a screen whose panes
   are a different weight from the rest is the thing this was meant to avoid. */
:root[data-theme="day"] .page[data-screen="profile"] { --glass-plate: var(--glass-plate-soft); }
/* Daylight, in the profile: the buttons are a soft grey card rather than a
   dark pane, and what is written on them goes dark with the page.  The plate
   itself turns over with the token; only the writing is named here.

   It has to be named on the panes themselves: the block that keeps a pane's
   writing light sets it on each pane, and a value set on an element beats one
   it would otherwise inherit from this page. */
:root[data-theme="day"] .page[data-screen="profile"] :is(
  .profile-line, .picker, .themeswitch, .rowcard, .panel
) {
  --text: var(--page-ink);
  --text-soft: var(--page-ink-soft);
  --text-dim: var(--page-ink-dim);
  --text-faint: var(--page-ink-faint);
  color: var(--page-ink);
}

/* ---------- the offer to keep us on the home screen -------------------- */
/* It arrives the way a notification does: over the top of the screen, under
   whatever Telegram floats there, and it leaves pushed up with a finger or
   closed with the cross.  Fixed rather than in the page, so it belongs to the
   app and not to the screen that happens to be open. */
.toast {
  position: fixed; z-index: 30;
  left: var(--s3); right: var(--s3);
  top: calc(var(--safe-t) + var(--s2));
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 18px;
  --glass-plate: var(--glass-plate-soft);
  isolation: isolate;
  box-shadow: var(--pane-lift);
  color: var(--chrome-text);
  /* Off the top until it is shown, and back up when it is waved away. */
  transform: translateY(calc(-100% - var(--safe-t) - var(--s4)));
  opacity: 0;
  transition: transform .34s cubic-bezier(.22, 1, .36, 1), opacity .2s ease;
}
.toast.in { transform: translateY(0); opacity: 1; }
.toast-mark {
  width: 34px; height: 34px; flex: none; border-radius: 12px;
  display: grid; place-items: center; color: #fff;
  background: var(--neon-face);
}
.toast-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.toast-text b { font-size: 13px; font-weight: 650; }
.toast-text > span { font-size: 11.5px; color: var(--chrome-text); opacity: .74; }
.toast.is-good .toast-mark { background: var(--ok); }
.toast.is-bad .toast-mark { background: var(--alarm); }
.toast-go {
  flex: none; padding: 7px 13px; border-radius: var(--r-chip);
  font-size: 12.5px; font-weight: 650; color: #fff;
  background: var(--neon-face); box-shadow: var(--neon-accent);
}
.toast-close {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center; color: var(--chrome-text);
  background: rgba(125, 133, 146, .18);
}
.toast-close:active { background: rgba(125, 133, 146, .3); }
/* What the phone said when it would not do it: there is no error to catch,
   so the note is the only report there is. */
.settingsnote {
  margin: -2px 4px 0; font-size: 11.5px; line-height: 1.35; color: var(--text-faint);
}
/* Already there: a statement rather than a button. */
.themeswitch.is-done { color: var(--ink-quiet); }
.themeswitch.is-done svg { color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .toast { transition: none; } }

/* ---------- the search field that follows the reader ------------------- */
/* Past the top of a catalogue the field gives up its place in the page and
   folds to the right, into the one round mark it already carries, and rides
   along at the corner.  It folds towards its own button rather than away from
   it: the mark the reader ends up pressing is the mark that was there all
   along, so nothing appears out of nowhere.

   Width is what animates, anchored right -- the element keeps its right edge
   and loses its left, which is the fold read as a movement rather than as a
   swap.  Everything inside it fades a little ahead of the fold, or the words
   would still be there in a field too narrow to hold them. */
.searchbar.afloat {
  position: fixed; z-index: 12;
  top: calc(var(--safe-t) + var(--s2));
  right: var(--page-side); left: auto;
  width: 42px; min-height: 42px; padding: 3px; margin: 0;
  justify-content: flex-end; overflow: hidden;
  transition: width .28s cubic-bezier(.22, 1, .36, 1);
}
.searchbar.afloat > :not(.searchgo) {
  opacity: 0; transition: opacity .12s ease;
  pointer-events: none;
}
/* Opened where the reader is: the full field again, still fixed, still riding
   at the top of the screen. */
.searchbar.afloat.open {
  left: var(--page-side); right: var(--page-side); width: auto;
  padding: 3px 3px 3px 16px; justify-content: flex-start;
}
.searchbar.afloat.open > :not(.searchgo) {
  opacity: 1; transition: opacity .2s ease .12s; pointer-events: auto;
}
/* The page keeps the room the field left, so nothing jumps when it folds. */
.catalog-search-gap { height: 42px; margin-bottom: var(--s3); }
@media (prefers-reduced-motion: reduce) {
  .searchbar.afloat, .searchbar.afloat > * { transition: none; }
}


/* ---- the promotion panel ----
   An internal tool for two people, opened from the bot.  It borrows the
   app's tokens and nothing else: no section colour, no hero, no chrome --
   the whole screen is one column of figures somebody is going to be paid
   on, and every line of it has to be readable beside the same figures in a
   chat message. */
.promo { display: flex; flex-direction: column; gap: var(--s4); padding: var(--s4) 0 40px; }
.promo h1 { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: -.01em; }
.promoheading { margin: var(--s4) 0 0; font-size: 15px; font-weight: 650; }
.promostate { margin: 0; font-size: 13.5px; color: var(--text-dim); }
.promoperiod { display: flex; gap: 9px; }
.promosteps { display: flex; flex-direction: column; gap: 10px; }
/* Each step is a card so that the sentence under a figure plainly belongs to
   it: run together in one column, the note of one step read as the heading
   of the next, which is how seven true numbers became unreadable. */
.promostep {
  display: flex; flex-direction: column; gap: 4px;
  padding: 13px 14px; border-radius: 14px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.promostep-head {
  margin: 0; font-size: 12.5px; color: var(--text-dim);
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
}
/* A field in the panel.  Plain on purpose: this is a tool for one person, and
   the glass the public forms wear would be decoration here. */
.adminfield {
  width: 100%; min-height: 40px; padding: 9px 12px;
  border-radius: 10px; border: 1px solid var(--edge-line);
  background: var(--glass-1); color: var(--text);
  font: inherit; font-size: 16px; outline: none;
}
.adminfield::placeholder { color: var(--text-faint); }

/* What is wrong with the field above it.  Under the field rather than at the
   foot of the form: a line by the publish button is read after the reader has
   already hunted for the red outline. */
/* Said at the top of a form the moment it opens: what stops it, and what to
   do about it.  A refusal after the typing costs somebody the whole form. */
.limitnote {
  display: flex; gap: var(--s3); align-items: flex-start;
  padding: var(--s3) var(--s4); margin-bottom: var(--s4);
  border-radius: var(--r-card); border: 1px solid var(--action-line);
  background: var(--glass-2);
}
.limitnote-mark { flex: none; color: var(--action-ink); margin-top: 1px; }
.limitnote b { display: block; font-size: 14px; font-weight: 650; color: var(--text); }
.limitnote i {
  display: block; margin-top: 3px; font-style: normal;
  font-size: 12.5px; line-height: 1.4; color: var(--text-dim);
}

/* What is WRONG with one field, under that field -- which is not `.fieldnote`,
   the quiet word a field says about itself and which never stopped anybody.
   Its own ink, because "you have not filled this in" and "this cannot be
   undone" are not the same thing said twice, and because a form of fourteen
   questions lit in full accent reads as a form of fourteen mistakes. */
.fieldsay {
  margin: 6px 0 0; font-size: 12.5px; line-height: 1.35; color: var(--field-note);
}
.promostep-head b { color: var(--text); font-weight: 650; }
.promostep-figure { margin: 0; font-size: 16px; font-weight: 650; }
.promostep-note { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--text-faint); }
.promosections { margin: 2px 0 0; padding-left: 18px; font-size: 13.5px; color: var(--text-dim); }
.promosections li { margin: 2px 0; }
.promoaddress { display: flex; flex-direction: column; gap: 5px; }
/* The address is a button rather than a link: tapping it copies, because a
   tap that opened it would spend a click on the very counter below. */
.promocopy {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; padding: 10px 12px; border-radius: 12px; text-align: left;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.promocopy code {
  min-width: 0; overflow-x: auto; white-space: nowrap;
  font-size: 12.5px; color: var(--text);
}
.promocopy span { flex: none; font-size: 11.5px; color: var(--accent-hi); }
.promolinks { display: flex; flex-direction: column; gap: 8px; }
.promolink {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 12px 13px; border-radius: 13px; text-align: left;
  background: var(--glass-1); border: 1px solid var(--glass-line); color: var(--text);
}
.promolink-mark { flex: none; }
.promolink-name { flex: 1; min-width: 0; font-size: 14px; font-weight: 600;
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.promolink-count { flex: none; font-size: 12.5px; color: var(--text-faint);
                   font-variant-numeric: tabular-nums; }


/* One fact about a link, and the way to change it: the label, the value it
   has, and a chevron only when pressing it would do something.  A watcher
   sees the same rows and none of them move. */
.promofacts { display: flex; flex-direction: column; gap: 6px; }
.promofact {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 11px 13px; border-radius: 12px; text-align: left; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.promofact:disabled { opacity: 1; }
.promofact-label { flex: none; font-size: 12.5px; color: var(--text-faint); }
.promofact-value { flex: 1; min-width: 0; font-size: 13.5px; }
.promolink.on { border-color: var(--action-line); color: var(--action-ink); }
/* Switching a link off is not the thing anybody came here to do, so it does
   not wear the accent that the one thing they did come for wears. */
.promostop {
  width: 100%; padding: 12px; border-radius: 12px; font-size: 14px;
  color: var(--text-dim); background: var(--glass-1);
  border: 1px solid var(--glass-line);
}


/* The period chips on the panel.  A chip that is on has to say so: pressing
   one and seeing nothing change is pressing a button that does not work. */
.promo .chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: 999px; font-size: 13px; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--glass-line);
  transition: transform .12s ease;
}
.promo .chip:active { transform: scale(.97); }
.promo .chip.on {
  color: var(--action-ink);
  background: var(--action-face);
  border-color: var(--action-line);
  box-shadow: var(--action-glow);
}
.promoperiod { flex-wrap: wrap; }


/* ---- the owner's own figures ----
   Six numbers people actually watch, laid out so the eye finds them, and two
   tables underneath where the comparison matters more than the exact count. */
.admintiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: 9px; }
.admintile {
  display: flex; flex-direction: column; gap: 2px;
  padding: 13px 14px; border-radius: 14px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.admintile b { font-size: 22px; font-weight: 700; letter-spacing: -.02em;
               font-variant-numeric: tabular-nums; }
.admintile span { font-size: 12px; line-height: 1.35; color: var(--text-faint); }
.adminrows { display: flex; flex-direction: column; gap: 7px; }
.adminrow { display: flex; align-items: center; gap: 10px; }
.adminrow-label { flex: none; width: 40%; font-size: 13px;
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The bar is the comparison the numbers alone make you do in your head. */
.adminrow-bar { flex: 1; height: 8px; border-radius: 999px;
                background: var(--edge-ground); overflow: hidden; }
.adminrow-bar i { display: block; height: 100%; border-radius: 999px;
                  background: var(--action-line); }
.adminrow-count { flex: none; min-width: 2.5em; text-align: right;
                  font-size: 13px; font-variant-numeric: tabular-nums; }
/* Two figures per row need a heading, or the dot between them is a riddle. */
.adminlegend { margin: 0 0 -2px; font-size: 11.5px; color: var(--text-faint); text-align: right; }
.adminrows + .adminlegend { margin-top: 2px; }
.adminrow-count { min-width: 4.2em; }

/* Fourteen days of arrivals, tall enough to read a shape off and no taller.
   A day nobody came still draws a sliver, so the row reads as fourteen days
   rather than as however many had somebody in them. */
.adminchart { display: flex; align-items: flex-end; gap: 3px; height: 54px; }
.adminchart-day { flex: 1; height: 100%; display: flex; align-items: flex-end; }
.adminchart-day i { display: block; width: 100%; border-radius: 3px 3px 0 0;
                    background: var(--action-line); }


/* What an empty catalogue leaves a reader to do: put the first listing in it.
   Being told when somebody else does is the bell on the header. */
.emptyacts { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: var(--s3); }
.emptyacts .cta { display: inline-flex; align-items: center; gap: 7px; width: auto; }

/* The bell on a catalogue's header (components/SectionBell.tsx).  Secondary,
   so it takes no height of its own: the size of the back button it stands
   opposite, cut from the filter chips' material just below it -- a dark face
   in both themes, so its ink is the one that does not move with daylight.  A
   dot in the accent says something in the section is followed. */
.bellbtn {
  position: relative; width: 36px; height: 36px; flex: none; border-radius: 50%;
  display: grid; place-items: center; color: var(--ink-quiet);
  background: var(--edge-ground); border: 1px solid transparent;
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 1px 3px rgba(3, 5, 12, .4);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
}
.bellbtn:active { filter: brightness(1.2); }
.belldot {
  position: absolute; top: 7px; right: 8px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 2px var(--edge-ground);
}
/* Where Telegram draws its own back button the app hides its own; this takes
   its place, so the title stays in the middle with the bell beside it. */
.bellbalance { display: none; width: 36px; height: 36px; flex: none; }
.app[data-native-back="true"] .screenbar .bellbalance { display: block; }
/* The sheet behind it: the filters' own head and rows, a line of why, and the
   cities under a quiet caption. */
.bellsheet .belllead { margin: -4px 0 12px; font-size: 13px; line-height: 1.4; color: var(--text-dim); }
.bellsheet .filter-option { min-height: 36px; }
.bellsheet .bellcaption { margin: 12px 10px 4px; font-size: 12px; color: var(--text-faint); }
.bellsheet .bellwait { height: 72px; }
.bellsheet .bellunread p { margin: 0 0 10px; font-size: 13px; color: var(--text-dim); }
.bellsheet .bellothers { margin: 14px 10px 0; font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }
.bellsheet .bellothers b { display: block; font-weight: 650; color: var(--text); }
.bellsheet .bellothers ul { margin: 4px 0; padding-left: 18px; }
.bellsheet .bellothers p { margin: 0; color: var(--text-faint); }
.bellsheet .bellfailed { margin: 12px 10px 0; font-size: 13px; color: var(--undone-ink); }
.bellsheet .bellok { margin-top: 14px; }
/* «Шукають моделей»: what is kept now, said above the form -- lit when on, quiet when off (owner, 11.10.2026). */
.bellsheet .bellstate { margin: -4px 10px 12px; font-size: 13px; font-weight: 650; line-height: 1.4; color: var(--accent-hi); }
.bellsheet .bellstate.is-off { font-weight: 500; color: var(--text-faint); }
.bellsheet .belloff { margin-top: 8px; }

/* ---- an event that is not happening ----
   Said once, at the top of the sheet, and small.  Somebody who opened a link
   to it has to know before they read the day and the address -- but it is off,
   not on fire, and a full red screen would be shouting about it. */
.eventoff {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 0 0 var(--s3); padding: 10px 14px; border-radius: var(--r-card);
  background: var(--undone-face); border: 1px solid var(--undone-line);
}
.eventoff svg { align-self: center; color: var(--undone-ink); flex: none; }
.eventoff b { font-size: 13.5px; font-weight: 700; color: var(--undone-ink); }
.eventoff > span { font-size: 12.5px; color: var(--text-dim); }
/* The way in to calling it off: an ordinary button, because pressing it only
   asks the question.  Nothing here is dangerous yet. */
.eventoff-ask {
  display: block; width: 100%; margin-top: var(--s3); padding: 11px;
  border-radius: var(--r-card); border: 1px solid var(--line);
  background: none; color: var(--text-dim); font: inherit; font-size: 13.5px;
}
.eventoff-sure {
  display: flex; flex-direction: column; gap: 4px;
  margin-top: var(--s3); padding: 14px; border-radius: var(--r-card);
  border: 1px solid var(--undone-line); background: var(--undone-face);
}
.eventoff-sure b { font-size: 14.5px; font-weight: 700; }
.eventoff-sure > span { font-size: 12.5px; line-height: 1.4; color: var(--text-dim); }
.eventoff-buttons { display: flex; gap: 8px; margin-top: 8px; }
.eventoff-keep, .eventoff-do {
  flex: 1; padding: 11px; border-radius: 11px; font: inherit;
  font-size: 13.5px; font-weight: 600;
}
/* The face this button wears is `--surface`, and `--surface` is one of the
   handful of dark surfaces the daylight switch deliberately leaves alone --
   the switch moves the ground and the writing on it, never the surfaces.  So
   the writing has to come from the light set that goes with a dark surface
   (`--ink`), not from the page's own ink (`--text`), which turns grey-black at
   sunrise: `--text` on `--surface` in daylight is #14161b on #15181d, which is
   a contrast of 1.02 and a button with nothing written on it.  `--ink` is
   #f6f7f9 on the same face in both themes: 16.6. */
.eventoff-keep { border: 1px solid var(--line); background: var(--surface); color: var(--ink); }
/* The one thing on the screen that wears the danger, and only once there is
   nothing after it to change anybody's mind. */
.eventoff-do { border: 0; background: var(--undone); color: #fff; }
.eventoff-do:disabled { opacity: .6; }

/* The reach-them pill as a real link: once a `tel:` or `mailto:` way is
   armed, the button becomes an anchor to it and must look like the button
   it was, not like a line of underlined text. */
a.pill-cta { text-decoration: none; }

/* ---- the ways to reach somebody, down the right edge -----------------------
   One wide "Зв'язатися" became the channels themselves: a reader sees Viber
   and presses Viber.  They used to share the bottom bar with the heart and
   the share button -- three round things in a row above the navigation, and
   the one thing a reader came for was the hardest of the three to find.  The
   rail stands at the middle of the right edge instead: alone, under the
   thumb at any scroll position, the same distance from the text on every
   screen, and over nothing. */
.contactrail {
  position: fixed; z-index: 7;
  /* Against the right edge, clear of a landscape notch. */
  right: calc(var(--s3) + var(--safe-r));
  /* Halfway down the viewport, not halfway down the page: fixed, so scrolling
     does not take it away.  The nudge upwards keeps a tall stack clear of the
     navigation while leaving a single circle honestly centred. */
  top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s3);
  /* The rail is a frame around the circles and catches nothing itself, so a
     tap in the gap between two of them reaches the page underneath. */
  pointer-events: none;
  /* A stack taller than the screen scrolls inside itself rather than running
     under the navigation.  Bounded by the viewport less the navigation and
     both safe areas, so the last circle is always reachable. */
  /* The Mini App's own visible height, which Telegram changes when the sheet
     is dragged or a keyboard opens -- `--tg-viewport-height` is kept in step
     with `viewportStableHeight` by `lib/telegram`.  `100dvh` is the fallback
     for a browser outside Telegram. */
  max-height: calc(var(--tg-viewport-height, 100dvh)
                   - var(--nav-h) - var(--nav-floor) - var(--nav-lift) - var(--s8));
  /* NOT a scroll container.  `overflow-y: auto` here made the rail its own
     backdrop root, and then each circle's `backdrop-filter` filtered an empty
     backdrop -- which paints a flat square of the glass behind every round
     button.  The same trap this file already names under `.searchbar`, and
     the same one the old contact strip avoided.  A stack too tall for the
     screen wraps into a second column instead, leftwards, away from the edge
     and still on it. */
  flex-wrap: wrap-reverse;
}
/* PLAIN GLASS, NOT THE ACCENT.  These wore the strong-action plate: a pink
   ring (`--action-line`), and in daylight a pink wash and a pink halo as well
   (`--action-face`, `--action-glow`).  Three haloed circles down the edge of
   every listing were the loudest thing on the screen, and the one colour that
   should be loud here is the messenger's own -- Telegram blue, WhatsApp
   green, Viber purple, which is what a reader is looking for.  So the circle
   is the same quiet glass the heart beside it is cut from, and the colour
   left on it belongs to the service, not to the brand. */
.contactgo {
  flex: none; display: grid; place-items: center; text-decoration: none;
  pointer-events: auto;
  width: 52px; height: 52px; border-radius: 50%;
  color: var(--text);
  background: var(--glass-face), var(--glass-plate-soft);
  border: 1px solid var(--line-strong);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: var(--lift-round);
  transition: transform .14s ease, box-shadow .18s ease, border-color .18s ease;
}
.contactgo > i { display: grid; place-items: center; }
.contactgo:active { transform: translateY(1px) scale(.97); }
/* Keyboard focus stays plainly visible and is the page's own ink rather than
   the accent: the strongest contrast this theme has, in both of them, and no
   pink. */
.contactgo:focus-visible {
  outline: 2px solid var(--text); outline-offset: 3px;
}
.contactgo[disabled] { opacity: .5; }
.contactgo[aria-busy="true"] { opacity: .72; }
/* Each messenger in the colour people recognise it by -- the shape at this
   size cannot do that work alone.  Only the ones that have a colour of their
   own: a phone and an e-mail are nobody's, and stay the app's own ink. */
.contactgo.is-telegram { color: var(--brand-telegram); }
.contactgo.is-whatsapp { color: var(--brand-whatsapp); }
.contactgo.is-viber { color: var(--brand-viber); }
.contactgo.is-instagram { color: var(--brand-instagram); }
.contactgo.is-facebook { color: var(--brand-facebook); }
/* Noticed, not shouted (owner, 15.10.2026: the contact circles should draw the eye -- premium, never fairy lights).
   Two quiet movements, both on the circles at the edge only:
   - arriving, they slide in from the edge one after another, so the eye follows them to where they stand;
   - standing, each breathes out one soft ring in its messenger's own colour, in turn, every few seconds -- a
     glance's worth of light, then stillness.
   `translate` rather than `transform`, so a press (`:active`) still sinks the circle while it arrives; the ring is
   its own layer and catches nothing.  Reduced motion: neither. */
.contactrail:not(.is-labelled) .contactgo {
  position: relative;
  animation: contact-arrive 520ms cubic-bezier(.22, .61, .36, 1) both;
}
.contactrail:not(.is-labelled) .contactgo:nth-child(2) { animation-delay: 80ms; }
.contactrail:not(.is-labelled) .contactgo:nth-child(3) { animation-delay: 160ms; }
.contactrail:not(.is-labelled) .contactgo:nth-child(n+4) { animation-delay: 240ms; }
@keyframes contact-arrive {
  from { opacity: 0; translate: 18px 0; }
  to { opacity: 1; translate: 0 0; }
}
.contactrail:not(.is-labelled) .contactgo::after {
  content: ""; position: absolute; inset: -1px; border-radius: inherit; pointer-events: none;
  box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 42%, transparent);
  opacity: 0;
  animation: contact-breathe 7.2s cubic-bezier(.33, 0, .2, 1) 1.6s infinite;
}
.contactrail:not(.is-labelled) .contactgo:nth-child(2)::after { animation-delay: 2.4s; }
.contactrail:not(.is-labelled) .contactgo:nth-child(3)::after { animation-delay: 3.2s; }
.contactrail:not(.is-labelled) .contactgo:nth-child(n+4)::after { animation-delay: 4s; }
@keyframes contact-breathe {
  0% { opacity: 0; box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 42%, transparent); }
  4% { opacity: 1; }
  18% { opacity: 0; box-shadow: 0 0 0 11px color-mix(in srgb, currentColor 0%, transparent); }
  100% { opacity: 0; box-shadow: 0 0 0 11px color-mix(in srgb, currentColor 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .contactrail:not(.is-labelled) .contactgo, .contactrail:not(.is-labelled) .contactgo::after { animation: none; }
}
/* The circles float over the page's right edge: the line «Оголошення перевірене… · Поскаржитися» runs to that edge,
   so where they stand it stops short of them rather than running under them (owner's screenshot, 15.10.2026). */
.app:has(.contactrail:not(.is-labelled)) .trustbar { padding-right: calc(52px + var(--s3)); }
/* On the site, in the page (ContactRail labelled, web UX audit B2): the same ways as buttons that say what they do,
   one under another -- standing where the way in stood for a guest. */
.contactrail.is-labelled {
  position: static; transform: none; max-height: none; pointer-events: auto;
  flex-direction: column; align-items: stretch; flex-wrap: nowrap; gap: var(--s2);
}
.contactrail.is-labelled .contactgo {
  width: auto; height: 48px; border-radius: var(--r-chip);
  display: flex; align-items: center; justify-content: flex-start; gap: 10px; padding: 0 16px;
  font: inherit; font-size: 14px; font-weight: 650;
}
.contactrail.is-labelled .contactgo > span { color: var(--text); }
/* Armed: a real link to a `tel:` or `mailto:`, lit so it reads as the next
   press rather than as the same button again -- by a brighter edge, not by a
   colour. */
.contactgo.is-armed { border-color: var(--edge-line); }
@media (hover: hover) {
  .contactgo:hover { border-color: var(--edge-line); box-shadow: 0 8px 22px rgba(0, 0, 0, .4); }
}
@media (prefers-reduced-motion: reduce) { .contactgo { transition: none; } }
/* The rail reserves NOTHING.  It used to push the whole page left by its own
   width so that a value at the end of a line could never be under it -- and
   that moved every card, heading and picture on the screen to buy room for
   three circles.  It is an overlay: the page keeps the width it has without
   it, and the circles float over the corner.  Nothing here may touch `.page`.

/* A short screen -- a phone held sideways -- has room for the circles or for
   the listing, not both at this size.  Smaller and closer, still a thumb's
   worth: 44px is the smallest target a finger reliably hits. */
@media (max-height: 560px) {
  .contactrail { gap: var(--s2); right: calc(var(--s2) + var(--safe-r)); }
  .contactgo { width: 44px; height: 44px; }
}

/* ---- the contact, shown: where a press could not open it from here ------- */
.contactshown {
  flex: 1 1 100%; order: -1; min-width: 0; pointer-events: auto;
  display: grid; gap: var(--s2);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-card);
  /* The same glass the save and share buttons beside it stand on, and the
     bar at the foot: the soft plate, the frost, the hairline, the round
     lift, and the page's own writing on it.  It used to be a dark pane of
     its own -- black over a light page, a window from some other app
     dropped onto this one. */
  color: var(--text);
  background: var(--glass-face), var(--glass-plate-soft);
  border: 1px solid var(--line-strong);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: var(--lift-round);
}
.contactshown-head { display: flex; align-items: center; gap: var(--s2); }
/* The channel's own colour on its mark, as the rail's circles wear it; the
   accent has no business here. */
.contactshown-head i { display: grid; place-items: center; color: var(--text-dim); }
.contactshown.is-telegram .contactshown-head i { color: var(--brand-telegram); }
.contactshown.is-whatsapp .contactshown-head i { color: var(--brand-whatsapp); }
.contactshown.is-viber .contactshown-head i { color: var(--brand-viber); }
.contactshown-head b {
  flex: 1; min-width: 0; font-size: 12px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim);
}
/* Said in a word as well as a mark, and tall enough for a thumb. */
.contactshown-close {
  flex: none; height: 34px; padding: 0 12px 0 10px; border-radius: var(--r-chip);
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-weight: 600; color: var(--text);
  background: var(--glass-face), var(--glass-plate-soft);
  border: 1px solid var(--line-strong);
}
/* Large and selectable: a number is read off a screen and typed elsewhere. */
.contactshown-value {
  font-size: 21px; font-weight: 700; letter-spacing: .01em; color: var(--text);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
  user-select: all; -webkit-user-select: all;
}
/* Wraps: on a narrow phone the words take a line and the button the next,
   rather than the two squeezing each other. */
.contactshown-foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s2) var(--s3);
  font-size: 12.5px; line-height: 1.35; color: var(--text-dim);
}
.contactshown-foot span { flex: 1 1 180px; min-width: 0; }
/* The one action, on the same glass: no accent plate and no halo.  The
   number is the bright thing here, not the button under it. */
.contactshown-copy {
  flex: none; height: 40px; padding: 0 16px; border-radius: var(--r-chip);
  font-size: 13.5px; font-weight: 700; color: var(--text);
  background: var(--glass-face), var(--glass-plate-soft);
  border: 1px solid var(--line-strong);
  box-shadow: var(--lift-round);
}
.contactshown-copy:active, .contactshown-close:active { transform: translateY(1px) scale(.98); }
.contactshown-copy:focus-visible, .contactshown-close:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
  .contactshown { animation: contactshown-in .18s ease-out; }
  @keyframes contactshown-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
}

/* ---- the partner cabinet: three sections ----
   One long screen had been the figures, the links and the partners stacked
   on top of each other, and nobody could find the one thing they had opened
   it for.  The tabs are the whole fix: what somebody came to READ, what they
   came to MAKE, and who they brought in.  Narrow phones only ever get this
   panel, so everything here wraps rather than scrolls sideways. */
.promotabs {
  display: flex; gap: 6px; padding: 4px; border-radius: 14px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
/* A finger, not a cursor: 40px is the floor, and three of them share the
   width evenly so no tab is a harder target than its neighbours. */
.promotab {
  flex: 1 1 0; min-width: 0; min-height: 40px; padding: 0 6px;
  border-radius: 11px; font-size: 13.5px; font-weight: 600; color: var(--text-dim);
  background: transparent; border: 1px solid transparent;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: transform .12s ease;
}
.promotab:active { transform: scale(.98); }
.promotab.on {
  color: var(--action-ink); background: var(--action-face);
  border-color: var(--action-line); box-shadow: var(--action-glow);
}
.promotab:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 2px; }

/* What the figures under it are about, said in words.  The chips say it too,
   but they are above the fold and the numbers are below it. */
.promoactive {
  display: flex; flex-wrap: wrap; gap: 6px; margin: 0;
  font-size: 12px; color: var(--text-faint);
}
.promoactive span {
  padding: 3px 9px; border-radius: 999px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
  overflow-wrap: anywhere;
}

/* The four figures the cabinet exists for.  Two to a row at every phone
   width: four across would put a five-digit number under a two-word label. */
.promocards { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.promocard {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
  padding: 12px 13px; border-radius: 13px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.promocard-figure {
  font-size: 22px; font-weight: 700; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.promocard-label {
  font-size: 12px; line-height: 1.3; color: var(--text-faint); overflow-wrap: anywhere;
}

/* One dimension of the slice, cut into rows.  The label may be a city nobody
   shortened and the count may be four digits, so the label gives way and the
   count never does. */
.promobreak { display: flex; flex-direction: column; gap: 6px; }
.promorows { display: flex; flex-direction: column; gap: 6px; }
.promorow {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 9px 12px; border-radius: 11px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.promorow-label { min-width: 0; font-size: 13.5px; overflow-wrap: anywhere; }
.promorow-count {
  flex: none; font-size: 12.5px; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

/* Експорт: three rows of chips, the number, then the list.  The list is a
   column of ids that a thumb can select, a page at a time; copying takes all
   of them from the server, never the page. */
.promoexport { display: flex; flex-direction: column; gap: 10px; }
/* One action on this tab: a pill a thumb can hit, in the cabinet's own glass. */
.promoact {
  appearance: none; cursor: pointer; font: inherit; font-size: 13.5px; font-weight: 600;
  padding: 10px 14px; border-radius: 12px; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--line-strong);
}
.promoact:disabled { opacity: .45; cursor: default; }
.promoact:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
.promoexport-row { display: flex; flex-direction: column; gap: 6px; }
.promoexport-title { font-size: 11.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; }
.promoexport-list { display: flex; flex-direction: column; gap: 8px; }
.promoexport-tools { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.promoexport-people {
  margin: 0; padding: 0 0 0 1.6em; display: flex; flex-direction: column; gap: 4px;
  font-size: 13.5px; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.promoexport-people code { font-size: 13px; }
.promoexport-none { color: var(--text-faint); }

/* The platform's own figures, at the foot of «Огляд».  Set apart from the
   partner's cards by a rule above and a plate of its own, because a number
   about everybody beside «Мої залучені» would be read as theirs.  The
   warning is bold and first.  Four columns that never wrap: the country
   gives way, the three counts keep their width.  The total card takes the
   whole first line, the two figures it splits into share the second. */
.promoglobal {
  display: flex; flex-direction: column; gap: 10px;
  margin-top: var(--s5); padding-top: var(--s4);
  border-top: 1px solid var(--line-strong);
}
.promoglobal-heading { margin: 0; font-size: 15px; font-weight: 650; }
.promoglobal-warning {
  margin: 0; padding: 10px 12px; border-radius: 11px;
  font-size: 12.5px; line-height: 1.35; letter-spacing: .01em;
  background: var(--glass-plate-soft); border: 1px solid var(--line-strong);
}
.promoglobal-table { display: flex; flex-direction: column; gap: 4px; }
.promoglobal-cards .promoglobal-total { grid-column: 1 / -1; }
.promoglobal-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 3.6em 5.2em 5.6em;
  align-items: baseline; gap: 8px;
  padding: 8px 12px; border-radius: 11px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
  font-size: 13.5px; font-variant-numeric: tabular-nums;
}
.promoglobal-row > :first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.promoglobal-row > :not(:first-child) { text-align: right; }
.promoglobal-head {
  background: transparent; border-color: transparent; padding-top: 0; padding-bottom: 0;
  font-size: 10.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .02em;
  align-items: end;
}
.promoglobal-head > span { white-space: normal; line-height: 1.15; }

/* A partner in a row: their name, then what the panel may say about them.
   Two lines, because a name and a date on one line is a name cut in half. */
.promoperson { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.promoperson-name {
  font-size: 14px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.promoperson-when { font-size: 11.5px; color: var(--text-faint); overflow-wrap: anywhere; }

/* What a screen says instead of a figure it cannot vouch for.  A zero here
   would be read as an answer, and somebody is paid on these numbers. */
.promotrouble {
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
  padding: 14px; border-radius: 13px;
  background: var(--glass-1); border: 1px solid var(--action-line);
}
.promotrouble p { margin: 0; font-size: 13.5px; color: var(--text); }

/* Every chip the cabinet draws is a finger's target.  The rows keep the shape
   they already had -- this only puts a floor under the height, because the
   panel is read on a phone held in one hand. */
.promotaps .chip { min-height: 40px; }

/* ---- the house reading somebody else's cabinet ----
   An admin-only strip, and it is the server that says who is an admin: the
   list of cabinets comes back empty for everybody else, so nothing here is
   drawn off a flag the app decided for itself.

   The banner wears the action's own face rather than the panel's glass,
   because the one thing it must never do is read as ordinary content: every
   figure under it belongs to somebody who is not the person reading. */
.promopreview { display: flex; flex-direction: column; gap: 8px; }
.promopreview-said {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 10px 12px; border-radius: 13px; color: var(--action-ink);
  background: var(--action-face); border: 1px solid var(--action-line);
  box-shadow: var(--action-glow);
}
.promopreview-mark { flex: none; }
/* The name gives way and the way out never does: a partner named after the
   whole group they post in must not push the button that ends the preview off
   the side of a phone. */
.promopreview-name {
  flex: 1 1 auto; min-width: 0; font-size: 13.5px; font-weight: 650;
  overflow-wrap: anywhere;
}
.promopreview-out {
  flex: none; min-height: 40px; padding: 8px 14px; border-radius: 999px;
  font-size: 13px; font-weight: 600; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
/* A line of its own at every width: beside the button it read as a second
   label on it rather than as a footnote to the name above. */
.promopreview-state { flex: 1 0 100%; font-size: 11.5px; }
/* Long names are the rule rather than the exception here, so the row wraps
   instead of scrolling sideways and each name is cut inside the width it is
   given.  Forty pixels because this is read on a phone held in one hand. */
.promopreview-list { display: flex; flex-wrap: wrap; gap: 8px; }
.promopreview-one {
  max-width: 100%; min-height: 40px; padding: 8px 14px; border-radius: 999px;
  font-size: 13px; color: var(--text); text-align: left;
  background: var(--glass-1); border: 1px solid var(--glass-line);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.promopreview-one.on {
  color: var(--action-ink); background: var(--action-face);
  border-color: var(--action-line); box-shadow: var(--action-glow);
}

/* ---- one invited partner, on a screen of their own ----
   The name is the title, and it may be the name of a whole group somebody
   posts in, so it wraps rather than being cut: a title cut at the phone's
   edge is a title that names nobody. */
.promotitle {
  margin: 0; font-size: 18px; font-weight: 700; letter-spacing: -.01em;
  overflow-wrap: anywhere;
}

/* ---- the zones of «Посилання» ----
   A small label over each of the three things the tab holds: the button
   that makes a link, the chips that narrow and order the list, and the list.
   With nothing between them the button ran into the chips and the chips
   into the rows, and the whole tab read as one control.  Typography and air
   do the whole job -- no rule, no card -- because a divider between things
   this close would be louder than the things.  The column's own gap already
   sits under the label; it is pulled back a little so the label sits nearer
   to what it names than to what it follows.  No width and no nowrap: the
   label is read on a 320-px phone and must never be the thing that scrolls. */
.promozone {
  margin: var(--s5) 0 calc(-1 * var(--s2)); font-size: 11px; font-weight: 650;
  line-height: 1.3; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-faint); overflow-wrap: anywhere;
}

/* Wrapping belongs to the taps themselves, not to the period row they share
   a class with: a row of chips that stops being a period row must go on
   wrapping, because a row that scrolls sideways is a row whose last chip
   nobody on a narrow phone ever finds. */
.promotaps { flex-wrap: wrap; }

/* ---- the house reading somebody else's cabinet, as one line ----
   The card this replaced took the top of a 320-px phone, so the cabinet
   somebody had opened the preview to study began below the fold and the
   banner was what they actually saw.  One row instead, wrapping rather than
   scrolling sideways, so the tabs are the very next thing under it.

   Quiet on purpose.  The cabinet below has to read exactly as the partner's
   own, and a loud band across the top is itself a difference between the two
   screens; the way out is the one part that wears the action's face, because
   it is the only thing here anybody presses in a hurry. */
.promostrip { display: flex; flex-direction: column; gap: 6px; }
.promostrip-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 5px 12px; border-radius: 999px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.promostrip-mark { flex: none; font-size: 13px; }
/* The name gives way and the way out never does: a partner named after the
   whole group they post in must not push the button that ends the preview off
   the side of a phone. */
.promostrip-name {
  flex: 1 1 auto; min-width: 0; padding: 0; text-align: left;
  font-size: 12.5px; font-weight: 650; line-height: 1.3; color: var(--text);
  background: none; border: 0; overflow-wrap: anywhere;
}
.promostrip-out {
  flex: none; min-height: 30px; padding: 5px 12px; border-radius: 999px;
  font-size: 12px; font-weight: 600; color: var(--action-ink);
  background: var(--action-face); border: 1px solid var(--action-line);
}
/* Behind the name while a preview is open, and open while none is: long
   names are the rule here, so the row wraps and each name is cut inside the
   width it is given rather than dragging the list sideways. */
.promostrip-list { display: flex; flex-wrap: wrap; gap: 6px; }
.promostrip-one {
  max-width: 100%; min-height: 34px; padding: 6px 12px; border-radius: 999px;
  font-size: 12.5px; color: var(--text); text-align: left;
  background: var(--glass-1); border: 1px solid var(--glass-line);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.promostrip-one.on {
  color: var(--action-ink); background: var(--action-face);
  border-color: var(--action-line); box-shadow: var(--action-glow);
}

/* ---------- «Шукають моделей» (Beauty model requests) ------------------------------------------------------ */
/* Discovery is a teaser (owner, 08.10.2026): a compact module and a short card, everything else on the request's
   own screen.  One accent (the app's own), no gradient, no glow, no large icon -- a list item, not a banner.
   The card wears the same glass frame as an event's card (.cardframe), so the two lists are one hand. */
/* About 80px in all: «Нове» still begins on the first screen under it. */
/* 8px under it rather than the 12 of a section: the frame grew 2px twice (owner, 08.10.2026) and «Нове» stays put. */
.mspot { margin: 0 0 8px; }
/* On Home it stands between the search and the six sections (owner, 08.10.2026): the search keeps its own 16px
   above it, and below it the sections' own 12px -- the step between the tiles -- so the module reads as the
   first row of that block, and «Нове» keeps the top of its first card on a 360x780 screen. */
/* Under the six sections, before «Нове» (owner, 10.10.2026): the sections' 16px above it, 16px down to «Нове». */
.mspot.is-home { margin-bottom: var(--s4); }
/* «Дивитися всі ›» as «Нове» writes it (.rail-all), with a press taller than it looks. */
.mspot.is-home .mspot-head .rail-all { padding: 8px 0 8px 12px; margin: -8px 0; line-height: 1.3; }
/* The steps on Home: small neutral marks by the arrow, the one on show longer and darker -- no pink, no glow. */
.mspot-card .mspot-steps { gap: 4px; margin-right: -4px; }
.mspot-card .mspot-steps i { width: 4px; height: 4px; border-radius: 2px; background: var(--text-faint); opacity: .45; }
.mspot-card .mspot-steps i.is-on { height: 12px; opacity: 1; background: var(--text-dim); }
.mspot-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3); margin-bottom: 4px;
}
.mspot-title { margin: 0; font-size: 14.5px; line-height: 20px; font-weight: 700; letter-spacing: -.01em; }
/* How many are live (12.10.2026): the counter the author page's groups carry, beside the title. */
.mspot-count { margin-left: 6px; }
.newall-more { margin: var(--s3) auto 0; }
.mspot-head .rail-all { padding: 0; line-height: 20px; }
/* The frame is one fixed height whatever it shows, so nothing under it ever moves -- and a low one: two lines
   (owner, 08.10.2026: the chips, then «… — потрібна модель»; the terms are the request's own screen's), 48px, the
   two centred in it about 6px from either edge, the picture the full height at the left.  50px (owner, 08.10.2026:
   1px more above and below -- the lines, their gap and their sizes as they were, only the frame round them). */
.mspot-item {
  position: relative; display: flex; flex-direction: row; align-items: center; gap: 10px; width: 100%; height: 50px;
  padding: 0 10px 0 0; text-align: left; font: inherit; color: inherit; overflow: hidden;
}
.mspot-item > svg { flex: none; color: var(--text-faint); }
.mspot-item:active { background: var(--action-face); }
/* Several: the frame keeps a vertical drag for itself -- the page scrolls from anywhere else -- and a drag does
   not select the words it passes over. */
.mspot-item.swipes { touch-action: pan-x; user-select: none; -webkit-user-select: none; }
/* Every request is drawn once, stacked; a turn only changes classes.  At rest one is on show and the others wait,
   hidden.  During a turn (.turning) the outgoing one moves out a little and fades while the next comes in from
   the other side: forward, out upwards and in from below; back (.back), out downwards and in from above.
   750 ms (owner, 08.10.2026: calmer than 600), a soft ease-out, no spring, no snap, nothing sideways.  Keyframes rather than transitions: the incoming one starts from
   its own side whichever way the last turn went, and nothing moves when the module is first drawn. */
.mspot-slides { position: relative; flex: 1; min-width: 0; align-self: stretch; }
.mspot-slide {
  position: absolute; inset: 0; display: flex; align-items: center; gap: 10px; min-width: 0;
  opacity: 0; visibility: hidden; pointer-events: none;
}
.mspot-slide.is-current { opacity: 1; visibility: visible; }
.mspot-slide.is-leaving { visibility: visible; }
.mspot-item.turns.turning .mspot-slide.is-current { animation: mspot-in-up 750ms cubic-bezier(.33, 1, .68, 1) both; }
.mspot-item.turns.turning .mspot-slide.is-leaving { animation: mspot-out-up 750ms cubic-bezier(.33, 1, .68, 1) both; }
.mspot-item.turns.turning.back .mspot-slide.is-current { animation-name: mspot-in-down; }
.mspot-item.turns.turning.back .mspot-slide.is-leaving { animation-name: mspot-out-down; }
/* The outgoing one is gone by 60% of the turn and the incoming one shows from 30%, so the two never read as
   one smudged line -- the frame rolls rather than cross-fades. */
@keyframes mspot-in-up {
  0% { opacity: 0; transform: translate3d(0, 22px, 0); } 30% { opacity: 0; } 100% { opacity: 1; transform: none; }
}
@keyframes mspot-out-up {
  0% { opacity: 1; transform: none; } 60% { opacity: 0; } 100% { opacity: 0; transform: translate3d(0, -22px, 0); }
}
@keyframes mspot-in-down {
  0% { opacity: 0; transform: translate3d(0, -22px, 0); } 30% { opacity: 0; } 100% { opacity: 1; transform: none; }
}
@keyframes mspot-out-down {
  0% { opacity: 1; transform: none; } 60% { opacity: 0; } 100% { opacity: 0; transform: translate3d(0, 22px, 0); }
}
/* Home: «Шукають моделей» as one card (owner, 09.10.2026) -- a plain light surface in daylight, the neutral card of
   the dark theme at night, 18px corners, a fine neutral line, a faint shadow; no glass, no pink, no glow.  12px
   inside, 12px between the photo and the words, 4px between the three lines; at least 88px, taller where the words
   need it -- the slides share one grid cell, so the card is as tall as its tallest request and never jumps. */
.mspot.is-home .mspot-head { margin-bottom: 8px; }
.mspot-item.mspot-card {
  /* The photo is the card's left edge, top to bottom (owner, 09.10.2026): no inset on that side; the card's
     own rounding clips its outer corners, its right edge is straight.  The words keep 12px round them. */
  height: auto; min-height: 88px; padding: 0 12px 0 0; gap: 12px; align-items: center;
  border-radius: 18px; color: var(--text);
  background: var(--soft-surface); border: 1px solid var(--soft-surface-line);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .22), 0 8px 18px -12px rgba(0, 0, 0, .45);
}
.mspot-item.mspot-card:active { background: var(--card-hi); }
:root[data-theme="day"] .mspot-item.mspot-card {
  /* Quieter than white (owner, 09.10.2026): the very light neutral the search shares, over the grey page. */
  background: var(--soft-surface); border-color: var(--soft-surface-line);
  box-shadow: 0 1px 2px rgba(15, 20, 30, .06), 0 6px 16px -8px rgba(15, 20, 30, .12);
}
:root[data-theme="day"] .mspot-item.mspot-card:active { background: rgba(244, 245, 247, .98); }
.mspot-card .mspot-slides { display: grid; align-self: stretch; }
/* The photo stands out of the flow, the slide's full height: a portrait picture can never make the card taller --
   the words set the height, the photo fills it. */
.mspot-card .mspot-slide {
  position: relative; inset: auto; grid-area: 1 / 1; align-items: center; min-height: 86px; padding-left: 84px;
}
.mspot-photo {
  /* height 100%, not auto: a picture placed out of the flow keeps its own ratio otherwise and runs past the card. */
  position: absolute; left: 0; top: 0; width: 72px; height: 100%; border-radius: 0;
  object-fit: cover; object-position: 50% 50%; overflow: hidden;
}
.mspot-photo.is-empty {
  /* The sparkle in the brand's own pink (owner, 10.10.2026), as every model placeholder draws it; the plate stays. */
  display: grid; place-items: center; color: var(--accent);
  background: var(--line); border: 0; border-right: 1px solid var(--line-strong);
}
:root[data-theme="day"] .mspot-photo.is-empty { background: rgba(20, 22, 27, .05); border-right-color: rgba(20, 22, 27, .08); }
.mspot-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; padding: 12px 0; }
.mspot-name {
  font-size: 16px; font-weight: 650; line-height: 1.25; letter-spacing: -.01em; color: var(--text);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere;
}
.mspot-when { font-size: 13px; line-height: 1.3; color: var(--text-dim); overflow-wrap: anywhere; }
.mspot-pay { font-size: 14px; font-weight: 650; line-height: 1.3; color: var(--text-soft); overflow-wrap: anywhere; }
.mspot-card > svg { flex: none; color: var(--text-faint); }

/* Less motion asked for: the next request simply takes the place of the last. */
@media (prefers-reduced-motion: reduce) {
  .mspot-item .mspot-slide { animation: none !important; }
  .mspot-slide.is-leaving { visibility: hidden; }
  .mspot-steps i { transition: none; }
}
/* Several: a thin vertical step indicator by the arrow -- one short tick a request, the one on show longer and
   in the accent.  Nothing at all for one request. */
.mspot-steps {
  flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  margin-right: -3px;
}
.mspot-steps i {
  display: block; width: 3px; height: 5px; border-radius: 2px;
  background: var(--text-faint); opacity: .5;
  transition: height 360ms cubic-bezier(.22, .61, .36, 1), opacity 360ms ease-out, background-color 360ms ease-out;
}
.mspot-steps i.is-on { height: 12px; opacity: 1; background: var(--accent-hi); }
.mspot-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
/* The picture is the frame's left edge (owner, 08.10.2026): flush with it, top to bottom, its outer corners the
   frame's own (the frame clips them), 45px wide -- so the words start exactly where they did beside the old 38px square
   and its 7px inset. */
.mspot-thumb {
  flex: none; align-self: stretch; width: 49px; height: auto; border-radius: 0; object-fit: cover; overflow: hidden;
  /* 4px wider than it was, taken from the gap to the words (owner, 08.10.2026): a square picture loses about 6% a
     side to the slot rather than 10%, and the words still start where they did. */
  margin-right: -4px;
}
/* The crystal sits in the middle of its square: centred, nothing of it is cut at either side. */
.mspot-thumb.is-art { object-position: 50% 50%; }
.mspot-thumb.is-empty, .mreq-card-thumb.is-empty {
  display: grid; place-items: center; color: var(--accent);
  background: var(--action-face); border: 1px solid var(--action-line);
}
/* The empty square is the frame's edge too: a hairline only where it meets the words. */
.mspot-thumb.is-empty { border: 0; border-right: 1px solid var(--action-line); }
.mspot-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
/* category → day → city, one row whatever they say: the day is never cut; the category gives way first, then the
   city, each to an ellipsis -- never to a second line. */
.mspot-chips { display: flex; gap: 3px; min-width: 0; overflow: hidden; }
.mspot-chip {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 10.5px; font-weight: 650; line-height: 1; padding: 2px 5px; border-radius: 999px;
  /* The text colours are the «Нове» cards' own (owner, 08.10.2026): the chips in the .gcard-chip.tagmark grey, the name in
     .gcard-title's white, the rest in .gcard-where's quiet grey -- the module reads as one of those cards. */
  /* Their writing the grey «потрібна модель» is written in -- the module's own quiet ink -- rather than near-white
     (owner, 08.10.2026, the dark theme): plate, frame and size as they were.  Daylight writes them its own grey below. */
  color: var(--ink-quiet); border: 1px solid var(--edge-line);
}
/* On «Послуги» (owner, 08.10.2026) the three chips wear the plate of the category chip on the cards right under
   them -- the grey outline, the grey wash and the soft writing of `.tagpill, .gcard-chip.tagmark`, the same grey in
   both themes -- at their own compact size, so the row, its cuts and the frame are as they were.  Weightless
   (:where), so «Сьогодні» is written like the rest.  Home keeps the chips it has. */
:where(.mspot:not(.is-home)) .mspot-chip {
  border-color: rgba(125, 133, 146, .6); background: rgba(125, 133, 146, .3); color: var(--ink-quiet);
}
.mspot-chip.is-day { flex: none; }
/* Only the category gives way: the day and the city are read whole -- a city cut short only past 9em. */
.mspot-chip.is-category { flex-shrink: 1; min-width: 4.5em; }
.mspot-chip.mspot-city { flex-shrink: 0; max-width: 9em; display: inline-flex; align-items: center; gap: 2px; padding-left: 3px; }
/* A pin costs the row 12px; the chips gave back 1px a side, the gaps 2px and the city chip 3px of its own, so
   nothing is cut sooner for it. */
/* The pin of .gcard-where, at the chip's size, in the chip's own colour, centred on its words. */
.mspot-city svg { flex: none; display: block; }
.mspot-city-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.mspot-line {
  font-size: 13px; line-height: 16px; color: var(--ink-quiet);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mspot-line b { color: rgba(255, 255, 255, .8); font-weight: 650; }
/* Daylight (owner, 08.10.2026): the white of the «Нове» cards sank into the light glass.  The module reads there
   as the light glass of the search and the header chips does -- in the page's own inks, the name in the soft one
   rather than black, the rest in the dim one -- and its chips take the catalogue's grey tag plate
   (.tagpill / .gcard-chip.tagmark).  The dark theme is left exactly as it was. */
:root[data-theme="day"] .mspot-item { --ink-quiet: var(--page-ink-dim); }
/* In daylight the name is written in the page's own soft ink (owner, 09.10.2026): clear and solid on this light
   glass -- not the white of the dark theme, not a half-dimmed mix of it.  The dark theme is left as it is. */
:root[data-theme="day"] .mspot-line b { color: var(--page-ink-soft); }
:root[data-theme="day"] .mspot-chip { border-color: rgba(125, 133, 146, .6); background: rgba(125, 133, 146, .3); }
/* Their writing in daylight a grey, not the near-black of the name (owner, 08.10.2026): the module's own second ink,
   the one its terms were written in -- the plate, the size and the places as they were; «Сьогодні» keeps its
   accent.  Home and «Послуги» alike: one module. */
:root[data-theme="day"] .mspot-chip { color: var(--page-ink-dim); }
/* «Сьогодні» is one chip like the other two (owner, 09.10.2026): no pink writing, frame or glow of its own. */
/* The request's screen: the sum and its currency in a service card's price pink (owner, 08.10.2026), the kind of
   payment before it in the row's own writing. */
.mreq-sum { color: var(--price-ink); font-weight: 650; }

/* The picture is the card's left edge, top to bottom (owner, 08.10.2026): flush with the frame, which rounds its outer
   corners; the words keep the card's own inset on the other three sides. */
/* Every card in the list one height (owner, 09.10.2026): fixed, whatever its picture or words -- a portrait photo
   no longer stretches its card -- the title kept to one line and the chips to one row. */
.mreq-card {
  display: flex; flex-direction: row; align-items: stretch; gap: var(--s3); width: 100%; height: 112px;
  padding: 0 var(--s3) 0 0; text-align: left; font: inherit; color: inherit;
  /* No transparent 1px border round it, which left a hairline of the page between the picture and the edge: the
     glass draws its own ring, and the picture covers it where it stands. */
  border: 0;
}
.mreq-card-thumb {
  flex: none; align-self: stretch; width: 88px; height: 100%; min-height: 0; border-radius: 0;
  object-fit: cover; object-position: 50% 50%; overflow: hidden;
}
.mreq-card-body {
  flex: 1; min-width: 0; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  padding: var(--s3) 0;
}
/* With its author's buttons: one press across the whole card under them, so a button never opens it. */
.mreq-card.has-acts { position: relative; }
.mreq-card-hit {
  position: absolute; inset: 0; z-index: 1; padding: 0; border: 0; border-radius: inherit; background: none;
}
/* The author's two marks (.mineacts .markbtn.onshot): a column of their own at the right edge, one above the
   other, centred -- above the press across the card, so a mark never opens it. */
.mreq-card-acts {
  position: relative; z-index: 2; flex: none; align-self: center;
  flex-direction: column; flex-wrap: nowrap; gap: 6px; margin: 0 0 0 auto;
}
.mreq-card-top .tagpill { min-width: 0; flex: 0 1 auto; }
/* «+2»: how many more categories were chosen -- never the one to give way (owner, 11.10.2026). */
.mreq-card-top .tagpill.is-more, .mspot-chip.is-more { flex: none; }
/* «✓ Можна обрати декілька варіантів»: straight under the field's name, in the field note's quiet ink, its tick
   in the lit chip's colour -- the one mark that the chips below take more than one. */
.form .fieldnote.is-multi {
  display: flex; align-items: center; gap: 5px; margin: -2px 2px 2px; font-size: 12px; color: var(--text-dim);
}
.form .fieldnote.is-multi svg { flex: none; color: var(--accent-hi); }
.mreq-card-top .statemark { flex: none; }
/* Daylight: the lit pink and the lit yellow of a state are a glow on the light card, not words (owner, 08.10.2026:
   «Відхилено» could not be read).  There they take the deep inks daylight already writes in -- the field note's pink
   and the housing mood's deep amber; the green is daylight's own already. */
:root[data-theme="day"] :is(.mreq-card, .mreq-state) .statemark.is-bad { color: var(--field-note); }
:root[data-theme="day"] :is(.mreq-card, .mreq-state) .statemark.is-waiting { color: var(--c-housing-b); }

.mreq-card-top { display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; min-width: 0; }
.mreq-card-title {
  flex: none; font-size: 15px; font-weight: 700; line-height: 1.3; letter-spacing: -.01em;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden;
}
.mreq-card-line, .mreq-card-terms {
  display: flex; align-items: center; gap: 5px; min-width: 0;
  font-size: 12.5px; line-height: 1.3; color: var(--text-dim);
}
.mreq-card-line svg { flex: none; }
.mreq-card-city { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mreq-when { flex: none; }
/* The terms: their kind in the row's grey, the sum in a card's price (.gcard-price) -- the card's one accent. */
.mreq-card-line, .mreq-card-terms { flex: none; }
.mreq-card-terms svg { flex: none; }
.mreq-card-kind { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mreq-card-price { flex: none; margin: 0 0 0 2px; padding: 0; align-self: center; }
.mreq-hero {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 16px; overflow: hidden;
}
/* The press that opens the picture whole: the picture's own frame, nothing added to it. */
.mreq-hero-open {
  display: block; width: 100%; padding: 0; border: 0; background: none; border-radius: 16px; cursor: zoom-in;
  text-align: inherit; font: inherit; color: inherit;
}
.mreq-hero-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.mreq-photos { display: flex; flex-wrap: wrap; gap: var(--s2); }
.mreq-photo {
  position: relative; width: 72px; height: 72px; padding: 0; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--edge-line); background: none;
}
.mreq-photo img, .mreq-photo .skeleton, .mreq-photo .media-missing { display: block; width: 100%; height: 100%; object-fit: cover; }
.mreq-photo[aria-pressed="true"] { border: 2px solid var(--accent-hi); }
.mreq-photo-mark {
  position: absolute; right: 4px; bottom: 4px; width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center; color: #fff; background: var(--accent-hi);
}
.mreq-photo-sub { margin: var(--s2) 0 6px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.mreq-list { display: flex; flex-direction: column; gap: var(--s3); margin-bottom: var(--s4); }
/* «Інші заявки на моделей» under a request (owner, 09.10.2026): the catalogue's cards under a section head, a section
   step from the words above; where the contact rail floats at the right edge the cards keep it clear. */
.mreq-similar { margin: var(--s2) 0 var(--s2); }
.mreq-similar .mreq-list { margin-bottom: 0; }
.mreq-similar.has-rail .mreq-card { padding-right: 62px; }
/* «Дивитися всі» at the container's right edge, as «Нове» stands on Home (owner, 10.10.2026); a narrow phone wraps the
   row rather than laying the words over each other. */
.mreq-similar .rail-head { flex-wrap: wrap; row-gap: 4px; }
.mreq-similar .rail-title { min-width: 0; }
/* The contact rail (52px circles, 12px off the screen's edge) floats over whatever the page scrolls under it: on a
   request's page the words keep that column clear -- the title, the facts' values, the requirements and the
   description -- so it never covers a word (owner, 09.10.2026).  The panels keep their full width and look. */
.mreq-sheet.has-rail > .eventtitle, .mreq-sheet.has-rail > .mreq-words { padding-right: 60px; }
.mreq-sheet.has-rail .eventfacts .eventfact { padding-right: 62px; }
/* The list's filters: one row -- «Усі», «Місто», «Тип процедури».  A group's
   choices slide out to the right of its button inside the same row, so the
   row never grows a second line and never changes height; a row wider than
   the screen scrolls sideways, edge to edge, like the catalogue's filter bar.
   Chosen reads as a pink-washed plate with a pink hairline and white words --
   on the dark glass both grounds share -- rather than as a white pill, which
   on this row weighed more than the cards under it. */
.mfilter {
  position: relative;
  display: flex; align-items: center; gap: 8px;
  margin: 0 calc(var(--page-side) * -1) var(--s4);
  padding: 2px var(--page-side) 4px;
  overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.mfilter::-webkit-scrollbar { display: none; }
.mfilter-btn, .mfilter-opt {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; white-space: nowrap;
  color: var(--ink-quiet); background: var(--edge-ground);
  border: 1px solid var(--edge-line);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
  transition: color 320ms ease-out, background-color 320ms ease-out,
              border-color 320ms ease-out, box-shadow 320ms ease-out;
}
.mfilter-btn { height: 36px; padding: 0 13px 0 14px; font-size: 13px; font-weight: 550; }
.mfilter-all { padding: 0 14px; }
/* A chosen value stands in its button, cut short with an ellipsis: two long ones («Cluj-Napoca», «Косметологія /
   догляд») and «Усі» still fit one 360px row with its side gutter. */
.mfilter-label { max-width: 8em; overflow: hidden; text-overflow: ellipsis; }
.mfilter-btn svg { flex: none; opacity: .7; transform: rotate(0deg); transition: transform 360ms cubic-bezier(.22, .61, .36, 1); }
.mfilter-btn[aria-expanded="true"] svg { transform: rotate(180deg); }
.mfilter-btn[aria-expanded="true"] { color: #fff; border-color: rgba(255, 255, 255, .34); }
.mfilter-btn[aria-pressed="true"], .mfilter-btn.is-set, .mfilter-opt[aria-pressed="true"] {
  color: #fff; font-weight: 650;
  background: linear-gradient(rgba(255, 63, 146, .24), rgba(255, 63, 146, .24)), var(--edge-ground);
  border-color: rgba(255, 106, 168, .72);
}
/* Daylight: the glass of the cards with the page's own dark writing, and
   chosen washed in the brand with the deep pink the «Потрібна модель» button
   above writes in -- grey words on the dark plate were barely there. */
:root[data-theme="day"] :is(.mfilter-btn, .mfilter-opt) {
  /* Light and see-through (owner, 10.10.2026): the quiet surface the cards' words stand on, a fine line round it, no
     grey glass and no shadow -- the row reads as light as the cards under it. */
  color: var(--page-ink-soft);
  background: var(--soft-surface);
  border-color: rgba(20, 22, 27, .14);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
:root[data-theme="day"] .mfilter-btn[aria-expanded="true"] { color: var(--page-ink); border-color: rgba(20, 22, 27, .30); }
:root[data-theme="day"] :is(.mfilter-btn[aria-pressed="true"], .mfilter-btn.is-set, .mfilter-opt[aria-pressed="true"]) {
  color: var(--action-ink); background: var(--action-face); border-color: var(--action-line);
}
/* The rail: folded to nothing beside its button, and opened to its width --
   a grid track from 0fr to 1fr, so it opens to exactly what it holds without
   a number written for it.  The negative margin takes back the row's gap
   while it is folded, so a closed rail leaves no hole. */
.mfilter-rail {
  flex: none; display: grid; grid-template-columns: 0fr; margin-left: -8px;
  transition: grid-template-columns 360ms cubic-bezier(.22, .61, .36, 1),
              margin-left 360ms cubic-bezier(.22, .61, .36, 1);
}
.mfilter-rail.is-open { grid-template-columns: 1fr; margin-left: 0; }
/* The form's «Обрати»: a listing's name is said whole up to a short line, then cut. */
.mreq-pick .mfilter-opt { max-width: 13em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* In the form it is a field like the rest (owner, 10.10.2026): not the catalogue row's 16px under it, and its 4px for
   the glide given back -- the next field starts the form's own 16px below «Обрати». */
.mfilter.mreq-pick { margin-bottom: -4px; }
.mfilter-rail-inner { min-width: 0; overflow: hidden; display: flex; gap: 6px; padding: 2px 0; }
/* Many choices (owner, 10.10.2026: a category list): two rows, column by column, scrolled sideways with the row --
   never wider than the screen, never a sheet. */
.mfilter-rail.is-two.is-open .mfilter-rail-inner { display: grid; grid-auto-flow: column; grid-template-rows: auto auto; }
/* Folded it is one row, so a closed list of many leaves the row its own height. */
.mfilter-rail.is-two .mfilter-opt { justify-content: center; }
/* A hairline between two questions on one row -- the search's city and its sections (owner, 10.10.2026). */
.mfilter-split { flex: none; align-self: center; width: 1px; height: 22px; margin: 0 2px; background: var(--edge-line); }
:root[data-theme="day"] .mfilter-split { background: rgba(20, 22, 27, .16); }
/* «Скинути» at the row's end: quieter than a filter, with the app's own cross. */
.mfilter-drop { color: var(--text-faint); gap: 5px; }
.mfilter-opt {
  height: 32px; padding: 0 12px; font-size: 12.5px; font-weight: 550;
  opacity: 0; transform: translateX(-10px);
  transition: opacity 300ms ease-out, transform 360ms cubic-bezier(.22, .61, .36, 1),
              color 320ms ease-out, background-color 320ms ease-out, border-color 320ms ease-out;
}
.mfilter-rail.is-open .mfilter-opt {
  opacity: 1; transform: none;
  transition-delay: calc(min(var(--i, 0), 6) * 28ms);
}
@media (prefers-reduced-motion: reduce) {
  .mfilter-rail, .mfilter-opt, .mfilter-btn, .mfilter-btn svg { transition: none; }
  .mfilter-opt { transform: none; }
}
/* After the list (owner, 08.10.2026): the requests first, then writing one. */
.mreq-create { width: 100%; margin: 0 0 var(--s3); }
.mreq-sheet .mreq-state {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0;
  font-size: 12.5px; line-height: 1.4; color: var(--text-dim);
}
.mreq-words h3 { margin: 0 0 6px; font-size: 13px; font-weight: 650; color: var(--text-dim); }
/* «Хто шукає модель» is the one author card (AuthorCard, owner 09.10.2026), as every listing's. */
.mreq-author .providercard { margin-bottom: 0; }
.mreq-owner { display: flex; flex-direction: column; gap: var(--s2); }
.mreq-owner .cta { width: 100%; }
.mreq-found { border-color: var(--ok); color: var(--ok); }
.mreq-times { display: flex; gap: var(--s2); }
.mreq-times .glassfield { flex: 1; min-width: 0; }
.mreq-stepper { display: flex; align-items: center; gap: var(--s3); }
.mreq-stepper b { min-width: 2ch; text-align: center; font-size: 18px; }
.mreq-adult { display: flex; align-items: center; gap: var(--s2); font-size: 14px; font-weight: 600; }
.mreq-adult input { flex: none; width: 20px; height: 20px; accent-color: var(--accent-hi); }
.mreq-mine-create { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s3); }
.mreq-mine-create .cta { width: 100%; }
.mreq-mineslot { display: flex; flex-direction: column; gap: 6px; }
/* «Поновити» / «Редагувати й поновити» of one's own cancelled request (owner, 08.10.2026): what happened and what
   renewing does, then the two side by side -- the renewal the full one, the form the outlined one -- one under the
   other where the screen is too narrow for both. */
.mreq-renew { display: flex; flex-direction: column; gap: var(--s2); }
.mreq-renew-note { margin: 0; font-size: 12.5px; line-height: 1.4; color: var(--text-dim); }
.mreq-renew-acts { display: flex; flex-wrap: wrap; gap: var(--s2); }
/* A size down from the screen's big actions (a card's own, not the page's): one line each, side by side where both
   fit and the second on a row of its own where they do not. */
.mreq-renew-acts .cta {
  flex: 1 1 auto; width: auto; min-width: 0; padding: 11px 14px; font-size: 14px; white-space: nowrap;
}
.mreq-renew .formerror, .mreq-renewed { margin: 0; }
.fieldproblem { color: var(--accent-hi); }

/* ---------- the author's own page (owner, 12.10.2026) ------------------- */
/* The owner's panel over the public page: one line and one button, in the page's own card. */
/* Outlined in the accent like .minenotice: it is about what the owner is looking at, on either ground. */
.authormine { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); margin-bottom: var(--s3); padding: 10px 12px; border-radius: 16px; border: 1px solid rgba(255, 63, 146, .45); background: rgba(255, 255, 255, .04); }
.authormine-text { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-dim); }
.authormine .cta { width: auto; padding: 8px 12px; font-size: 13px; flex: none; }
/* Each country's cards together, named where a group holds more than one. */
.authorgroup-country { margin: 6px 0 4px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.authorgroup-part + .authorgroup-part { margin-top: var(--s2); }
.review-country { margin-left: 6px; font-size: 12px; color: var(--text-dim); white-space: nowrap; }

/* ---------- the professional profile (owner, 12.10.2026) ---------------- */
.authormine-actions { display: inline-flex; gap: 6px; flex: none; }
.authormine-pending { margin: -4px 0 var(--s3); font-size: 12px; color: var(--text-dim); }
.authorcard-tags { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 var(--s4) 10px; }
.authorcard-tags span { font-size: 12px; padding: 4px 9px; border-radius: 999px; border: 1px solid var(--line); color: var(--text-dim); }
.authorcard-about { margin: 0; padding: 0 var(--s4) 12px; font-size: 13.5px; line-height: 1.45; white-space: pre-line; }
.authorfilter { margin-bottom: var(--s3); }
.placerow { display: flex; flex-direction: column; gap: 8px; padding: 10px; margin-bottom: 8px; border: 1px solid var(--line); border-radius: 14px; }
.form .chip.contactpublic { align-self: flex-start; }
.editprofile-photo { padding: 6px 0 0; }
.publicpreview-list { margin: 6px 0 0; padding: 0 0 0 18px; font-size: 12.5px; line-height: 1.5; color: var(--text-dim); }
.publicpreview-list b { color: var(--text); font-weight: 600; }
.profilestatus.is-applied, .profilestatus.is-saved { border-color: rgba(60, 200, 120, .55); }
.profilestatus.is-failed, .profilestatus.is-rejected { border-color: rgba(255, 90, 90, .6); }

/* ---------- invitations to review from one's own page (owner, 12.10.2026) ---- */
.authorinvite { display: flex; flex-direction: column; gap: 8px; margin-bottom: var(--s3); padding: 10px 12px; border-radius: 16px; border: 1px solid var(--line); }
.authorinvite p { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-dim); }
.authorinvite .cta { padding: 9px 12px; font-size: 13.5px; }
.invite-label { margin: var(--s3) 0 6px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.invite-ended { font-weight: 400; color: var(--text-faint); }
.invite-rules { margin: var(--s3) 0; padding: 0 0 0 18px; font-size: 12.5px; line-height: 1.5; color: var(--text-dim); }
.invite-made { display: flex; flex-direction: column; gap: 8px; }
.invite-link { margin: 0; font-size: 12px; color: var(--text-dim); overflow-wrap: anywhere; }
.invite-recent { margin-top: var(--s3); }
.invite-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; border-top: 1px solid var(--line); font-size: 12.5px; }
.invite-state.is-used { color: var(--text-faint); }
.authorreview-invited { margin-bottom: var(--s3); }

/* ---------- keeping in touch (owner, 12.10.2026) ------------------------- */
/* The invitation to important messages: the bottom bar's own pane (see the
   shared `::after` above), soft corners, a small bell in the accent, one
   action that stands out and one that does not.  Inline under a publication
   and in the profile; above the bar -- never over it -- on a screen being
   browsed. */
.keepcard {
  position: relative; isolation: isolate;
  --glass-plate: var(--glass-plate-soft);
  display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; align-items: start;
  margin: var(--s3) 0 0; padding: 14px 16px 14px 14px; border-radius: 20px;
  color: var(--chrome-text); text-align: left;
}
.keepcard.is-dock {
  position: fixed; z-index: 6;
  left: var(--nav-inset); right: var(--nav-inset); margin: 0 auto; max-width: 460px;
  bottom: calc(var(--nav-h) + var(--nav-floor) + var(--nav-lift) + var(--s2));
  box-shadow: var(--pane-lift);
}
/* The bell of the messaging permission (owner, 13.10.2026): the country question's own mark -- a thin accent ring
   with a clear centre and the outline bell inside, never a filled slab.  A subscription's bell is another thing and
   keeps its own look. */
.keepbell {
  width: 34px; height: 34px; border-radius: 12px; flex: none;
  display: grid; place-items: center; color: var(--accent);
  background: transparent; box-shadow: inset 0 0 0 1.5px var(--accent);
}
.keepbell.is-on { color: var(--accent); }
.keepwords { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.keepwords b { font-size: 14.5px; font-weight: 650; line-height: 1.25; }
.keepwords span { font-size: 12.5px; line-height: 1.4; opacity: .78; }
.keepacts { grid-column: 1 / -1; display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.keepgo {
  flex: 1 1 auto; min-height: 42px; padding: 10px 16px; border-radius: var(--r-chip);
  font-size: 14px; font-weight: 650; color: var(--action-ink);
  background: var(--glass-sheen), var(--action-face);
  border: 1px solid var(--action-line); box-shadow: var(--action-glow);
}
.keepgo:disabled { opacity: .7; }
.keepgo.is-small { flex: none; min-height: 32px; padding: 6px 14px; font-size: 13px; }
.keepquiet {
  flex: none; padding: 10px 6px; font-size: 13px; font-weight: 600;
  color: var(--chrome-text); opacity: .72; background: none; border: 0;
}
.keepcard.is-done .keepquiet, .keepcard.is-quiet .keepclose { align-self: center; }
.keepcard.is-done, .keepcard.is-quiet { grid-template-columns: auto 1fr auto; align-items: center; }
.keepclose {
  width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
  color: var(--chrome-text); background: rgba(125, 133, 146, .18);
}
/* The one plain line under a publication when the bot can already write. */
/* One run of text with the bell in it, so a line that wraps keeps its bell beside its first word (seen 15.10.2026:
   as a flex item the bell stood alone at the left edge of a two-line block). */
.keepline {
  display: block; text-align: center; text-wrap: balance;
  margin: var(--s2) 0 0; font-size: 12.5px; line-height: 1.4; color: var(--text-dim);
}
.keepline > svg { display: inline-block; vertical-align: -2px; margin-right: 6px; }
/* The compact line in the profile and on «Мої оголошення». */
.keeprow:not(.is-setting) {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 16px;
  background: var(--soft-surface); border: 1px solid var(--soft-surface-line);
  color: var(--text);
}
/* In the profile it IS a settings control (.themeswitch): the same glass, edge, corners and height as «Додайте на
   головний екран» and the theme switch -- only room for the name over its state, and the action at the end. */
.themeswitch.keeprow.is-setting {
  height: auto; min-height: var(--settings-row); padding: 6px 8px 6px 12px; gap: 10px;
}
.themeswitch.keeprow.is-setting .keepbell { width: 28px; height: 28px; border-radius: 9px; }
.themeswitch.keeprow.is-setting .keepbell svg { color: var(--accent); }
.themeswitch.keeprow.is-setting .keeprow-label { font-size: 13px; font-weight: 650; line-height: 1.2; min-width: 0; }
.themeswitch.keeprow.is-setting .keeprow-label i { margin-top: 2px; }
.themeswitch.keeprow.is-setting .keepgo.is-small { min-height: 32px; padding: 6px 12px; font-size: 12.5px; }
.themeswitch.keeprow.is-setting:active { transform: none; }
.keeprow .keepbell { width: 28px; height: 28px; border-radius: 10px; }
/* The ways into the account on the site (WaysIn, E2b): the same settings rows, each way with its state or «Підключити». */
.waysin { display: flex; flex-direction: column; gap: var(--s2); }
.waysin > .as-label { margin: var(--s2) 0 0; }
.themeswitch.waysin-row { min-height: var(--settings-row); height: auto; padding: 6px 8px 6px 12px; gap: 10px; }
.themeswitch.waysin-row:active { transform: none; }
.waysin-row svg { color: var(--accent); flex: none; }
.waysin-name { flex: 1 1 auto; min-width: 0; }
.waysin-state { display: inline-flex; align-items: center; gap: 4px; padding-right: 4px; font-style: normal;
  font-weight: 500; font-size: 12px; color: var(--text-dim); }
.waysin-state svg { color: var(--text-dim); }
.themeswitch.waysin-row .keepgo.is-small { min-height: 32px; padding: 6px 12px; font-size: 12.5px; }
.keeprow-label { flex: 1 1 auto; display: flex; flex-direction: column; font-size: 13.5px; font-weight: 600; }
.keeprow-label i { font-style: normal; font-weight: 500; font-size: 12px; color: var(--text-dim); }
.keeprow.is-line { margin: 0 0 var(--s3); }
.keeprow.is-line > span:not(.keepbell) { flex: 1 1 auto; font-size: 12.5px; line-height: 1.35; color: var(--text-dim); }
/* Under a publication: a breath above the screen's own «Готово», so the two never read as one block. */
.empty .keepcard { width: 100%; margin: var(--s3) 0 var(--s4); }
.empty .keepline { margin-bottom: var(--s3); }
/* «Мої оголошення» under «Готово» on a new listing's last screen: the quieter second way on. */
.empty .cta + .cta { margin-top: var(--s2); }

/* While the invitation is asking, the screen's own «Готово» steps back to the quiet plate (.cta-ghost's): one strong
   action at a time, and still a plain, full-width way to finish without saying yes.  Once answered («увімкнено»,
   «добре»), «Готово» is the strong action again. */
.empty .keepcard:not(.is-done):not(.is-quiet) + .cta {
  /* The page's own quiet surface (light by day, dark by night) and the page's ink on it -- the card token was a
     dark plate in daylight, which put dark words on dark (seen at 320 px, 12.10.2026). */
  background: var(--soft-surface); border: 1px solid var(--soft-surface-line); color: var(--text);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.keepline.is-bell { text-align: left; margin: 0 0 var(--s2); }
.bellsheet .keeprow.is-line { margin: var(--s2) 0; }

/* A toast that is itself the action: the whole card answers a press (its ✕ excepted). */
.toast.is-pressable { cursor: pointer; }
.toast.is-pressable:active { filter: brightness(1.04); }
/* The mark as a thin accent ring with a clear centre -- a hint, not a slab of colour. */
.toast.is-outline .toast-mark {
  background: transparent; color: var(--accent);
  box-shadow: inset 0 0 0 1.5px var(--accent);
}

/* Inside the bell's sheet, which is dark glass in either theme: the line takes the sheet's own light ink on a faint
   pane of its own, never the page's light surface (light words on light, seen 12.10.2026). */
.bellsheet .keeprow.is-line {
  background: rgba(255, 255, 255, .07); border-color: rgba(255, 255, 255, .16); color: inherit;
}
.bellsheet .keeprow.is-line > span:not(.keepbell) { color: inherit; opacity: .86; }
.bellsheet .keepline.is-bell { color: inherit; opacity: .8; }

/* The country question's own button (owner, 13.10.2026): compact, the accent's face, under the words -- never
   beside them, where on a 320 px phone it would squeeze the question into three lines. */
.toast-cta {
  align-self: flex-start; margin-top: 7px;
  display: inline-flex; align-items: center; gap: 3px;
  min-height: 28px; padding: 0 10px 0 13px; border-radius: var(--r-chip);
  font-size: 12.5px; font-weight: 650; letter-spacing: .01em; color: #fff;
  background: var(--neon-face); border: 0;
}
.toast-cta:active { filter: brightness(.94); }
.toast-cta svg { opacity: .9; }
/* «ОК» on the «on» card: the same compact plate as the card's small action, nothing more. */
.keepok {
  flex: none; align-self: center; min-width: 52px; min-height: 32px; padding: 6px 14px;
  border-radius: var(--r-chip); font-size: 13px; font-weight: 650; color: var(--action-ink);
  /* The face without the panels' 1px lit top edge: on a pill this small it read as a white bar across the top
     (owner, 14.10.2026). */
  background: var(--glass-face), var(--action-face); border: 1px solid var(--action-line);
}

/* The site (web version): no Telegram back button to stand in for ours, so a listing without a picture keeps
   ours inside the page's gutters rather than on the screen's edge; and the way into Telegram is a button. */
.app:not([data-native-back="true"]) .hero-empty .detailbar { padding-left: var(--page-side); padding-right: var(--page-side); }
.guest-contact .chatnote { margin: 0 0 var(--s3); }
.guest-contact .cta { text-decoration: none; }
.guest-note { margin: 0 0 var(--s3); }
/* Signing in on the site (web version, E2). */
.signin { display: grid; gap: var(--s2); }
.signin .cta { text-decoration: none; position: relative; }
.signin-mark { font-style: normal; font-size: 11px; font-weight: 700; letter-spacing: .04em; opacity: .8;
  padding: 2px 8px; border-radius: 999px; background: rgba(255, 255, 255, .16); }
.appbar .guest-signin-open { align-self: flex-start; flex: none; }
.guest-signin .chatnote { margin: 0 0 var(--s3); }
.guest-signin .is-warning { color: var(--accent); }

/* ---------- where a listing is seen (owner, 14.10.2026) ------------------ */
/* The keeping-in-touch card's own material: everything already published in
   the app's green, the country's group in a quiet outline until its author
   joins.  Chips that wrap, so four of them fit a narrow phone. */
.reachcard { margin: 0 0 var(--s3); }
.reachplaces { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 2px; opacity: 1 !important; }
.reachplace {
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 999px;
  font-style: normal; font-size: 12px; font-weight: 600; line-height: 1.3;
  color: var(--text-dim); border: 1px dashed color-mix(in oklab, currentColor 45%, transparent);
}
.reachplace.is-on {
  color: var(--ok); border: 1px solid color-mix(in oklab, var(--ok) 34%, transparent);
  background: color-mix(in oklab, var(--ok) 12%, transparent);
}
.reachcard .keepbell { color: var(--ok); box-shadow: inset 0 0 0 1.5px var(--ok); }
/* The bar's doors that need a person, on the site (owner 14.10.2026): why, then the way in. */
.guest-gate .mark, .guest-face { width: 56px; height: 56px; border-radius: 18px; display: grid; place-items: center;
  background: var(--card); border: 1px solid var(--line); color: var(--text-faint); }
.guest-gate .mark { margin: var(--s4) 0 var(--s4); }
.guest-face { width: 84px; height: 84px; border-radius: 50%; }
.guest-gate .chatnote { margin: 0 0 var(--s3); }
.guest-gate .guest-soon { color: var(--text-dim); }

/* ---- «Вставити готовий пост» (components/PastePost.tsx, lib/autofill.ts) ----
   One card per question on the check screen, the same quiet card everywhere it
   appears, and a small mark under a field's name saying how it was filled. */
.pastecard {
  display: flex; flex-direction: column; gap: 12px;
  margin: 0 0 var(--s3); padding: 14px; border-radius: 18px;
  /* The screen's own glass, as every block inside a screen (`.panel`): it was the
     dark pane, which on the daylight page read as black cards with dark writing. */
  background: var(--glass-1); border: 1px solid var(--edge-line);
  box-shadow: inset 0 1px 0 var(--edge-hi);
  color: var(--text); font-size: 14px; line-height: 1.4;
}
.pastecard > b { font-size: 15px; font-weight: 650; }
/* The card is the page's own light glass in daylight, so it writes in the page's dark ink.  Said here by name, at a
   weight the shared "glass is dark, so its writing is light" list (tokens.css) cannot override: with the card on
   that list as well, every word on the check screen turned white on pale lilac (owner, 14.10.2026). */
:root[data-theme="day"] .pastecard {
  --text: var(--page-ink);
  --text-soft: var(--page-ink-soft);
  --text-dim: var(--page-ink-dim);
  --text-faint: var(--page-ink-faint);
}
/* The card's answers are small pills in one line, not the screen's big button (owner, 14.10.2026: «акуратнішими,
   по висоті вужчими»): the one big button on this screen is the one that goes on to the form. */
.pasteactions { display: flex; flex-wrap: wrap; gap: 8px; }
.pasteactions .cta {
  width: auto; flex: none; min-height: 36px; padding: 7px 16px;
  font-size: 13.5px; font-weight: 600; white-space: nowrap;
  box-shadow: none;
}
.pasterows { display: flex; flex-direction: column; gap: 12px; margin: 0; }
.pasterows > div { display: grid; grid-template-columns: 74px 1fr; gap: 10px; align-items: baseline; }
.pasterows dt { font-size: 12px; color: var(--text-dim); }
.pasterows dd { margin: 0; display: flex; flex-direction: column; gap: 4px; min-width: 0; overflow-wrap: anywhere; }
.pasterows dd > span { font-weight: 600; }
.pasterows dd > small { font-size: 12px; color: var(--text-faint); }
/* What was read, one line each, as it goes into the form: the way, the value, how it was read. */
.pastecontacts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.pastecontacts li { display: grid; grid-template-columns: 16px 1fr; column-gap: 8px; align-items: center; }
.pastecontacts li > svg { color: var(--text-dim); }
.pastecontacts li > span { font-weight: 600; overflow-wrap: anywhere; }
.pastecontacts li > small {
  grid-column: 2; display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-faint);
}
.pastecontacts li > small svg { color: var(--ok); }
.pastecontacts li.is-left > span { font-weight: 450; color: var(--text-dim); }
.pastequestions p { margin: 0; }
.pastequestions ul { margin: 6px 0 0; padding-left: 18px; color: var(--text-dim); }
.pastequestions li + li { margin-top: 4px; }
.pastewait {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: var(--s8) var(--s4); color: var(--text-dim); font-size: 14px;
}
.pastewait svg { color: var(--accent); animation: pastepulse 1.4s ease-in-out infinite; }
@keyframes pastepulse { 50% { opacity: .35; transform: scale(.9); } }
@media (prefers-reduced-motion: reduce) { .pastewait svg { animation: none; } }
/* The paste box.  Empty, it is one centred card that says what it takes and
   offers the two ways in side by side, with the line to type into under them;
   with words in it, it is the post's own text box, the paste button a small
   mark in its corner (owner, 14.10.2026: the floating pill and the left-hand
   placeholder read as two things that did not line up). */
.glassfield.tall.pastebox { position: relative; padding-right: 48px; }
.glassfield.tall.pastebox textarea { min-height: 220px; }
.glassfield.tall.pastebox.is-empty {
  flex-direction: column; align-items: stretch; gap: 14px;
  padding: 22px 16px 14px; text-align: center;
}
.glassfield.tall.pastebox.is-empty textarea {
  min-height: 0; padding: 12px 0 0; text-align: center;
  border-top: 1px solid var(--glass-line);
}
.pastezone { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.pastezone > b { font-size: 15.5px; font-weight: 700; line-height: 1.35; color: var(--text); max-width: 30ch; }
.pastezone-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; width: 100%; }
.pastezone-actions button {
  min-height: 46px; padding: 10px 8px; border-radius: 14px;
  font-size: 13.5px; font-weight: 700; color: var(--text);
  background: var(--accent-soft); border: 1px solid rgba(255, 63, 146, .35);
}
.pastezone-actions button:disabled { opacity: .5; }
.pastezone-kinds {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px;
  margin: 0; padding: 0; list-style: none;
}
.pastezone-kinds li {
  padding: 3px 9px; border-radius: 999px; font-size: 11.5px;
  color: var(--text-dim); border: 1px solid var(--glass-line);
}
.pastein {
  position: absolute; top: 10px; right: 10px;
  width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: 11px; font-size: 16px;
  background: var(--accent-soft); border: 1px solid rgba(255, 63, 146, .35);
}
.pastein:disabled { opacity: .5; }
.pastein.pastephoto { top: 52px; }
/* Reading the post: the spark turns inside a ring of the brand's pink and blue,
   a sheen crosses the button, and a pictures row is swept by a scan line.
   None of it moves for somebody who asked for less motion; the words still
   change. */
.publish.is-reading { position: relative; overflow: hidden; }
.publish.is-reading:disabled { filter: none; }
.publish.is-reading::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 63, 146, .22) 42%, rgba(255, 255, 255, .22) 50%, rgba(79, 139, 255, .2) 58%, transparent 80%);
  transform: translateX(-100%); animation: pastesheen 1.8s ease-in-out infinite;
}
.pastespark { position: relative; display: grid; place-items: center; width: 30px; height: 30px; flex: none; }
.publish.is-reading .pastespark::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(from 0deg, var(--accent), var(--ring-blue), var(--star), var(--accent));
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), black calc(100% - 2px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), black calc(100% - 2px));
  animation: pastering 1.1s linear infinite;
}
.publish.is-reading .pastespark svg { animation: pastepulse 1.4s ease-in-out infinite; }
.publish.is-reading b { animation: pastestep .35s ease-out; }
.pastescan .shot.filled::after {
  content: ""; position: absolute; left: 0; right: 0; height: 26%; pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(79, 139, 255, .45) 70%, rgba(255, 255, 255, .9) 100%);
  animation: pastescan 1.6s ease-in-out infinite alternate;
}
/* The same line sweeps the post's text while it is read, pictures or none (owner, 15.10.2026). */
.glassfield.pastebox.is-scanning { overflow: hidden; }
.glassfield.pastebox.is-scanning::after {
  content: ""; position: absolute; left: 0; right: 0; height: 18%; pointer-events: none; z-index: 1;
  background: linear-gradient(180deg, transparent, rgba(79, 139, 255, .32) 70%, rgba(255, 255, 255, .8) 100%);
  animation: pastescan 2s ease-in-out infinite alternate;
}
@keyframes pastering { to { transform: rotate(360deg); } }
@keyframes pastesheen { to { transform: translateX(100%); } }
@keyframes pastescan { from { top: -26%; } to { top: 100%; } }
@keyframes pastestep { from { opacity: 0; transform: translateY(4px); } }
@media (prefers-reduced-motion: reduce) {
  .publish.is-reading::after, .publish.is-reading .pastespark::before, .publish.is-reading .pastespark svg,
  .publish.is-reading b, .pastescan .shot.filled::after { animation: none; }
  .glassfield.pastebox.is-scanning::after { display: none; }
  .publish.is-reading::after { display: none; }
}
.fieldnote.pasteprivacy { font-size: 11.5px; text-align: center; margin-top: 2px; }
.fieldnote.pastehint { color: var(--text); }
.linkbtn.pasteforget { align-self: center; margin-top: 4px; color: var(--text-dim); }
.pasteentry {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 12px 14px; border-radius: 14px; font-size: 13.5px; font-weight: 600;
  color: var(--text); background: var(--accent-soft); border: 1px solid rgba(255, 63, 146, .35);
}
.pasteentry span { flex: 1; }
.pasteentry svg { flex: none; color: var(--accent); }
.importmark {
  display: flex; align-items: center; gap: 5px; margin: -2px 0 2px;
  font-size: 11.5px; line-height: 1.35; color: var(--text-faint);
}
.importmark svg { flex: none; }
.importmark.from_text svg, .importmark.chosen svg { color: var(--ok); }
.importmark.suggested { color: var(--text-dim); }
.importmark.check, .importmark.missing { color: var(--field-note); }
/* A form's one-answer question as the filter row's button and rail (RailPick: a contact's kind, the city).  Its own
   mark stays upright -- only the chevron turns, drawn heavier so the button reads as one (owner, 14.10.2026) -- and
   the field's own gap stands for the catalogue row's margin.  Unanswered it is the question, in the quiet glass. */
.mfilter.railpick { margin-bottom: 0; }
.railpick .mfilter-btn svg:not(:last-child),
.railpick .mfilter-btn[aria-expanded="true"] svg:not(:last-child) { transform: none; opacity: 1; }
.railpick .mfilter-btn svg:last-child { stroke-width: 2.6; opacity: .9; }
.railpick .mfilter-btn.is-ask { color: var(--text); font-weight: 650; }
.railpick .mfilter-label { max-width: 14em; }
.railpick .mfilter-drop svg { opacity: .8; }
.railpick .mfilter-opt { gap: 6px; }
/* The same chip as every other answer in a form (owner, 15.10.2026: «Оберіть місто» and «Спосіб зв'язку» stood out
   grey): the form's blue glass and white words unanswered, its pink once answered.  `:root` so daylight's filter-row
   colours, which tie with these, give way to them. */
:root .railpick :is(.mfilter-btn, .mfilter-opt) {
  color: var(--text); background: var(--glass-1); border-color: var(--glass-line);
}
:root .railpick .mfilter-btn { height: auto; min-height: 36px; padding: 8.5px 15px; font-size: 13.5px; }
:root .railpick :is(.mfilter-btn.is-set, .mfilter-btn[aria-pressed="true"], .mfilter-opt[aria-pressed="true"]) {
  color: #fff; background: rgba(255, 63, 146, .16); border-color: rgba(255, 63, 146, .7);
}
/* A post with several listings: the ones already published, ticked and out of the way; the way on to the next. */
.pastecard .chip.is-done { display: inline-flex; align-items: center; gap: 5px; opacity: .55; }
.cta.pastenext { display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 10px; }

/* Screenshots sent only to fill the form: small, quiet, each one tap from the listing's photos. */
.pasteshots { margin-top: 10px; }
.pasteshots-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.pasteshot { display: flex; flex-direction: column; align-items: center; gap: 6px; width: 84px; }
.pasteshot-img { width: 84px; height: 84px; border-radius: 12px; object-fit: cover; opacity: .7; border: 1px solid var(--glass-line); }
.pasteshot-add { display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 999px; font-size: 11.5px;
  color: var(--text); background: var(--glass-1); border: 1px solid var(--glass-line); }
.pasteshot-add:disabled { opacity: .45; }

/* Voice on a filled form (lib/voice.ts, owner 15.10.2026): the round mic at the right edge, and the pop over it that
   takes a change said or typed.  The paste screen has no voice of its own (owner: nobody dictates a whole post). */
.voicebtn.is-on {
  color: var(--action-ink); background: linear-gradient(135deg, var(--accent), var(--ring-blue)); border-color: transparent;
  animation: voicering 1.4s ease-out infinite;
}
@keyframes voicering {
  0% { box-shadow: 0 0 0 0 rgba(255, 63, 146, .45); }
  100% { box-shadow: 0 0 0 12px rgba(255, 63, 146, 0); }
}
.voicelive {
  display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 10px 12px;
  border-radius: 14px; background: var(--accent-soft); border: 1px solid rgba(255, 63, 146, .35);
  font-size: 14px; color: var(--text);
}
.voicelive b { flex: 1; font-weight: 700; font-variant-numeric: tabular-nums; }
.voicelive svg { color: var(--accent); animation: pastepulse 1.4s ease-in-out infinite; }
.voicelive button {
  padding: 7px 14px; border-radius: 10px; font-weight: 700; font-size: 13.5px;
  color: var(--action-ink); background: var(--action-face); border: 1px solid var(--action-line);
}
.voicedot {
  width: 10px; height: 10px; border-radius: 50%; flex: none; background: var(--accent);
  animation: pastepulse 1s ease-in-out infinite;
}
/* The round mic on a filled form (owner 15.10.2026): it rides the right edge at mid-height whatever the scroll, clear
   of the bar and the contact row at the foot; it goes while the keyboard is up.  Tapped, a sheet takes the change. */
.voicefab {
  position: fixed; z-index: 7; right: 12px; top: 50%; transform: translateY(-50%);
  width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center;
  color: var(--action-ink); background: linear-gradient(135deg, var(--accent), var(--ring-blue));
  border: 1px solid var(--glass-line); box-shadow: var(--pane-lift);
}
.voicefab:active { transform: translateY(-50%) scale(.94); }
.voicefab.is-on { animation: voicering 1.4s ease-out infinite; }
:root[data-typing] .voicefab:not(.is-open) { display: none; }
/* What the mic heard, over the mic and joined to it by a point (owner 15.10.2026): one tool, not a sheet.  Nothing
   is dimmed or held under it -- the form scrolls and takes taps while a change is spoken -- and it is the bar's
   glass and ink, so it reads in both themes. */
.voicepop {
  position: fixed; z-index: 8; right: 12px; display: flex; flex-direction: column;
  bottom: calc(50% + 34px);
  width: min(340px, calc(100vw - 24px));
  max-height: calc(50% - 34px - var(--safe-t) - var(--s3));
  border-radius: 20px; isolation: isolate;
  --glass-plate: var(--glass-plate-soft);
  box-shadow: var(--pane-lift); color: var(--chrome-text);
  transform-origin: calc(100% - 26px) 100%;
  animation: voicepop .18s cubic-bezier(.2, .9, .3, 1) both;
}
/* Thicker than the bar's plate: the pop sits over the form's chips and words, and the words in it must win.  Said
   past the engine rule that thins every plate on Blink. */
:root[data-theme] .voicepop.voicepop { --glass-plate: rgba(12, 14, 19, .62); }
:root[data-theme="day"] .voicepop.voicepop { --glass-plate: var(--soft-surface); }
.voicepop > section { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 14px; border-radius: inherit; }
/* The keyboard up: the pop goes to the top of the screen, under Telegram's buttons, where the keyboard cannot reach
   it; the point would point at nothing there. */
:root[data-typing] .voicepop {
  bottom: auto; top: calc(max(var(--safe-t), var(--s4)) + 52px);
  max-height: calc(45vh - var(--safe-t));
}
:root[data-typing] .voicepop::before { display: none; }
/* The point: a square of the same glass turned on its corner, under the pop and over the mic's middle. */
.voicepop::before {
  content: ""; position: absolute; z-index: -1; right: 19px; bottom: -7px; width: 14px; height: 14px;
  transform: rotate(45deg); border-radius: 0 0 4px 0;
  background: var(--glass-plate);
  backdrop-filter: var(--glass-frost);
  -webkit-backdrop-filter: var(--glass-frost);
  box-shadow: inset -1px -1px 0 var(--glass-ring);
}
@keyframes voicepop {
  from { opacity: 0; transform: translateY(8px) scale(.94); }
  to { opacity: 1; transform: none; }
}
.voicepop-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.voicepop-head b { font-size: 15px; font-weight: 700; color: var(--chrome-text); }
.voicepop-close {
  width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; flex: none;
  color: var(--chrome-text); background: transparent; border: 1px solid var(--glass-line); opacity: .8;
}
.voicepop .voicelive { margin-top: 10px; }
/* 16px and not less: iOS zooms the whole page into a field written smaller, and the pop went off the screen. */
.voicepop-text {
  display: block; width: 100%; margin-top: 10px; padding: 9px 11px; resize: none; min-height: 68px;
  border-radius: 12px; font: inherit; font-size: 16px; line-height: 1.4;
  color: var(--chrome-text); background: transparent; border: 1px solid var(--glass-line);
}
.voicepop-live {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 12px;
  margin-top: 12px; padding: 12px; border-radius: 16px;
  background: var(--accent-soft); border: 1px solid var(--action-line);
}
.voicepop-live b { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--chrome-text); }
.voicepop-live .voiceapply {
  min-height: 40px; padding: 0 16px; border-radius: 12px; font-weight: 700; font-size: 14px;
  color: #fff; background: var(--neon-face); border: 1px solid transparent;
}
.voicepop-pulse {
  width: 48px; height: 48px; border-radius: 50%; display: grid; place-items: center;
  color: #fff; background: var(--neon-face);
  animation: voicering 1.1s ease-out infinite;
}
.voicepop-before {
  margin: 10px 0 0; max-height: 72px; overflow: hidden; white-space: pre-line;
  font-size: 13.5px; line-height: 1.4; color: var(--chrome-text); opacity: .6;
}
.voicepop .voicesaid { color: var(--chrome-text); }
.voicepop-actions { display: flex; gap: 8px; margin-top: 10px; }
.voicepop-actions button {
  flex: 1; min-height: 42px; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border-radius: 12px; font-weight: 700; font-size: 14px; white-space: nowrap; padding: 0 10px;
}
.voicepop-actions .voicebtn { color: var(--chrome-text); background: transparent; border: 1px solid var(--glass-line); }
.voicepop-actions .voiceapply { color: #fff; background: var(--neon-face); border: 1px solid transparent; }
.voicepop-actions button:disabled { opacity: .5; }
.voicepop-hint { margin: 10px 0 0; font-size: 13px; line-height: 1.4; color: var(--chrome-text); opacity: .75; }
.voicetoast {
  position: fixed; z-index: 7; left: var(--s3); right: var(--s3); margin: 0 auto; max-width: 460px;
  bottom: calc(var(--nav-h) + var(--nav-floor) + var(--nav-lift) + var(--s2));
  display: flex; align-items: center; gap: 10px; padding: 10px 14px 10px 10px; border-radius: 18px;
  --glass-plate: var(--glass-plate-soft);
  isolation: isolate; box-shadow: var(--pane-lift); color: var(--chrome-text);
  animation: panel-rise .18s cubic-bezier(.2, .9, .3, 1) both;
}
.voicetoast p { margin: 0; flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; line-height: 1.35; color: var(--chrome-text); }
.voicechanged { animation: voicechanged 2.6s ease-out; border-radius: 12px; }
@keyframes voicechanged {
  0%, 40% { box-shadow: 0 0 0 2px var(--accent); background: var(--accent-soft); }
  100% { box-shadow: 0 0 0 2px transparent; background: transparent; }
}
.voicesaid { margin: 10px 0 0; font-size: 13.5px; line-height: 1.4; color: var(--text); }
.voicesaid.note { color: var(--text-dim); }
@media (prefers-reduced-motion: reduce) {
  .voicebtn.is-on, .voicedot, .voicelive svg,
  .voicefab.is-on, .voicechanged, .voicetoast, .voicepop, .voicepop-pulse { animation: none; }
}
/* ---- the admin panel ----
   One way in, seven sections, and the same card everywhere inside them.

   It borrows the app's tokens and adds four of its own: the words for a state.
   Neutral is a section with nothing to do in it; waiting is somebody's thing
   sitting unanswered; open is a complaint, which is somebody already unhappy;
   settled is the state worth seeing at a glance because it means the queue is
   empty.  Nothing else is coloured.  An admin screen where every row carries a
   tint is an admin screen where the one row that matters is invisible.

   Night is the app's own ground and is left exactly as it stands; daylight
   only deepens the three inks that have to be read as words on a bright page. */
:root {
  --admin-wait-ink: var(--star);
  --admin-wait-face: rgba(246, 195, 68, .13);
  --admin-wait-line: rgba(246, 195, 68, .30);
  --admin-open-ink: var(--accent-hi);
  --admin-open-face: var(--accent-soft);
  --admin-open-line: var(--action-line);
  --admin-clear-ink: var(--ok);
}
:root[data-theme="day"] {
  /* The lit yellow is a glow on a white page; this one has to be read. */
  --admin-wait-ink: #8a6100;
  --admin-wait-face: rgba(246, 195, 68, .20);
  --admin-wait-line: rgba(138, 97, 0, .28);
  --admin-open-ink: var(--action-ink);
}

.adminpanel { display: flex; flex-direction: column; gap: var(--s4); padding: var(--s4) 0 40px; }
.adminpanel h1 { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: -.01em; }
.adminpanel-lede { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--text-dim); }
.adminheading {
  margin: var(--s4) 0 0; font-size: 12px; font-weight: 650;
  letter-spacing: .07em; text-transform: uppercase; color: var(--text-faint);
}

/* One section of the panel: what it is, what it is for, and how much of it is
   waiting.  A count only where something really is waiting -- a row of zeroes
   is a row that teaches the eye to skip the number. */
.adminsections { display: flex; flex-direction: column; gap: 8px; }
.adminsection {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 13px; border-radius: 13px; text-align: left; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminsection-mark { flex: none; color: var(--text-dim); }
.adminsection-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.adminsection-text b { font-size: 14.5px; font-weight: 650; }
.adminsection-text span {
  font-size: 12.5px; line-height: 1.35; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.adminsection.is-waiting { border-color: var(--admin-wait-line); }
.adminsection.is-open { border-color: var(--admin-open-line); }

.admincount {
  flex: none; min-width: 24px; padding: 2px 8px; border-radius: 999px;
  font-size: 12.5px; font-weight: 650; text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim); background: var(--glass-2); border: 1px solid transparent;
}
.admincount.is-waiting {
  color: var(--admin-wait-ink); background: var(--admin-wait-face);
  border-color: var(--admin-wait-line);
}
.admincount.is-open {
  color: var(--admin-open-ink); background: var(--admin-open-face);
  border-color: var(--admin-open-line);
}

/* The filters above a queue.  They scroll sideways rather than wrap: nine
   kinds of thing wrapped onto three lines pushes the queue itself off the
   screen, and the queue is what the reader came for. */
.admintabs {
  display: flex; gap: 7px; overflow-x: auto; padding-bottom: 2px;
  scrollbar-width: none; -ms-overflow-style: none;
}
.admintabs::-webkit-scrollbar { display: none; }
.admintab {
  flex: none; display: flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 999px; font-size: 13px; white-space: nowrap;
  color: var(--text-dim); background: var(--glass-1); border: 1px solid var(--glass-line);
}
.admintab.on { color: var(--text); border-color: var(--action-line); background: var(--glass-2); }
.admintab em {
  font-style: normal; font-size: 11.5px; font-variant-numeric: tabular-nums;
  color: var(--admin-wait-ink);
}

/* One waiting thing, or one complaint.  Two lines: what it is, and everything
   else in one quiet line under it -- a card that repeats every field as a
   labelled row is a card nobody reads twice. */
.adminlist { display: flex; flex-direction: column; gap: 8px; }
.adminitem {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 12px 13px; border-radius: 13px; text-align: left; color: var(--text);
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminitem-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.adminitem-title {
  font-size: 14px; font-weight: 600; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.adminitem-facts {
  display: flex; flex-wrap: wrap; gap: 4px 8px;
  font-size: 12px; line-height: 1.35; color: var(--text-faint);
}
.adminitem-facts b { font-weight: 600; color: var(--text-dim); }
.adminitem-note {
  flex: none; font-size: 11.5px; padding: 2px 7px; border-radius: 6px;
  color: var(--admin-wait-ink); background: var(--admin-wait-face);
}

/* New, or a change to something that was there: a mark above a waiting
   thing's title in the list, and the head of one opened.  The server decides
   which, from what the database knows -- never the words. */
.adminitem-mark, .adminop {
  align-self: flex-start; padding: 1px 7px; border-radius: 6px;
  font-size: 11px; font-weight: 650; line-height: 1.5; letter-spacing: .02em;
  color: var(--text-dim); background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminitem-mark.is-new, .adminop.is-new { color: var(--admin-clear-ink); }
.adminitem-mark.is-edit, .adminop.is-edit { color: var(--admin-wait-ink); background: var(--admin-wait-face); }
.adminop { display: inline-block; margin: 0 0 6px; font-size: 12px; }

/* What a change changed, first, field by field: «Було» over «Стало». */
.adminchanges {
  display: flex; flex-direction: column; gap: 9px; padding: 12px 13px;
  border-radius: 13px; background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminchanges-head, .adminread-head { display: block; font-size: 12.5px; font-weight: 700; color: var(--text); }
.adminchanges-note { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }
.adminchange { display: flex; flex-direction: column; gap: 2px; }
.adminchange-label { font-size: 13px; font-weight: 600; color: var(--text); }
.adminchange-was, .adminchange-now { font-size: 13px; line-height: 1.4; word-break: break-word; }
.adminchange-was { color: var(--text-faint); }
.adminchange-now { color: var(--text); }
.adminchange i { font-style: normal; color: var(--text-faint); }

/* What a screen says while it has nothing to show.  Four states and never a
   blank: a page that shows nothing is a page that has not said whether it is
   still counting, empty, or broken. */
.adminstate {
  display: flex; flex-direction: column; align-items: center; gap: 9px;
  padding: 30px 18px; text-align: center;
  border-radius: 13px; background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminstate p { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--text-dim); }
.adminstate.is-clear p { color: var(--admin-clear-ink); }
.adminstate button {
  padding: 9px 16px; border-radius: 10px; font-size: 13.5px;
  color: var(--text); background: var(--glass-2); border: 1px solid var(--glass-line);
}

/* A row of plain facts, for the read-only sections. */
.adminfacts { display: flex; flex-direction: column; gap: 6px; }
.adminfact {
  display: flex; align-items: baseline; gap: 10px;
  padding: 11px 13px; border-radius: 12px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminfact-label { flex: none; font-size: 12.5px; color: var(--text-faint); }
.adminfact-value {
  flex: 1; min-width: 0; font-size: 13.5px; text-align: right; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.adminfact.is-wrong { border-color: var(--admin-open-line); }
.adminfact.is-wrong .adminfact-value { color: var(--admin-open-ink); }

/* One waiting thing, in full.  A field per row, and the two things that are
   not fields at all standing apart from them: what the model thought, and what
   pressing approve will also do. */
.adminread { display: flex; flex-direction: column; gap: 5px; }
.adminread-row {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 13px; border-radius: 11px;
  background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminread-row span { font-size: 11.5px; color: var(--text-faint); }
.adminread-row b { font-size: 13.5px; font-weight: 500; line-height: 1.4; word-break: break-word; }

.adminverdict {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 11px 13px; border-radius: 12px;
  background: var(--admin-wait-face); border: 1px solid var(--admin-wait-line);
  font-size: 13px; line-height: 1.4; color: var(--admin-wait-ink);
}
/* The model's reading is of older words than the listing now carries: the
   same frame in the colour of the one action it changes, the takedown. */
.adminverdict.is-stale {
  background: var(--admin-open-face); border-color: var(--admin-open-line); color: var(--admin-open-ink);
}
/* The way to the thing itself from the model's published list. */
.adminopen { flex: none; }

/* What approving will also do.  Not a warning and not decoration: a plain list
   of the things that happen the moment the button is pressed, because they
   used to happen silently. */
.adminalso {
  padding: 11px 13px; border-radius: 12px;
  background: var(--glass-2); border: 1px solid var(--glass-line);
}
.adminalso b { display: block; font-size: 12.5px; font-weight: 650; color: var(--text); }
.adminalso ul { margin: 5px 0 0; padding-left: 17px; }
.adminalso li { font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }

.adminacts { display: flex; flex-direction: column; gap: 8px; margin-top: var(--s2); }
.adminacts-pair { display: flex; gap: 8px; }
.adminact {
  flex: 1; padding: 13px; border-radius: 12px; font-size: 14px; font-weight: 600;
  color: var(--text); background: var(--glass-1); border: 1px solid var(--glass-line);
}
.adminact.is-yes { border-color: var(--admin-clear-ink); color: var(--admin-clear-ink); }
.adminact.is-no { border-color: var(--admin-open-line); color: var(--admin-open-ink); }
.adminact:disabled { opacity: .55; }
.adminsaid { margin: 0; font-size: 12.5px; line-height: 1.4; color: var(--admin-open-ink); }

/* ---- what a moderator said, above the form that fixes it ----
   Not in the admin panel, but written here beside the other status voices so
   the one palette holds all of them.  Small: a refusal is not a catastrophe,
   and a full red screen reads as one.  What somebody needs is the sentence
   they were given and the form underneath it, in that order. */
.refused {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 14px; margin-bottom: var(--s4);
  border-radius: var(--r-card);
  background: var(--admin-open-face); border: 1px solid var(--admin-open-line);
}
.refused-mark { flex: none; color: var(--admin-open-ink); margin-top: 1px; }
.refused-said { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.refused-said b { font-size: 14px; font-weight: 650; color: var(--admin-open-ink); }
.refused-said i {
  font-style: normal; font-size: 13.5px; line-height: 1.45; color: var(--text);
  word-break: break-word;
}
.refused-said i span { color: var(--text-faint); }
.refused-said > span { font-size: 12.5px; line-height: 1.4; color: var(--text-dim); }

/* ---- and the other thing a form can be told ----
   Not "you wrote something wrong" but "somebody wrote this while you were
   typing".  Same shape as the refusal above it, because it stands in the same
   place and means the same kind of thing -- the form is not going through yet
   -- with the one way out of it as a button rather than a sentence. */
.refused.is-stale .refused-mark { color: var(--text-dim); }
.refused.is-stale .refused-said b { color: var(--text); }
.refused-again {
  align-self: flex-start; margin-top: 6px;
  padding: 6px 14px; border-radius: 999px;
  border: 1px solid var(--admin-open-line); background: var(--surface);
  /* `--ink` and not `--text`, for the reason spelled out at `.eventoff-keep`
     in app.css: a dark surface the daylight switch leaves alone has to bring
     the light writing with it, or the button comes out blank at sunrise. */
  color: var(--ink); font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.refused-again:active { opacity: .7; }
/* ---------- Premium UI & Motion (owner, 11.10.2026) ----------------------
   Everything that moves under the `data-motion` switch (lib/motion.ts), and
   nothing that was already moving: with the attribute `off`, or missing, not a
   rule in this file applies and the app is as it was.  The numbers are the
   motion tokens in tokens.css; `prefers-reduced-motion` rewrites them there
   to a fade of --m-fade or nothing, so no rule here needs a second copy.

   Two things are not motion and hold in every mode: a panel's backdrop does
   not scroll the page under a finger, and a panel's own scrolling stops at
   its edges rather than pulling the page.                                   */

.sheet-backdrop, .popover-backdrop { touch-action: none; }
.sheet > section, .popover-panel { overscroll-behavior: contain; }
/* While the sheet is on its way out nothing on it, or behind it, takes a press. */
.sheet[data-phase="closing"], .popover[data-phase="closing"] { pointer-events: none; }

/* ---- the press: the answer comes first, the screen follows ---- */
:root[data-motion]:not([data-motion="off"]) :is(.cta, .iconbtn, .heart, .fav-fab, .bellbtn, .tab, .filter-option,
    .sheet-option, .themeswitch, .linkbtn.framed, .mfilter-opt, .motionswitch button) {
  transition: transform var(--m-press) var(--e-out);
}
:root[data-motion]:not([data-motion="off"]) :is(.cta, .iconbtn, .heart, .fav-fab, .bellbtn, .tab, .filter-option,
    .sheet-option, .themeswitch, .linkbtn.framed, .mfilter-opt, .motionswitch button):active {
  transform: scale(var(--press-scale));
}
/* A large card sinks less: .99, and only the card. */
:root[data-motion]:not([data-motion="off"]) .gcard { transition: transform var(--m-press) var(--e-out), box-shadow var(--m-press) var(--e-out); }
:root[data-motion]:not([data-motion="off"]) .gcard:active { transform: scale(var(--press-scale-card)); box-shadow: var(--pane-press); }
:root[data-motion]:not([data-motion="off"]) .tile { transition: transform var(--m-press) var(--e-out); }
:root[data-motion]:not([data-motion="off"]) .tile:active { transform: scale(var(--press-scale-card)); }

/* ---- the screen change: the new screen rises a little into place ----
   `translate`, not `transform`: the swipe-back gesture writes the page's
   transform by hand (lib/swipeBack.ts) and the two must not fight.  Set by
   App.tsx for the one frame after the route changes and taken off when the
   animation ends. */
:root[data-motion]:not([data-motion="off"]) .page[data-enter="in"] { animation: page-in var(--m-content) var(--e-in) both; }
:root[data-motion]:not([data-motion="off"]) .page[data-enter="back"] { animation: page-back var(--m-state) var(--e-out) both; }
@keyframes page-in { from { opacity: 0; translate: 0 var(--m-rise); } to { opacity: 1; translate: 0 0; } }
@keyframes page-back { from { opacity: 0; } to { opacity: 1; } }

/* ---- a list arriving: the cards fade in together, no stagger ---- */
:root[data-motion]:not([data-motion="off"]) .cardgrid > .gcard { animation: fade-in var(--m-content) var(--e-in) both; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ---- the sheet: in from below over --m-sheet-in; out is run by Sheet.tsx ---- */
:root[data-motion]:not([data-motion="off"]) .sheet[data-phase] > section {
  animation: sheet-in var(--m-sheet-in) var(--e-in) both;
}
:root[data-motion]:not([data-motion="off"]) .sheet[data-phase] .sheet-backdrop {
  animation: fade-in var(--m-state) var(--e-in) both;
}
@keyframes sheet-in { from { opacity: 0; translate: 0 16px; } to { opacity: 1; translate: 0 0; } }
/* The popover keeps its own way in, on the tokens. */
:root[data-motion]:not([data-motion="off"]) .popover-panel {
  transition: opacity var(--m-state) var(--e-in), transform var(--m-state) var(--e-in);
}
:root[data-motion]:not([data-motion="off"]) .popover-notch { transition: opacity var(--m-state) var(--e-in); }

/* ---- the heart: a small beat on being pressed on, and only then ---- */
:root[data-motion]:not([data-motion="off"]) :is(.heart, .fav-fab, .hero-save)[data-pop] svg {
  animation: heart-pop var(--m-state) var(--e-in);
}
@keyframes heart-pop { 50% { transform: scale(var(--pop-scale)); } }

/* ---- a saved card going: it fades where it stands, and only then does the list close up ----
   Nothing on it takes a press while it goes, so the card that moves into its
   place is never pressed by the finger that meant this one. */
:root[data-motion]:not([data-motion="off"]) .gcard[data-leaving] {
  animation: card-out var(--m-state) var(--e-out) both;
  pointer-events: none;
}
.gcard[data-leaving] { pointer-events: none; }
@keyframes card-out { to { opacity: 0; scale: var(--leave-scale); } }

/* ---- the cover arriving on the listing ---- */
:root[data-motion]:not([data-motion="off"]) .hero-photo { animation: fade-in var(--m-content) var(--e-in) both; }

/* ---- Signature: the flying copy of a card's photograph (lib/sharedPhoto.ts) ----
   Over the page, under every sheet and toast (z 20+), and never pressed. */
.flight-layer { position: fixed; inset: 0; z-index: 15; pointer-events: none; overflow: hidden; }
/* A frame that crops, and the picture inside it at its cover size: no background of its own, so nothing grey
   can show at an edge (owner's iPhone check, 12.10.2026). */
.flight-photo { position: fixed; overflow: hidden; background: none; }
.flight-picture { position: absolute; display: block; max-width: none; }
/* Nothing grey under a flying photograph: the listing's placeholders for its cover are not painted while one is
   up -- the skeleton of a listing still loading (its first block stands where the cover lands, its title bar
   inside the cover's height) and the cover's own skeleton.  The grey square the owner saw was the first of them. */
:root[data-flight] .detail-cover-wait { visibility: hidden; }
:root[data-flight] [data-flight-target] .skeleton { background: none; animation: none; }

/* ---- the switch, in the profile's settings (administrators only) ---- */
.motionswitch { display: flex; gap: 6px; margin-top: 10px; }
.motionswitch button {
  flex: 1; min-height: 40px; padding: 0 10px; border-radius: 999px;
  border: 1px solid var(--line-strong); background: none; color: var(--text-dim);
  font: inherit; font-size: 13px; font-weight: 600;
}
.motionswitch button[aria-checked="true"] { color: var(--accent-hi); border-color: var(--accent); }
/* ---------- the site on a computer (web plan, stage E3) ----------------
   Only the site: `.app[data-site]` is drawn by a browser outside Telegram
   (GuestApp, and App signed in on the site), so the Mini App -- Telegram
   Desktop's window included -- never reads a line below.  The phone's layout
   is the starting point and nothing here changes what a phone sees: the same
   bar becomes the header, the same column becomes a wider page, the same
   filters stand at the left.  1024px is `WIDE` in lib/wide.ts. */

/* The logo at the start of the header: never in the bar along the bottom. */
.bottomnav .bottomnav-brand { display: none; }
.bottomnav .bottomnav-hello { display: none; }
.bottomnav .bottomnav-wide { display: none; }

@media (min-width: 1024px) {
  .app[data-site] {
    --site-width: 1200px;
    --site-head: 64px;
    --page-side: 32px;
  }

  /* -- the header: the same five, along the top, with the logo before them */
  .app[data-site] .bottomnav {
    top: 0; bottom: auto; left: 0; right: 0; margin: 0; max-width: none;
    height: var(--site-head); border-radius: 0;
    display: flex; align-items: center; gap: 4px;
    padding: 0 max(var(--page-side), calc((100% - var(--site-width)) / 2 + var(--page-side)));
  }
  /* What the app says for itself stands under the header, at its right -- never over the logo and the menu. */
  .app[data-site] .toast {
    top: calc(var(--site-head) + var(--s3)); left: auto;
    right: max(var(--page-side), calc((100% - var(--site-width)) / 2 + var(--page-side)));
    width: min(420px, calc(100% - 2 * var(--page-side)));
  }
  /* A hairline along its foot rather than a capsule's ring. */
  .app[data-site] .bottomnav::after {
    box-shadow: inset 0 -1px 0 var(--glass-ring);
  }
  .app[data-site] .bottomnav .bottomnav-brand {
    display: flex; align-items: center; order: -1; margin-right: auto; padding: 0;
    color: #fff;
  }
  :root[data-theme="day"] .app[data-site] .bottomnav-brand { color: var(--page-ink); }
  /* «Привіт, <ім'я>» right after the logo (owner, 15.10.2026): it takes the room between the logo and «Головна»,
     and a long name makes the words smaller -- down to a floor, then it trails off -- never the header wider. */
  .app[data-site] .bottomnav:has(.bottomnav-hello) .bottomnav-brand { margin-right: 0; }
  .app[data-site] .bottomnav .bottomnav-hello {
    display: block; flex: 1 1 0; min-width: 0; margin: 0 24px 0 22px; container-type: inline-size;
  }
  .app[data-site] .bottomnav-hello b {
    display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    font-family: var(--font-brand); font-weight: 700; letter-spacing: -.03em; line-height: 1.2; color: #fff;
    font-size: clamp(12px, calc(100cqi / var(--hello-len) * 1.32), 22px);
  }
  :root[data-theme="day"] .app[data-site] .bottomnav-hello b { color: var(--page-ink); }
  .app[data-site] .bottomnav-brand .brandlogo { display: block; height: auto; }
  .app[data-site] .bottomnav button:not(.bottomnav-brand) {
    flex-direction: row; gap: 8px; height: 40px; padding: 0 16px; border-radius: 999px;
    font-size: 14px; font-weight: 600; text-shadow: none;
    transition: background-color .2s ease, color .2s ease;
  }
  .app[data-site] .bottomnav button:not(.bottomnav-brand):hover { background: var(--edge-ground); }
  .app[data-site] .bottomnav button i { height: auto; }
  /* The profile's door says who is signed in, or «Увійти» (BottomNav profileWide); the home screen's own chip and
     face, a second door to the same place, step aside. */
  .app[data-site] .bottomnav .bottomnav-word { display: none; }
  .app[data-site] .bottomnav .bottomnav-wide {
    display: inline; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .app[data-site] .home-avatar, .app[data-site] .guest-signin-open { display: none; }
  /* «Додати» last, as the one lit button a header carries, not a ball rising out of it. */
  .app[data-site] .bottomnav .add { order: 1; margin-left: 8px; color: #fff; background: var(--neon-face); box-shadow: var(--neon-accent); }
  .app[data-site] .bottomnav .add:hover { background: var(--neon-face); filter: brightness(1.08); }
  .app[data-site] .bottomnav .add i {
    width: auto; height: auto; margin: 0; top: 0; background: none; box-shadow: none; border-radius: 0;
  }
  .app[data-site] .bottomnav .add[aria-current="page"] span { color: #fff; }

  /* -- the page: wider, under the header, nothing to clear at its foot */
  .app[data-site] .page,
  .app[data-site] .page[data-cta] {
    max-width: var(--site-width);
    padding: calc(var(--site-head) + 28px) var(--page-side) 72px;
  }
  /* What is read rather than browsed keeps a reading width. */
  .app[data-site] .page:not(:is([data-screen="home"], [data-screen="search"], [data-screen="catalog"],
      [data-screen="services"], [data-screen="new"], [data-screen="favorites"], [data-screen="mine"],
      [data-screen="events"], [data-screen="info"], [data-screen="info-list"], [data-screen="community"],
      [data-screen="community-chats"], [data-screen="people"], [data-screen="models"], [data-screen="author"],
      [data-screen="author-me"], [data-screen="listing"], [data-screen="service"], [data-screen="event"],
      [data-screen="model"])) {
    max-width: 800px;
  }
  /* The search that folds into a corner as a phone scrolls stays where it is: the header holds that corner. */
  .app[data-site] .catalog-search-gap { display: none; }
  .app[data-site] .searchbar.afloat,
  .app[data-site] .searchbar.afloat.open {
    position: relative; top: auto; left: auto; right: auto; z-index: auto;
    width: auto; min-height: 0; padding: 3px 3px 3px 16px; margin: 0 0 var(--s3);
    justify-content: flex-start; overflow: visible; transition: none;
  }
  .app[data-site] .searchbar.afloat > :not(.searchgo) { opacity: 1; pointer-events: auto; transition: none; }

  /* -- home: the logo is in the header, the six sections in two rows of three */
  .app[data-site] .page[data-screen="home"] .appbar :is(.brand, .greeting) { display: none; }
  /* Where the greeting stood, the three settings a visitor changes, in one row: country, language, light. */
  .app[data-site] .page[data-screen="home"] .appbar .headtext {
    display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 28px;
  }
  .app[data-site] .page[data-screen="home"] .appbar .headtext > * { flex: none; width: auto; margin: 0; }
  .app[data-site] .page[data-screen="home"] .appbar .home-daylight {
    display: inline-flex; gap: 2px; padding: 3px; border-radius: 999px;
    background: color-mix(in srgb, var(--text) 7%, transparent);
  }
  .app[data-site] .page[data-screen="home"] .appbar .home-daylight button {
    display: grid; place-items: center; width: 32px; height: 26px; border-radius: 999px; color: var(--text-dim);
    transition: background-color .2s ease, color .2s ease;
  }
  .app[data-site] .page[data-screen="home"] .appbar .home-daylight button:hover { color: var(--text); }
  .app[data-site] .page[data-screen="home"] .appbar .home-daylight button[aria-pressed="true"] {
    background: color-mix(in srgb, var(--text) 16%, transparent); color: var(--text);
  }
  .app[data-site] .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
  .app[data-site] .tile { min-height: 150px; }

  /* -- rails: a row of cards to see at once, not a strip to drag sideways with a mouse */
  .app[data-site] .rail {
    display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 16px;
    overflow: visible; margin: 0; padding: 2px 0;
  }
  .app[data-site] .rail > * { min-width: 0; }
  .app[data-site] .rail > :nth-child(n + 11) { display: none; }
  .app[data-site] .page:is([data-screen="listing"], [data-screen="service"]) .rail {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .app[data-site] .page:is([data-screen="listing"], [data-screen="service"]) .rail > :nth-child(n + 7) { display: none; }

  /* -- every grid of cards: as many as the width holds */
  .app[data-site] .cardgrid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 18px 16px; }

  /* -- filters at the left of a list, the cards beside them.  The column runs
     from under the search to the foot; spanning the last, flexible row, it
     never stretches a row of cards (CSS Grid, items spanning a flexible track). */
  .app[data-site] .page:has(> .mfilter) {
    display: grid; grid-template-columns: 236px minmax(0, 1fr); column-gap: 32px;
    grid-template-rows: repeat(14, auto) 1fr; align-content: start;
  }
  .app[data-site] .page:has(> .mfilter) > * { grid-column: 2; }
  .app[data-site] .page:has(> .mfilter) > :is(.screenbar, .searchbar, .catalog-search-gap) { grid-column: 1 / -1; }
  .app[data-site] .page:has(> .mfilter) > .mfilter {
    grid-column: 1; grid-row: 3 / -1; align-self: start;
    position: sticky; top: calc(var(--site-head) + 20px);
    flex-direction: column; align-items: stretch; gap: 8px;
    margin: 0; padding: 0; overflow: visible;
  }
  .app[data-site] .page:has(> .mfilter) .mfilter-btn { justify-content: space-between; width: 100%; }
  .app[data-site] .page:has(> .mfilter) .mfilter-label { max-width: none; }
  .app[data-site] .page:has(> .mfilter) .mfilter-split { width: 100%; height: 1px; margin: 2px 0; }
  /* A group opens downwards, its choices wrapping in the column's width. */
  .app[data-site] .page:has(> .mfilter) .mfilter-rail {
    grid-template-columns: 1fr; grid-template-rows: 0fr; margin: -8px 0 0;
    transition: grid-template-rows 360ms cubic-bezier(.22, .61, .36, 1), margin-top 360ms cubic-bezier(.22, .61, .36, 1);
  }
  .app[data-site] .page:has(> .mfilter) .mfilter-rail.is-open { grid-template-rows: 1fr; margin-top: 0; }
  .app[data-site] .page:has(> .mfilter) .mfilter-rail-inner,
  .app[data-site] .page:has(> .mfilter) .mfilter-rail.is-two.is-open .mfilter-rail-inner {
    display: flex; flex-wrap: wrap; min-height: 0; padding: 2px 0 4px;
  }
  .app[data-site] .page:has(> .mfilter) .mfilter-opt { transform: translateY(-6px); }
  .app[data-site] .page:has(> .mfilter) .mfilter-rail.is-open .mfilter-opt { transform: none; }

  /* -- a listing, a service, an event, a request for models: the picture and what it says at the left, what
     decides it at the right, held in view while the left is read -- every detail the same way (owner, 15.10.2026) */
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"], .eventsheet.has-side) {
    display: grid; grid-template-columns: minmax(0, 1fr) 380px; column-gap: 40px;
    grid-template-rows: repeat(14, auto) 1fr; align-content: start;
  }
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"], .eventsheet.has-side) > * {
    grid-column: 1;
  }
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"], .eventsheet.has-side)
    > .detail-side {
    grid-column: 2; grid-row: 1 / -1; align-self: start;
    position: sticky; top: calc(var(--site-head) + 20px);
  }
  /* An event's and a request's sheet was a column with a gap: as two columns, the gap is each block's own margin,
     so the empty rows the side column spans add none. */
  .app[data-site] .eventsheet.has-side { row-gap: 0; }
  .app[data-site] .eventsheet.has-side > :not(.detail-side) { margin-bottom: var(--s4); }
  .app[data-site] .eventsheet > .detail-side { display: flex; flex-direction: column; gap: var(--s4); }
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"]) .hero {
    margin: 0 0 var(--s5, 20px); border-radius: var(--r-card); overflow: hidden;
  }
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"]) .hero-empty {
    margin-bottom: var(--s4); border-radius: 0; overflow: visible;
  }
  /* With no picture the back arrow stands in the page's own margin, level with what is beside it. */
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"]) .hero-empty .detailbar { padding: 0; }
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"]) .hero-photo { aspect-ratio: 4 / 3; }
  .app[data-site] :is(.page[data-screen="listing"], .page[data-screen="service"]) .detail-side > .detail-top {
    margin-top: 0;
  }

  /* -- what floats over a phone's page: kept to the page, not to the window */
  /* Sharing and keeping at the foot of the window on the left, under the reading column: never over the column that
     decides at the right, whatever it holds (owner, 15.10.2026). */
  .app[data-site] .floating-cta {
    bottom: 24px; right: auto; transform: none; width: auto; max-width: 380px; padding: 0;
    left: max(var(--page-side), calc((100vw - var(--site-width)) / 2 + var(--page-side)));
  }
  /* The ways to reach the author, as on a phone: the circles at the right edge, in the margin beside the page
     (owner, 15.10.2026).  Where the window leaves no margin wide enough, the page gives way to them. */
  .app[data-site] .contactrail { right: max(var(--s3), calc((100vw - var(--site-width)) / 2 - 56px)); }
  .app[data-site] .page:has(.contactrail:not(.is-labelled)) {
    padding-right: max(var(--page-side), calc(80px - (100vw - var(--site-width)) / 2));
  }
  /* The page itself keeps the circles clear here: the line under a phone's page need not step aside a second time. */
  .app[data-site]:has(.contactrail:not(.is-labelled)) .trustbar { padding-right: 0; }
  .app[data-site] .mreq-sheet.has-rail > .eventtitle,
  .app[data-site] .mreq-sheet.has-rail .mreq-words { padding-right: 0; }
  .app[data-site] .mreq-sheet.has-rail .eventfacts .eventfact { padding-right: var(--s4); }
  .app[data-site] .mreq-similar.has-rail .mreq-card { padding-right: var(--s3); }
}
