/* The pieces pages are assembled from: chrome, cards, lists, forms, chart. */

/* ---- app bar and tab bar ------------------------------------------------ */

.appbar {
    position: sticky;
    top: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: calc(var(--safe-top) + 10px) var(--pad) 10px;
    background: var(--plane);
    border-bottom: 1px solid var(--hairline);
}

.appbar__back {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--ghost);
    color: var(--ink);
}

.appbar__titles {
    flex: 1;
    min-width: 0;
}

/* Which budget the dashboard is showing. Sits where the page title would be:
   the highlighted tab below already says which page this is, so the bar can
   spend that room on scope instead. */
.scope {
    position: relative;
}

.scope__summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    padding: 3px 8px 3px 4px;
    border-radius: 999px;
    cursor: pointer;
    list-style: none;
    font-size: 17px;
    font-weight: 650;
    letter-spacing: -0.01em;
}

/* Safari draws its own marker unless this is spelled out. */
.scope__summary::-webkit-details-marker {
    display: none;
}

.scope__summary:hover,
.scope[open] .scope__summary {
    background: var(--ghost);
}

.scope__summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* The same rotated corner a <select> wears, so every dropdown in the app opens
   with the same mark. */
.scope__chevron {
    flex: none;
    width: 6px;
    height: 6px;
    margin-left: 1px;
    border-right: 2px solid var(--ink-muted);
    border-bottom: 2px solid var(--ink-muted);
    border-radius: 1px;
    /* Pulled up a little: a rotated corner reads as sitting lower than it is. */
    transform: translateY(-2px) rotate(45deg);
}

.scope[open] .scope__chevron {
    transform: translateY(1px) rotate(225deg);
}

.scope__panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 1;
    width: max-content;
    min-width: 240px;
    max-width: min(320px, 88vw);
    max-height: 60vh;
    overflow-y: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding: 4px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}

.scope__panel::-webkit-scrollbar {
    display: none;
}

/* Names the group of pots under it — "Plans" — inside the switcher's panel. */
.scope__label {
    margin: 6px 0 2px;
    padding: 4px 10px 0;
    border-top: 1px solid var(--hairline);
    font-size: 11.5px;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.scope__option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 10px;
    border-radius: var(--radius-small);
    color: var(--ink);
    font-size: 14.5px;
    font-weight: 500;
}

.scope__option--current {
    background: var(--ghost);
    font-weight: 650;
}

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

.scope__amount {
    flex: 0 0 auto;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

/*
 * The same switcher parked at the end of the bar rather than standing in for the
 * title — the transaction form keeps its title, because a screen you arrived at
 * to do one thing should say what that thing is.
 */
.scope--aside {
    flex: 0 1 auto;
    min-width: 0;
    /* The title is the longer of the two and has first claim on the room. */
    max-width: 55%;
}

.scope--aside .scope__summary {
    padding: 4px 8px;
    border: 1px solid var(--hairline);
    background: var(--surface);
    font-size: 14px;
    font-weight: 620;
}

/* Opening at the left edge would run the panel off the screen from here. */
.scope--aside .scope__panel {
    left: auto;
    right: 0;
}

/* The mark and the name together, so the summary can be swapped in one go when
   the choice changes. */
.scope__current {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

/* The month a page is scoped to, parked at the end of the app bar. Sticky with
   the bar, so it stays reachable however far the page has scrolled. */
.month-pill {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 999px;
}

.month-pill__step {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 999px;
    color: var(--ink);
    font-size: 18px;
    line-height: 1;
}

.month-pill__label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0 4px;
    font-size: 13.5px;
    font-weight: 620;
    white-space: nowrap;
    color: var(--ink);
}

/* Away from the current month the label becomes the way back to it. The glyph
   carries that, so it does not rest on the colour alone. */
.month-pill__label--reset {
    color: var(--link);
}

.month-pill__reset {
    font-size: 12px;
    line-height: 1;
}

.appbar__title {
    font-size: 17px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.appbar__subtitle {
    margin: 1px 0 0;
    font-size: 12.5px;
    color: var(--ink-muted);
}

.tabbar {
    position: fixed;
    left: 50%;
    bottom: calc(var(--safe-bottom) + var(--tabbar-inset));
    z-index: 20;
    transform: translateX(-50%);
    width: min(calc(100% - 2 * var(--tabbar-inset)), 460px);
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 2px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 999px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.14);
}

.tabbar__item {
    height: calc(var(--tabbar-height) - 12px);
    /* Five labels in the width of four: they must not wrap the bar taller. */
    overflow: hidden;
    white-space: nowrap;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    border-radius: 999px;
    font-size: 10.5px;
    font-weight: 550;
    color: var(--ink-muted);
}

.tabbar__item svg {
    width: 21px;
    height: 21px;
}

/* The active tab is a filled pill, so the current section reads at a glance
   without relying on colour alone. */
.tabbar__item[aria-current="page"] {
    background: var(--ghost);
    color: var(--accent);
}

/* ---- messages ------------------------------------------------------------ */

/*
 * A notice about something that already happened — "Expense deleted." — floats
 * over the page instead of pushing it down, and takes itself away.
 *
 * It sits above the tab bar and ignores pointer events, so it never blocks the
 * button underneath it while it is on screen.
 */
.toasts {
    position: fixed;
    left: var(--pad);
    right: var(--pad);
    bottom: calc(var(--tabbar-height) + var(--tabbar-inset) + var(--safe-bottom) + var(--gap));
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 640px;
    margin: 0 auto;
    pointer-events: none;
}

.toast {
    margin: 0;
    padding: 12px 15px;
    border-radius: var(--radius);
    background: var(--surface);
    border: 1px solid var(--hairline);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
    color: var(--ink);
    font-size: 14px;
    font-weight: 550;
    animation: toast-in 0.26s ease-out, toast-out 0.4s ease-in 4.2s forwards;
}

.toast--success {
    background: color-mix(in srgb, var(--state-on-track) 14%, var(--surface));
    border-color: color-mix(in srgb, var(--state-on-track) 28%, var(--surface));
}

.toast--error {
    background: color-mix(in srgb, var(--state-overspent) 14%, var(--surface));
    border-color: color-mix(in srgb, var(--state-overspent) 28%, var(--surface));
    color: var(--state-overspent);
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(14px);
    }
}

@keyframes toast-out {
    to {
        opacity: 0;
        transform: translateY(8px);
        visibility: hidden;
    }
}

/*
 * The global reduced-motion rule shortens every animation to nothing, which
 * would take the toast away before it could be read. Naming no animation at all
 * leaves it in place instead; it clears on the next page either way.
 */
@media (prefers-reduced-motion: reduce) {
    .toast {
        animation: none;
    }
}

/* An error that belongs to the form it sits in, and must stay put while the
   field it is about is being corrected. */
.flash {
    margin: 0 0 8px;
    padding: 11px 14px;
    border-radius: var(--radius-small);
    font-size: 14px;
    font-weight: 550;
    background: var(--ghost);
    color: var(--ink);
}

.flash--success {
    background: rgba(12, 163, 12, 0.14);
}

.flash--error {
    background: rgba(208, 59, 59, 0.14);
    color: var(--state-overspent);
}

/* ---- cards and sections ------------------------------------------------- */

.card {
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    padding: var(--pad);
}

.card--list {
    padding: 0;
    overflow: hidden;
}

.card--centred {
    text-align: center;
    padding: 26px var(--pad);
}

.empty__heading {
    margin-bottom: 6px;
    font-size: 16px;
}

.card--centred .button {
    margin-top: 14px;
}

/* A heading a control below it links back to, clearing the sticky app bar. */
.section-heading[id] {
    scroll-margin-top: 68px;
}

.section-heading {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--gap);
    margin: 6px 2px -2px;
    color: var(--ink-muted);
}

.section-heading h2 {
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.section-heading a,
.section-heading span {
    font-size: 13px;
    font-weight: 600;
}

/* A note at the foot of a card, ruled off from what it explains. */
.note {
    display: block;
    padding: 12px var(--pad);
    border-top: 1px solid var(--hairline);
}

/*
 * A hint belonging to the field right above it. No rule and no padding of its
 * own: .note draws a line across the whole card, which under a single field
 * reads as a stray border.
 */
.field__hint {
    display: block;
    margin-top: 6px;
    font-size: 12.5px;
}


/* ---- labels ------------------------------------------------------------- */

.stat__label {
    display: block;
    font-size: 12px;
    font-weight: 550;
    color: var(--ink-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- budgets, expenses, breakdowns -------------------------------------- */

/* A budget's emoji, standing in for its colour swatch at the same size. */
.mark {
    flex: 0 0 auto;
    display: inline-block;
    font-size: 15px;
    line-height: 1;
}

.tag .mark {
    font-size: 12px;
}

.swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    flex: 0 0 auto;
    border-radius: 3px;
    background: var(--series-neutral);
}

.series-1 { background: var(--series-1); }
.series-2 { background: var(--series-2); }
.series-3 { background: var(--series-3); }
.series-4 { background: var(--series-4); }
.series-5 { background: var(--series-5); }
.series-6 { background: var(--series-6); }
.series-7 { background: var(--series-7); }
.series-8 { background: var(--series-8); }
.series-neutral { background: var(--series-neutral); }

.budget,
.breakdown-bar {
    display: block;
    padding: 13px var(--pad);
    border-bottom: 1px solid var(--hairline);
    color: var(--ink);
}

.budget:last-child,
.breakdown-bar:last-child {
    border-bottom: 0;
}

.budget__header,
.breakdown-bar__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--gap);
}

.budget__name {
    display: flex;
    align-items: center;
    gap: 7px;
    font-weight: 600;
    font-size: 15px;
    min-width: 0;
}

.budget__amounts {
    font-size: 13px;
    color: var(--ink-soft);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.budget__footer,
.breakdown-bar__footer {
    display: flex;
    justify-content: space-between;
    gap: var(--gap);
    margin-top: 6px;
    font-size: 12px;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

/*
 * A second footer line, for a pot with something more to say about itself: a
 * plan puts the date it is for here, under the figures rather than beside them.
 */
.budget__footer--note {
    margin-top: 3px;
}

.meter {
    display: block;
    height: 7px;
    margin-top: 9px;
    border-radius: 999px;
    background: var(--ghost);
    overflow: hidden;
}

.meter__fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--state-on-track);
}

.meter__fill--nearly-spent { background: var(--state-nearly-spent); }
.meter__fill--overspent { background: var(--state-overspent); }

/* A category bar carries its own colour rather than a budget state,
   so these have to beat the plain .meter__fill background above. */
.meter__fill.series-1 { background: var(--series-1); }
.meter__fill.series-2 { background: var(--series-2); }
.meter__fill.series-3 { background: var(--series-3); }
.meter__fill.series-4 { background: var(--series-4); }
.meter__fill.series-5 { background: var(--series-5); }
.meter__fill.series-6 { background: var(--series-6); }
.meter__fill.series-7 { background: var(--series-7); }
.meter__fill.series-8 { background: var(--series-8); }
.meter__fill.series-neutral { background: var(--series-neutral); }

.expense {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px var(--pad);
    border-bottom: 1px solid var(--hairline);
    color: var(--ink);
    /*
     * Coming back from the form lands on this row by its id. The app bar is
     * sticky, so without room reserved above it the row arrives underneath the
     * bar and looks like it was missed.
     */
    scroll-margin-top: 76px;
}

/*
 * And a moment of colour on the row that was returned to, so it is obvious
 * which one it was. Fades out on its own; :target holds until the next
 * navigation, and a highlight that never left would read as a selection.
 */
.expense:target {
    animation: expense-found 2.4s ease-out;
}

@keyframes expense-found {
    0%, 40% { background: color-mix(in srgb, var(--accent) 14%, transparent); }
    100% { background: transparent; }
}

.expense:last-child {
    border-bottom: 0;
}

/* A sub-category, stepped in under the one it belongs to. */
.expense--child {
    padding-left: calc(var(--pad) + 26px);
}

.expense__body {
    flex: 1;
    min-width: 0;
}

.expense__title {
    display: block;
    font-size: 15px;
    font-weight: 570;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.expense__tags {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin-top: 4px;
}

/* Small, quiet labels: which pot it came from, and what it was for. */
.tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    max-width: 100%;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--ghost);
    font-size: 11.5px;
    font-weight: 550;
    color: var(--ink-soft);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tag .swatch {
    width: 8px;
    height: 8px;
}

.expense__trailing {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
}

.expense__amount {
    font-size: 15px;
    font-weight: 650;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.expense__when {
    font-size: 11.5px;
    color: var(--ink-muted);
    white-space: nowrap;
}

.expense__chevron {
    font-size: 22px;
    line-height: 1;
    color: var(--ink-muted);
}

/* A ranked category: icon, then its name over a bar with its share at the end. */
/*
 * The rule sits on the top of a row rather than the bottom, so suppressing it on
 * a sub-category closes the gap between a parent and its children as well as
 * between the children themselves — the group reads as one block, and the next
 * parent still gets its line.
 */
.rank {
    display: flex;
    color: var(--ink);
    align-items: center;
    gap: 12px;
    padding: 12px var(--pad);
    /*
     * A wider gutter than the card's own padding, so the disclosure mark has
     * clear air on both sides. Every row gets it, foldable or not: give it only
     * to the rows with a mark and their bars alone shorten, and the column of
     * percentages stops lining up.
     */
    padding-left: calc(var(--pad) + 8px);
    border-top: 1px solid var(--hairline);
}

.rank:first-child,
.rank--child {
    border-top: 0;
}

/* A category and its sub-categories: the rule belongs above the whole block. */
.rank-group {
    border-top: 1px solid var(--hairline);
}

.rank-group:first-child,
.rank-group .rank {
    border-top: 0;
}

.rank--parent {
    position: relative;
    cursor: pointer;
    list-style: none;
}

.rank--parent::-webkit-details-marker {
    display: none;
}

.rank--parent:hover {
    background: var(--ghost);
}

.rank--parent:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/*
 * Sits in the row's own left padding, ahead of the disc, and is taken out of the
 * flow entirely: a flex item here would shorten the bar on foldable rows alone
 * and knock the whole column of percentages out of line.
 */
.rank__chevron {
    position: absolute;
    /* Centred in the row's gutter rather than pinned to the card edge. */
    left: 8px;
    top: 50%;
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--ink-muted);
    border-bottom: 2px solid var(--ink-muted);
    border-radius: 1px;
    /* Pulled up a little: a rotated corner reads as sitting lower than it is. */
    transform: translateY(-70%) rotate(45deg);
}

/* Flipped to point at the rows it has revealed. */
.rank-group[open] .rank__chevron {
    transform: translateY(-30%) rotate(225deg);
}

/* A sub-category, stepped in under the parent it counts towards. */
.rank--child {
    padding-left: calc(var(--pad) + 32px);
}

.rank--child .avatar {
    width: 30px;
    height: 30px;
    font-size: 15px;
}

.rank--child .rank__name,
.rank--child .rank__share {
    font-size: 13px;
    font-weight: 500;
}

/* Tighter than a parent row: a child is part of the row above, not a new one. */
.rank--child {
    padding-top: 2px;
}

.rank__body {
    flex: 1;
    min-width: 0;
}

.rank__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.rank__name {
    min-width: 0;
    font-size: 14.5px;
    font-weight: 570;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.rank__share {
    flex: 0 0 auto;
    font-size: 13.5px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

.rank__amount {
    display: block;
    margin-top: 5px;
    font-size: 12px;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

.day-heading {
    display: flex;
    justify-content: space-between;
    padding: 8px var(--pad);
    background: var(--ghost);
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-soft);
}

.breakdown {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px var(--pad);
    border-bottom: 1px solid var(--hairline);
    color: var(--ink);
}

.breakdown:last-child {
    border-bottom: 0;
}

.breakdown__name {
    flex: 1;
    min-width: 0;
    font-size: 15px;
    font-weight: 560;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.breakdown__share {
    min-width: 4.5ch;
    text-align: right;
    font-size: 13px;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

.breakdown__value {
    font-size: 15px;
    font-weight: 620;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.detail {
    display: flex;
    justify-content: space-between;
    gap: var(--gap);
    padding: 13px var(--pad);
    border-bottom: 1px solid var(--hairline);
    font-size: 15px;
}

.detail:last-child {
    border-bottom: 0;
}

.drilldown__total {
    margin: 4px 0 0;
    font-size: 32px;
    font-weight: 700;
    letter-spacing: -0.02em;
}

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

.field {
    display: block;
    margin-bottom: 14px;
}

.field__label {
    display: block;
    margin-bottom: 6px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-soft);
}

.input {
    width: 100%;
    min-height: 46px;
    padding: 10px 13px;
    border: 1px solid var(--hairline);
    border-radius: 12px;
    background: var(--plane);
    color: var(--ink);
}

/*
 * The browser draws its own arrow on a select, in its own place and its own
 * colour. Ours is built from the same tokens as everything else, so it sits
 * where the app's other trailing marks sit and follows the theme.
 */
.select {
    position: relative;
    display: block;
}

.select .input {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 40px;
}

.select::after {
    content: '';
    position: absolute;
    top: 50%;
    right: 16px;
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--ink-muted);
    border-bottom: 2px solid var(--ink-muted);
    border-radius: 1px;
    /* Pulled up a little: a rotated corner reads as sitting lower than it is. */
    transform: translateY(-70%) rotate(45deg);
    pointer-events: none;
}

/* A value that cannot be changed, wearing the shape of the input it replaces. */
.input--static {
    margin: 0;
    color: var(--ink-muted);
}

/* A setting with its own explanation beside it, rather than a bare tickbox. */
.field--switch {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 10px;
    cursor: pointer;
}

.switch__input {
    flex: none;
    width: 20px;
    height: 20px;
    margin: 1px 0 0;
    accent-color: var(--accent);
}

.switch__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.switch__label {
    font-size: 14px;
    font-weight: 600;
}

.input--amount {
    min-height: 64px;
    font-size: 32px;
    font-weight: 700;
    text-align: center;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.choices {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    padding: 2px 0 4px;
    /* Scrolls, but without a bar: this runs inside Telegram on a phone, where
       a bar is chrome nobody asked for. A chip cut off at the edge is the cue
       that there is more. */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.choices::-webkit-scrollbar {
    display: none;
}

/* The second row, offering the children of whichever chip is chosen above. */
.choices--sub {
    margin-top: -4px;
    padding-left: 14px;
}

.choices--wrap {
    flex-wrap: wrap;
    overflow: visible;
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    min-height: 40px;
    padding: 0 14px;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    background: var(--plane);
    color: var(--ink);
    font-size: 14px;
    font-weight: 550;
    white-space: nowrap;
    cursor: pointer;
}

/* An "add one" chip, dashed like the tile that does the same job in the grid. */
.chip--add {
    border-style: dashed;
    color: var(--ink-muted);
    font-weight: 550;
}

/* A currency that is fixed because the budget already has transactions. */
.chip--muted {
    opacity: 0.45;
}

.chip__hint {
    font-size: 12px;
    font-weight: 450;
    color: var(--ink-muted);
}

/* The radio and checkbox themselves are hidden; the label is the control, so
   the selected state is driven entirely by CSS with no script involved. */
.choice__input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.choice__input:checked + .chip {
    background: var(--accent);
    border-color: transparent;
    color: var(--accent-ink);
}

.choice__input:checked + .chip .chip__hint {
    color: inherit;
    opacity: 0.85;
}

/* The first palette slot is the accent colour, so on a selected chip the
   swatch would otherwise disappear into the background. */
.choice__input:checked + .chip .swatch {
    box-shadow: 0 0 0 1.5px var(--accent-ink);
}

.choice__input:focus-visible + .chip {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.segmented {
    display: flex;
    gap: 2px;
    padding: 3px;
    background: var(--ghost);
    border-radius: 11px;
}

.segmented__option {
    flex: 1;
    display: grid;
    place-items: center;
    min-height: 36px;
    border-radius: 9px;
    font-size: 13.5px;
    font-weight: 570;
    color: var(--ink-soft);
}

.segmented__option[aria-current="true"] {
    background: var(--surface);
    color: var(--ink);
}

/* The transaction filters, stacked in one card above the list. */
.filters {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.search {
    display: flex;
    gap: 8px;
}

.search .input {
    flex: 1;
    min-width: 0;
}

.search .button {
    flex: 0 0 auto;
    min-height: 46px;
    padding: 0 14px;
    font-size: 14px;
}

/* The chosen category, marked the way a checked chip is. */
.chip--current {
    background: var(--accent);
    border-color: transparent;
    color: var(--accent-ink);
}

.range-form {
    display: flex;
    gap: 8px;
    margin-top: 10px;
}

.range-form .input {
    min-width: 0;
}

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

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding: 0 18px;
    border: 0;
    border-radius: 12px;
    background: var(--ghost);
    color: var(--ink);
    font-size: 16px;
    font-weight: 620;
    cursor: pointer;
}

.button--primary {
    background: var(--accent);
    color: var(--accent-ink);
}

.button--quiet {
    background: var(--ghost);
    color: var(--ink);
}

.button--danger {
    background: transparent;
    color: var(--danger);
    box-shadow: inset 0 0 0 1px var(--danger);
}

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

.stacked-form {
    display: block;
}

/* ---- fixed cost checklist ----------------------------------------------- */

/*
 * A checklist row carries its actions, which no other rank row does, so it
 * stacks instead of sitting on one line. The actions go under the amount rather
 * than beside it: at phone width a date field and two buttons have nowhere to
 * be on a row that already holds a name and a figure.
 */
.rank--stacked {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
}

.rank--stacked .rank__body {
    width: 100%;
}

.rank--stacked .stacked-form {
    margin: 0;
}

/* The date and its button share a line, the date taking what is left. */
.inline-form {
    display: flex;
    gap: 8px;
    align-items: center;
}

.inline-form .input {
    flex: 1 1 auto;
    min-width: 0;
}

.inline-form .button {
    flex: 0 0 auto;
}

/* ---- sign-in ------------------------------------------------------------ */

.spinner {
    width: 26px;
    height: 26px;
    margin: 0 auto 12px;
    border: 2.5px solid var(--hairline);
    border-top-color: var(--accent);
    border-radius: 999px;
    animation: spin 0.7s linear infinite;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* The day's two headline figures. They share a row, and either can stand alone
   — a past month has an average but no "today". */
.headlines {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    /* Every row takes the tallest card's height, so a card carrying a badge
       does not make its whole row taller than the one below. */
    grid-auto-rows: 1fr;
    gap: var(--gap);
}

/* An odd card stretches rather than sitting alone in half a row. */
.headline--wide {
    grid-column: 1 / -1;
}

/* A card with a chart on its back. The padding moves to the faces so both fill
   the card exactly, and the card keeps a floor high enough for the dial. */
.headline--flip {
    position: relative;
    min-height: 132px;
    padding: 0;
    perspective: 900px;
}

.flip {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    transition: transform 0.55s;
}

.flip__input:checked + .headline .flip {
    transform: rotateY(180deg);
}

.flip__face {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    padding: var(--pad);
    border-radius: var(--radius);
    background: var(--surface);
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
}

/* The dial is centred by nature, and its label reads as a caption beneath it. */
.flip__face--back {
    align-items: center;
    justify-content: center;
    text-align: center;
    transform: rotateY(180deg);
}

.flip__toggle {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 999px;
    color: var(--ink-muted);
    cursor: pointer;
}

.flip__toggle:hover,
.flip__input:checked + .headline .flip__toggle {
    background: var(--ghost);
    color: var(--accent);
}

.flip__input:focus-visible + .headline .flip__toggle {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.flip__toggle svg {
    width: 16px;
    height: 16px;
}

.headline {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    min-height: 96px;
    padding: var(--pad);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
}

/* Today carries the accent: it is the only figure that changes hour to hour.
   A wash rather than a fill, matching how the active tab, the category discs and
   the badges all highlight — which also leaves every colour inside the card
   working as it does everywhere else. */
.headline--accent {
    background: color-mix(in srgb, var(--accent) 9%, var(--surface));
    border-color: color-mix(in srgb, var(--accent) 22%, var(--surface));
}

.headline--accent .headline__value {
    color: var(--accent);
}

.headline__label {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-soft);
}

.headline__value {
    margin: 0;
    font-size: clamp(19px, 5.5vw, 24px);
    font-weight: 700;
    letter-spacing: -0.02em;
    overflow-wrap: anywhere;
    font-variant-numeric: tabular-nums;
}

/* The amount a headline figure stands for, kept at subtitle size so it reads as
   support rather than a second headline. */
.headline__aside {
    margin-left: 7px;
    font-size: 12.5px;
    font-weight: 550;
    letter-spacing: 0;
    color: var(--ink-muted);
}

/* The "used" card carries a dial as well as a figure. */
.headline__meter {
    display: block;
    margin: 2px 0 1px;
}

.gauge {
    display: block;
    width: 100%;
    max-width: 140px;
    margin: 0 auto;
}

.gauge svg {
    display: block;
    width: 100%;
    height: auto;
}

.gauge__track,
.gauge__value {
    fill: none;
    stroke-width: 10;
    stroke-linecap: round;
}

.gauge__track {
    stroke: var(--ghost);
}

.gauge__value {
    stroke: var(--state-on-track);
}

.gauge__value--nearly-spent { stroke: var(--state-nearly-spent); }
.gauge__value--overspent { stroke: var(--state-overspent); }

/* The reading sits in the dial's own empty middle rather than above it. */
.gauge__figure {
    fill: var(--ink);
    font-size: 23px;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.gauge__figure--long {
    font-size: 18px;
}

.headline__hint {
    margin: 0;
    font-size: 12px;
    color: var(--ink-muted);
}

/* A short verdict under a figure — whether it is keeping to the daily rate, or
   whether the budget still covers it. The glyph gives the direction and the
   words give the meaning, so the colour is never the only thing carrying it. */
.badge {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: 5px;
    margin: 2px 0 0;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 620;
    white-space: nowrap;
}

.badge__icon {
    font-size: 9px;
    line-height: 1;
}

.badge--good {
    background: color-mix(in srgb, var(--state-on-track) 16%, var(--surface));
    color: var(--state-on-track);
}

.badge--neutral {
    background: var(--ghost);
    color: var(--ink-soft);
}

.badge--bad {
    background: color-mix(in srgb, var(--state-overspent) 16%, var(--surface));
    color: var(--state-overspent);
}

.card--chart {
    padding: 12px 8px 4px;
}

/* Holds the chart's place when the chosen category has nothing in the month. */
.chart__empty {
    margin: 0;
    padding: 28px 8px;
    color: var(--ink-muted);
    font-size: 13.5px;
    text-align: center;
}

.chart {
    position: relative;
}

.chart svg {
    display: block;
    width: 100%;
    height: auto;
}

/*
 * The line takes the colour of the category it is drawing, falling back to the
 * accent when it is drawing the month as a whole.
 */
.chart--series-1 { --chart-ink: var(--series-1); }
.chart--series-2 { --chart-ink: var(--series-2); }
.chart--series-3 { --chart-ink: var(--series-3); }
.chart--series-4 { --chart-ink: var(--series-4); }
.chart--series-5 { --chart-ink: var(--series-5); }
.chart--series-6 { --chart-ink: var(--series-6); }
.chart--series-7 { --chart-ink: var(--series-7); }
.chart--series-8 { --chart-ink: var(--series-8); }
.chart--series-neutral { --chart-ink: var(--series-neutral); }

.chart__grid {
    stroke: var(--hairline);
    stroke-width: 1;
    stroke-dasharray: 2 5;
}

.chart__axis {
    fill: var(--ink-muted);
    font-size: 9.5px;
    font-weight: 550;
}

.chart__line {
    fill: none;
    stroke: var(--chart-ink, var(--accent));
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.chart__area {
    fill: var(--chart-ink, var(--accent));
    opacity: 0.14;
    stroke: none;
}

/*
 * Small enough to read as part of the line rather than as beads on it; the
 * column-wide target beside each one is what the pointer actually has to hit.
 */
.chart__point {
    fill: var(--chart-ink, var(--accent));
    stroke: var(--surface);
    stroke-width: 1;
}

.chart__target {
    fill: transparent;
}

.chart__target--active {
    fill: var(--chart-ink, var(--accent));
    fill-opacity: 0.07;
}

/*
 * Marks the day being read, down the whole plot. The hidden attribute is an
 * HTML one and does nothing to an SVG element on its own, so it is spelled out.
 */
.chart__rule[hidden] {
    display: none;
}

.chart__rule {
    stroke: var(--chart-ink, var(--accent));
    stroke-width: 1;
    stroke-dasharray: 3 3;
    opacity: 0.5;
}

/*
 * Sits on the point and is lifted clear of it. Never catches the pointer: it
 * would put itself between the pointer and the target underneath.
 */
.chart__readout {
    position: absolute;
    z-index: 1;
    margin: 0;
    padding: 5px 9px;
    transform: translate(-50%, calc(-100% - 12px));
    border-radius: var(--radius-small);
    background: var(--ink);
    color: var(--surface);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    pointer-events: none;
}

/* ---- categories: avatars and tiles -------------------------------------- */

/* The coloured disc that stands in for a category, holding its emoji or, when
   it has none, the first letter of its name. */
.avatar {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: 999px;
    font-size: 19px;
    line-height: 1;
    color: #ffffff;
}

/* Tinted rather than solid: an emoji reads better on a pale disc, and the
   letter fallback keeps its contrast. Browsers without color-mix fall back to
   the solid series colour set above, with white text. */
.avatar.series-1 { background: color-mix(in srgb, var(--series-1) 20%, var(--surface)); color: var(--series-1); }
.avatar.series-2 { background: color-mix(in srgb, var(--series-2) 20%, var(--surface)); color: var(--series-2); }
.avatar.series-3 { background: color-mix(in srgb, var(--series-3) 20%, var(--surface)); color: var(--series-3); }
.avatar.series-4 { background: color-mix(in srgb, var(--series-4) 20%, var(--surface)); color: var(--series-4); }
.avatar.series-5 { background: color-mix(in srgb, var(--series-5) 20%, var(--surface)); color: var(--series-5); }
.avatar.series-6 { background: color-mix(in srgb, var(--series-6) 20%, var(--surface)); color: var(--series-6); }
.avatar.series-7 { background: color-mix(in srgb, var(--series-7) 20%, var(--surface)); color: var(--series-7); }
.avatar.series-8 { background: color-mix(in srgb, var(--series-8) 20%, var(--surface)); color: var(--series-8); }
.avatar.series-neutral { background: color-mix(in srgb, var(--series-neutral) 20%, var(--surface)); color: var(--ink-soft); }

/* A disc and the badge on its corner, held together so the badge has something
   to be positioned against. */
.marked-avatar {
    position: relative;
    flex: 0 0 auto;
    line-height: 0;
}

/*
 * What a transaction is charged to and how it was paid for, worn on the corners
 * of its category disc rather than spelled out in chips of their own — marks
 * where the eye already is, instead of words that pushed the tags onto another
 * line.
 *
 * A ring in the surface colour cuts each out of the disc behind it, so a dark
 * emoji on a dark category colour still reads.
 */
.marked-avatar__badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    border-radius: 999px;
    background: var(--surface);
    box-shadow: 0 0 0 2px var(--surface);
    font-size: 10px;
    line-height: 1;
}

/* Beats the display above, which a bare [hidden] cannot. */
.marked-avatar__badge[hidden] {
    display: none;
}

/* The budget takes the opposite corner, so the two never crowd each other. */
.marked-avatar__badge--budget {
    top: -2px;
    left: -2px;
    right: auto;
    bottom: auto;
}

/* The mark inside is sized for a chip, which is roomier than this. */
.marked-avatar__badge .mark {
    font-size: 11px;
}

/* A budget with no emoji is known by its colour, so the swatch fills the badge
   rather than sitting as a small square inside it. */
.marked-avatar__badge .swatch {
    width: 10px;
    height: 10px;
    border-radius: 999px;
}

/* Carries a plain mark rather than an emoji, so it needs enough weight to read
   at the size the discs are drawn. */
.avatar--ghost {
    background: var(--ghost);
    color: var(--ink-muted);
    font-weight: 650;
}

/* Holds the category radios, their tiles, and the sub-category rows those
   radios reveal — all siblings, so one CSS rule can reach across them. */
.picker {
    display: block;
}

/* Hidden until its parent is the one chosen; expense-form.php emits one rule
   per parent to do the revealing, which needs no script. */
.subcategories__group {
    display: none;
    margin-top: 12px;
}

.tiles {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px 6px;
}

.tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    padding: 8px 2px;
    border: 1.5px solid transparent;
    border-radius: var(--radius-small);
    color: var(--ink);
    cursor: pointer;
}

.tile__label {
    max-width: 100%;
    font-size: 11px;
    font-weight: 550;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.choice__input:checked + .tile {
    border-color: var(--accent);
    background: var(--ghost);
}

.choice__input:focus-visible + .tile {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.tile--add .avatar {
    border: 1.5px dashed var(--hairline);
    background: transparent;
    font-size: 22px;
}

/* ---- transaction entry --------------------------------------------------- */

/* Cards stacked inside one form need the same gap the page gives its own
   children, or they read as a single block. */
.form-stack {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

/* The same box as .card — border included, so the two line up exactly. */
.amount-hero {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: var(--pad);
    min-height: 96px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
}

.amount-hero .avatar {
    width: 52px;
    height: 52px;
    font-size: 24px;
}

/* The disc beside the amount is the larger of the two, so its badges are too. */
.amount-hero .marked-avatar__badge {
    right: -3px;
    bottom: -3px;
    width: 22px;
    height: 22px;
    font-size: 12px;
}

.amount-hero .marked-avatar__badge--budget {
    top: -3px;
    left: -3px;
    right: auto;
    bottom: auto;
}

.amount-hero .marked-avatar__badge .mark {
    font-size: 13px;
}

.amount-hero .marked-avatar__badge .swatch {
    width: 12px;
    height: 12px;
}

/*
 * The amount input has no border of its own, so the shared focus outline drew a
 * rectangle around nothing. The ring moves to the card, which is the thing the
 * eye reads as the field.
 */
.amount-hero:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.amount-hero__input:focus-visible {
    outline: none;
}

.amount-hero__field {
    position: relative;
    flex: 1;
    min-width: 0;
}

/* The currency of the budget being charged, so the figure is never ambiguous. */
.amount-hero__currency {
    position: absolute;
    top: -2px;
    right: 2px;
    font-size: 12px;
    font-weight: 650;
    letter-spacing: 0.04em;
    color: var(--ink-muted);
}

.amount-hero__input {
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--ink);
    font-size: clamp(30px, 10vw, 42px);
    font-weight: 700;
    letter-spacing: -0.02em;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.field-row {
    display: flex;
    /* Rather than overflow when two controls will not fit, they stack. */
    flex-wrap: wrap;
    gap: 10px;
}

/*
 * A floor wide enough to hold a date, so the pair wraps onto separate lines
 * rather than trying to share a row it does not fit in.
 *
 * 200px is measured, not guessed: iOS writes the value as "31 Aug 2026" and
 * adds a picker icon, which needs about 190px once the input's own padding is
 * counted. A phone card gives each of two fields about 158px, so they used to
 * overflow — and iOS overflows where Chrome quietly clips, which is why this
 * only ever showed up on the phone. Above roughly a 440px viewport both fit and
 * they share the row again.
 */
.field-row .field {
    flex: 1 1 200px;
    min-width: 0;
}

/*
 * And the inputs themselves have to be allowed to shrink. The native appearance
 * is what carries that intrinsic width on iOS; without it, width:100% wins and
 * the field decides its own size again.
 */
.input[type="date"],
.input[type="time"],
.input[type="datetime-local"] {
    -webkit-appearance: none;
    appearance: none;
    min-width: 0;
}

/*
 * Scoped to the row so it beats ".field-row .field { flex: 1 }" above, which
 * otherwise wins on specificity and hands the icon half the row.
 */
.field-row .field--icon {
    flex: 0 0 auto;
}

/*
 * A circle the height of the field beside it: the emoji typed here becomes a
 * round disc everywhere else in the app, so the box it is chosen in should be
 * the same shape rather than a wide rectangle holding one character.
 */
.input--icon {
    width: 46px;
    height: 46px;
    padding: 0;
    border-radius: 999px;
    text-align: center;
    font-size: 22px;
    line-height: 1;
}

/* ---- cash against visa -------------------------------------------------- */

.pie-card {
    display: flex;
    align-items: center;
    gap: 18px;
}

.pie {
    flex: 0 0 108px;
}

.pie svg {
    display: block;
    width: 108px;
    height: 108px;
}

.pie__slice--cash { fill: var(--paid-cash); }
.pie__slice--visa { fill: var(--paid-visa); }

/* A hairline between wedges, so two dark colours still read as two. */
.pie__slice {
    stroke: var(--surface);
    stroke-width: 1.5;
}

.pie__legend {
    flex: 1;
    min-width: 0;
}

.pie__row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 0;
    font-size: 14px;
}

.pie__key {
    flex: none;
    width: 10px;
    height: 10px;
    border-radius: 3px;
}

.pie__key--cash { background: var(--paid-cash); }
.pie__key--visa { background: var(--paid-visa); }

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

.pie__share {
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

.pie__amount {
    color: var(--ink-muted);
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
}

/* ---- the way in to a web install ---------------------------------------- */

.login__mark {
    display: block;
    width: 72px;
    height: 72px;
    margin: 0 auto 12px;
    border-radius: 22%;
}

.login__title {
    margin: 0 0 18px;
    font-size: 22px;
}

/* ---- floating add button ------------------------------------------------- */

.fab {
    position: fixed;
    right: var(--pad);
    bottom: calc(var(--tabbar-height) + var(--tabbar-inset) + var(--safe-bottom) + 14px);
    z-index: 21;
    display: grid;
    place-items: center;
    width: var(--fab-size);
    height: var(--fab-size);
    border-radius: 999px;
    background: var(--accent);
    color: var(--accent-ink);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.24);
}

.fab svg {
    width: 26px;
    height: 26px;
}

/* Amounts in several currencies, printed side by side and never summed. */
.money-list__separator {
    opacity: 0.5;
}

/* Draft budgets --------------------------------------------------------- */

/*
 * The palette classes paint a background, which is what a swatch or a bar
 * wants and what an SVG wedge ignores. The same eight colours again as a fill,
 * so a wedge and the bar under it are recognisably one allocation.
 */
.pie__slice.series-1 { fill: var(--series-1); }
.pie__slice.series-2 { fill: var(--series-2); }
.pie__slice.series-3 { fill: var(--series-3); }
.pie__slice.series-4 { fill: var(--series-4); }
.pie__slice.series-5 { fill: var(--series-5); }
.pie__slice.series-6 { fill: var(--series-6); }
.pie__slice.series-7 { fill: var(--series-7); }
.pie__slice.series-8 { fill: var(--series-8); }
.pie__slice.series-neutral { fill: var(--series-neutral); }

/* What is still free reads as absence, so it is drawn as a gap would be. */
.pie__slice.draft-free { fill: color-mix(in srgb, var(--ink-muted) 22%, transparent); }
.pie__key.draft-free { background: color-mix(in srgb, var(--ink-muted) 34%, transparent); }
.meter__fill.draft-free { background: color-mix(in srgb, var(--ink-muted) 34%, transparent); }

/* Going over is the one thing the draft exists to catch, so it is the one
   colour that is not from the neutral palette. */
.pie__slice.draft-over,
.meter__fill.draft-over { fill: var(--state-overspent); background: var(--state-overspent); }

.rank--quiet .rank__name,
.rank--quiet .rank__share {
    color: var(--ink-muted);
}

/* A draft's headline reading, above its charts. */
.draft-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.draft-total__figure {
    font-size: 26px;
    font-weight: 680;
    font-variant-numeric: tabular-nums;
}

.draft-total__note {
    font-size: 13px;
    color: var(--ink-muted);
}

.draft-total__note--over {
    color: var(--state-overspent);
    font-weight: 620;
}

/* ---- bank sms ----------------------------------------------------------- */

/* The message as the bank wrote it: its own line breaks, and long words that
   wrap instead of pushing the card wider than the phone. */
.sms-body {
    margin: 6px 0 0;
    font-size: 13.5px;
    line-height: 1.45;
    color: var(--ink-soft);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.sms-source__heading {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-soft);
}

.sms-source .sms-body {
    padding: 10px 12px;
    border-radius: 12px;
    background: var(--ghost);
    color: var(--ink);
}

/* The card already has its padding, so the read-out rows run edge to edge
   inside it rather than doubling it. */
.sms-source__details {
    margin: 8px calc(var(--pad) * -1) calc(var(--pad) * -1);
}

.sms-source__details .detail span:last-child {
    text-align: right;
    overflow-wrap: anywhere;
}

/* A regex is read character by character, so it gets a font that keeps them apart. */
.input--code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13.5px;
}

textarea.input {
    resize: vertical;
}

.sms-actions {
    display: flex;
    gap: 8px;
}

.sms-actions > * {
    flex: 1 1 0;
}
