/* Mini App shell and screens.
 *
 * EVERY value here is a token from tokens.css — the machine projection of the brand book, linked in
 * from the Api project so there is one file for every surface. A value that is not there is added to
 * the brand book first and never picked at the call site (CLAUDE.md).
 *
 * There is no dark/light switch: the app has ONE theme, dark, and Telegram's themeParams are
 * deliberately not read — someone else's palette over ours gives unpredictable contrast against a
 * mandatory 4.5:1 (brandbook ch. 12 §9.1).
 */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* The DOCUMENT is what scrolls — neither .app-shell nor .app-content sets an overflow — so the gutter
   is reserved on the root element, which hands it to the viewport (on body it would do nothing).
   Without it, the classic scrollbar a long screen (Keys) brings in takes its width out of the
   viewport, and the fixed tab bar, sized to the viewport, shifts its columns every time a person
   switches between a short screen and a long one (Telegram Desktop, stand run 29.09.2026). With it,
   every screen is laid out at the same width whether it scrolls or not. Overlay scrollbars (mobile,
   macOS) take no width and get no gutter, so nothing appears there. */
html {
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    background: var(--color-bg-primary);
    color: var(--color-text-primary);
    font-family: var(--font-sans);
    font-size: var(--font-body);
    line-height: var(--leading-body);
    -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------- shell --- */

.app-shell {
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
}

.app-content {
    flex: 1 1 auto;
    /* 16px screen margins, safe areas on top of that (ch. 12 §9.3). The bottom pad clears the tab
       bar: content that scrolls under it can never be read or tapped. */
    padding:
        calc(var(--space-16) + env(safe-area-inset-top))
        calc(var(--space-16) + env(safe-area-inset-right))
        calc(var(--space-96) + env(safe-area-inset-bottom))
        calc(var(--space-16) + env(safe-area-inset-left));
}

.tabbar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: var(--layer-sticky);
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    /* 56px (brandbook ch. 4 §4.15 — the same figure the kit's own .tabbar carries in components.css),
       safe area on top. NOT min-height on the item below: a min lets padding inflate the row past 56,
       which is what shipped once the icon was added on top of the padding this rule used to carry
       (fix round 2, M2). */
    height: 56px;
    background: var(--color-bg-elevated);
    border-top: 1px solid var(--color-border-subtle);
    padding-bottom: env(safe-area-inset-bottom);
}

.tabbar__item {
    /* Matches the kit's own .tabbar__item (components.css) exactly — well above the 44px touch-target
       floor (ch. 12 §9.3), not merely meeting it. A CSS Grid item stretches to the row's height by
       default, so this is a floor for content, not a second height rule fighting the one above. */
    min-height: 56px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* Space/4 — the scale's own value for "between an icon and text inside a control" (ch. 4 §2.1). */
    gap: var(--space-4);
    /* Inactive is Text/Muted, active is Action/Primary — brandbook ch. 4 §4.15, no third colour. */
    color: var(--color-text-muted);
    font-size: var(--font-caption);
    text-decoration: none;
    transition: color var(--motion-fast) var(--ease-standard), background var(--motion-fast) var(--ease-standard);
}

.tabbar__item:hover {
    /* Same rule as the kit's own tabbar (components.css): hover washes the background, the colour
       stays Text/Muted — colour alone marks the ACTIVE tab, never hover (fix round 2, M2). */
    background: var(--color-surface-hover);
}

.tabbar__item.active {
    color: var(--color-action-primary);
}

/* 24px navigation icon (ch. 4 §2.8). Stroke colour is currentColor, so .tabbar__item's own colour
   rule above (Text/Muted default, Action/Primary active) paints it — no separate colour here. */
.tabbar__icon {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
}

/* ------------------------------------------------------------ typography --- */

h1 {
    margin: 0 0 var(--space-16);
    font-size: var(--font-h2);
    line-height: var(--leading-heading);
    font-weight: 600;
}

h2 {
    margin: var(--space-32) 0 var(--space-12);
    font-size: var(--font-h4);
    line-height: var(--leading-subhead);
    font-weight: 600;
}

p {
    margin: 0 0 var(--space-12);
}

.muted {
    color: var(--color-text-secondary);
}

.caption {
    font-size: var(--font-caption);
    color: var(--color-text-muted);
}

/* ----------------------------------------------------------------- card --- */

.card {
    background: var(--color-surface-default);
    border: 1px solid var(--color-border-default);
    border-radius: var(--radius-lg);
    padding: var(--space-16);
    margin-bottom: var(--space-12);
}

.card__title {
    margin: 0 0 var(--space-4);
    font-size: var(--font-h5);
    font-weight: 600;
}

.card__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-12);
    margin-bottom: var(--space-8);
}

/* --------------------------------------------------------------- badges --- */

.badge {
    display: inline-block;
    padding: var(--space-4) var(--space-12);
    border-radius: var(--radius-full);
    font-size: var(--font-caption);
    font-weight: 600;
}

.badge--success {
    background: var(--color-success-soft);
    color: var(--color-success);
}

.badge--warning {
    background: var(--color-warning-soft);
    color: var(--color-warning);
}

.badge--error {
    background: var(--color-error-soft);
    color: var(--color-error);
}

.badge--info {
    background: var(--color-info-soft);
    color: var(--color-info);
}

/* -------------------------------------------------------------- buttons --- */

.btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    padding: var(--space-12) var(--space-24);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: var(--font-body);
    font-weight: 600;
    cursor: pointer;
    transition:
        background-color var(--motion-fast) var(--ease-standard),
        color var(--motion-fast) var(--ease-standard);
}

.btn:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

/* Width is fixed while loading so the button does not resize under the finger mid-tap
   (ch. 12 §6.2, step 1). */
.btn[disabled] {
    background: var(--color-action-disabled);
    color: var(--color-text-disabled);
    cursor: default;
}

.btn--primary {
    background: var(--color-action-primary);
    color: var(--color-text-inverse);
}

.btn--primary:hover:not([disabled]) {
    background: var(--color-action-hover);
}

.btn--primary:active:not([disabled]) {
    background: var(--color-action-pressed);
}

.btn--secondary {
    background: var(--color-surface-default);
    border-color: var(--color-border-control);
    color: var(--color-text-primary);
}

.btn--secondary:hover:not([disabled]) {
    background: var(--color-surface-hover);
}

.btn--block {
    width: 100%;
}

.btn-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
}

/* Payment methods: two per row, equal columns (brandbook ch. 7 §4, open-questions §38). minmax(0, 1fr)
   rather than 1fr: a plain 1fr column cannot shrink below its longest word, and one long label would
   widen its column and shove the neighbour off the card. An odd last button keeps its own cell. */
.pay-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-8);
}

/* Half a phone's width is narrow: the usual 24px side padding would leave a label like «Зарубежная
   карта» a few characters per line. Space/12 keeps the label readable; a label that still does not fit
   wraps inside the button, which grows from its 44px floor rather than truncating the method's name. */
.pay-grid .btn {
    padding-inline: var(--space-12);
    text-align: center;
}

/* 20px — an icon inside an md control (ch. 4 §2.8). Never shrinks when the label wraps. */
.btn__icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* ---------------------------------------------------------- state panel --- */

.state-panel {
    border: 1px solid var(--color-border-default);
    border-radius: var(--radius-lg);
    padding: var(--space-16);
    margin-bottom: var(--space-16);
    background: var(--color-surface-default);
}

.state-panel--error,
.state-panel--offline {
    border-color: var(--color-error);
    background: var(--color-error-soft);
}

.state-panel--sessionended {
    border-color: var(--color-warning);
    background: var(--color-warning-soft);
}

.state-panel__text {
    margin: 0 0 var(--space-12);
}

.state-panel__text:last-child {
    margin-bottom: 0;
}

/* ------------------------------------------------------------- skeleton --- */

/* A skeleton in the geometry of the content to come, never a full-screen spinner
   (ch. 12 §4.2). */
.skeleton {
    background: var(--color-surface-hover);
    border-radius: var(--radius-sm);
    height: 1rem;
    margin-bottom: var(--space-8);
    animation: skeleton-pulse var(--motion-screen) var(--ease-standard) infinite alternate;
}

.skeleton--title {
    height: 1.5rem;
    width: 60%;
}

.skeleton--line {
    width: 100%;
}

.skeleton--short {
    width: 40%;
}

@keyframes skeleton-pulse {
    from { opacity: 1; }
    to { opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
    }

    * {
        transition-duration: var(--motion-instant) !important;
    }
}

/* ------------------------------------------------------------- key card --- */

.key-card__link {
    font-family: var(--font-mono);
    font-size: var(--font-small);
    color: var(--color-text-secondary);
    word-break: break-all;
    margin-bottom: var(--space-12);
    /* Reused inside .card__row (the XRPL invoice address, fix round 1, I4): a flex item's minimum
       width defaults to its content size, which would push a copy button out of view instead of
       wrapping. Both are no-ops for the standalone <p> use in Keys.razor. */
    min-width: 0;
    flex: 1 1 auto;
}

.key-card__qr {
    display: block;
    width: 220px;
    max-width: 100%;
    height: auto;
    margin: var(--space-12) auto 0;
    /* The QR is rendered on a white field because scanners need the quiet zone light — this is the
       one surface in the product that is not on the dark ground, and it is functional. */
    background: #FFFFFF;
    border-radius: var(--radius-sm);
    padding: var(--space-8);
}

/* ---------------------------------------------------------------- forms --- */

.field {
    margin-bottom: var(--space-16);
}

.field__label {
    display: block;
    margin-bottom: var(--space-8);
    font-size: var(--font-small);
    color: var(--color-text-secondary);
}

.field input {
    width: 100%;
    min-height: 44px;
    padding: var(--space-12);
    background: var(--color-bg-secondary);
    border: 1px solid var(--color-border-control);
    border-radius: var(--radius-md);
    color: var(--color-text-primary);
    font-family: inherit;
    font-size: var(--font-body);
}

.field input:focus-visible {
    outline: none;
    border-color: var(--color-border-focus);
    box-shadow: var(--shadow-focus);
}

/* ---------------------------------------------------------------- toast --- */

/* The ONE place a toast is right: copying leaves no state behind (ch. 12 §5.1). */
.toast {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(var(--space-96) + env(safe-area-inset-bottom));
    z-index: var(--layer-toast);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border-default);
    border-radius: var(--radius-full);
    padding: var(--space-8) var(--space-24);
    box-shadow: var(--shadow-popover);
    font-size: var(--font-small);
}

/* ------------------------------------------------------------- заголовок --- */

/* Тот же вид, что у голого h1: класс существует, чтобы экран мог называть свой заголовок явно, а
   не полагаться на то, что он единственный h1 на странице. Значения не дублируются -- правило
   наследует их у h1 выше и добавляет только принадлежность экрану. */
.screen-title {
    margin: 0 0 var(--space-16);
}

/* Пояснение под содержимым: тише основного текста, но не настолько, чтобы его перестали читать --
   .caption для подписей к элементам, .hint для целой мысли под блоком. */
.hint {
    margin: var(--space-12) 0 0;
    font-size: var(--font-small);
    color: var(--color-text-secondary);
}

/* ------------------------------------------------------------------ поле --- */

/* Отдельно от `.field input`: тот селектор работает только внутри обёртки .field, а поле кода
   стоит в своей раскладке. Значения те же -- расхождение вида одного и того же элемента между
   экранами читается как небрежность, а не как замысел. */
.input {
    min-height: 44px;
    padding: var(--space-12);
    background: var(--color-bg-secondary);
    border: 1px solid var(--color-border-control);
    border-radius: var(--radius-md);
    color: var(--color-text-primary);
    font-family: inherit;
    font-size: var(--font-body);
    transition: border-color var(--motion-fast) var(--ease-standard);
}

.input:focus-visible {
    outline: none;
    border-color: var(--color-border-focus);
    box-shadow: var(--shadow-focus);
}

.input[disabled] {
    color: var(--color-text-disabled);
    cursor: not-allowed;
}

/* --------------------------------------------------- разрушающее действие --- */

/* Отзыв пропуска -- единственное место, где кнопка красная. Отдельный вид нужен потому, что
   действие необратимо: вторичная кнопка рядом с ним выглядит равнозначной, а они не равнозначны. */
.btn--danger {
    background: var(--color-error);
    border-color: var(--color-error);
    color: var(--color-text-inverse);
}

.btn--danger:hover:not([disabled]) {
    background: var(--color-error-hover);
    border-color: var(--color-error-hover);
}

.btn--danger:active:not([disabled]) {
    background: var(--color-error-pressed);
    border-color: var(--color-error-pressed);
}

.btn--danger[disabled] {
    background: var(--color-action-disabled);
    border-color: var(--color-action-disabled);
    color: var(--color-text-disabled);
    cursor: not-allowed;
}

/* ---------------------------------------------------------- код привязки --- */

/* ПЕРВЫЙ экран, который человек видит вне Telegram, поэтому он не может выглядеть как черновик.
   Раскладка в одну колонку: поле и кнопка стоят друг под другом, а не в строку, — на узком экране
   строка ужимает поле до нечитаемой ширины ровно там, где надо разобрать шесть цифр. */
.device-code {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    padding: var(--space-16);
    background: var(--color-surface-default);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
}

.device-code__prompt {
    margin: 0;
    color: var(--color-text-primary);
}

.device-code__label {
    font-size: var(--font-small);
    color: var(--color-text-secondary);
}

/* Моноширинный и разреженный: шесть цифр переписывают с другого экрана, и слитный ряд в
   пропорциональной гарнитуре теряется на третьей цифре. Крупнее основного текста по той же
   причине. */
.device-code__input {
    width: 100%;
    font-family: var(--font-mono);
    font-size: var(--font-h4);
    letter-spacing: 0.2em;
    text-align: center;
}

.device-code__hint {
    margin: 0;
    font-size: var(--font-small);
    color: var(--color-text-muted);
}

/* -------------------------------------------------------------- устройства --- */

.device-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
}

.device-list__item {
    padding: var(--space-16);
    background: var(--color-surface-default);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
}

/* Отозванное устройство ОСТАЁТСЯ в списке и остаётся читаемым: строка нужна как след — «этот
   пропуск отозван тогда-то». Приглушение делается цветом текста, а не opacity: opacity гасит и
   границу, и человек читает это как «элемент выключен», а не «доступ отозван». */
.device-list__item--revoked .device-list__name {
    color: var(--color-text-secondary);
}

.device-list__name {
    font-weight: 600;
    color: var(--color-text-primary);
}

.device-list__meta {
    margin-top: var(--space-4);
    font-size: var(--font-small);
    color: var(--color-text-secondary);
}

/* Предупреждение перед отзывом: цена действия называется ДО нажатия, поэтому оно и выделено. */
.device-list__warning {
    margin: var(--space-12) 0;
    padding: var(--space-12);
    background: var(--color-error-soft);
    border-radius: var(--radius-md);
    font-size: var(--font-small);
    color: var(--color-text-primary);
}

/* ----------------------------------------------------------- нижняя панель --- */

/* Подпись вкладки: сама вкладка (.tabbar__item) держит зону нажатия и цвет, подпись отвечает
   только за перенос — длинное слово в узкой колонке иначе вылезает за её границу. */
.tabbar__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
