/* Shared components used by both the public and admin applications.
 *
 * Native nesting uses an explicit `&` and stays shallow. The supported
 * browser floor is Chrome/Edge 123, Firefox 125 and Safari/iOS 17.5.
 */

/* --- Buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding: var(--space-2) var(--space-4);
  margin: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border-strong);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  text-shadow: none;
  white-space: normal;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: none;

  &:hover,
  &:focus {
    background: var(--color-surface-sunk);
    color: var(--color-text);
    text-decoration: none;
  }

  &:active {
    transform: translateY(1px);
  }

  &[disabled],
  &.disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
  }
}

/* Three intents. btn-secondary is the bare .btn above, named for the templates
   that want to say so explicitly. */
.btn-primary,
.btn-primary:hover,
.btn-primary:focus {
  background: var(--color-action);
  border-color: var(--color-action);
  color: var(--color-action-ink);
}
.btn-primary:hover,
.btn-primary:focus {
  filter: brightness(var(--action-hover-brightness));
}

.btn-danger,
.btn-danger:hover,
.btn-danger:focus {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: var(--color-danger-ink);
}
.btn-danger:hover,
.btn-danger:focus {
  filter: brightness(1.15);
}

/* A secondary button has to read as a button against a white page. White on
   white with a hairline border did not: in a Spara / Tillbaka / Ta bort row the
   middle one dissolved and the three ran together. It gets a filled surface and
   a border dark enough to hold an edge. */
.btn-secondary,
.btn-default {
  background: var(--color-surface-sunk);
  border-color: var(--color-border-strong);
  color: var(--color-text);
}
.btn-secondary:hover,
.btn-default:hover {
  background: var(--color-border);
  border-color: var(--color-text-faint);
}

/* Bootstrap 2 leftovers still in the templates. Mapped rather than swept so no
   template edit is needed; the sweep can happen lazily. */
.btn-large,
.btn-lg {
  min-height: 3rem;
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-base);
}

/* --text-sm, not --text-xs. 12px is the badge and overline size -- text that
   annotates something else -- and a button is never that: "Maila Alla" and
   "Utskrift" sat at 12px directly under a 14px table and read as captions on
   it rather than as the two actions for it. The compact variant is a smaller
   button, not a smaller class of text, so it drops the box and keeps the
   label. */
/* .btn allows its label to wrap, which is what a full-width form action on a
   phone needs. The compact sizes are the opposite case: they are the inline
   actions inside a table row or beside a heading -- "+ kontrakt", "Skriv
   kontrakt", "+ Ny åtgärd" -- with two-word labels and a column width they do
   not control. Left wrappable, "+ kontrakt" broke across two lines inside its
   own capsule as soon as the Kontrakt column got squeezed, which reads as two
   buttons rather than one. The label holds together and the column gives way
   instead. */
.btn-mini,
.btn-small,
.btn-xs,
.btn-sm {
  min-height: var(--control-sm);
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
  white-space: nowrap;
}

/* On a touch screen even the "small" variants need a real target, so the
   compact sizes only apply where there is a pointer. */
@media (pointer: coarse) {
  .btn-mini,
  .btn-small,
  .btn-xs,
  .btn-sm {
    min-height: var(--tap);
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
  }
}

/* Tone-only variants left over from Bootstrap. They keep their meaning but
   stop being full-bleed colour: eight competing saturated fills in one toolbar
   is why the admin reads as noisy.
 *
 * The tone used to be a 3px stripe down the left edge, which fought the rounded
 * corner (a straight bar cannot follow one) and made the button look like a
 * status row that had wandered into a toolbar. It is carried by the whole
 * button now -- the status wash as the fill, the status ink as both label and
 * border -- so the shape stays one soft rectangle and the colour is still
 * clearly a tone rather than a second primary. Every pair below is a token we
 * already have, and each clears AA on its own wash: green 4.61, amber 4.85,
 * blue 5.45. */
.btn-success {
  background: var(--status-active-wash);
  border-color: var(--status-active);
  color: var(--status-active);
}
.btn-warning {
  background: var(--status-expiring-wash);
  border-color: var(--status-expiring);
  color: var(--status-expiring);
}
.btn-info {
  background: var(--status-upcoming-wash);
  border-color: var(--status-upcoming);
  color: var(--status-upcoming);
}

/* .btn's own :hover swaps in --color-surface-sunk, which would throw the tone
   away on hover. Deepening the wash instead keeps the tone and keeps the label
   contrast, since the ink is untouched. */
.btn-success:hover,
.btn-success:focus,
.btn-warning:hover,
.btn-warning:focus,
.btn-info:hover,
.btn-info:focus {
  filter: brightness(0.95);
}
.btn-success:hover,
.btn-success:focus {
  background: var(--status-active-wash);
  color: var(--status-active);
}
.btn-warning:hover,
.btn-warning:focus {
  background: var(--status-expiring-wash);
  color: var(--status-expiring);
}
.btn-info:hover,
.btn-info:focus {
  background: var(--status-upcoming-wash);
  color: var(--status-upcoming);
}

/* A full-width button is the right default for a form action on a phone. */
@media (max-width: 47.9375rem) {
  .form-actions .btn {
    width: 100%;
  }
}

/* A destructive or state-changing action is a form now, not a link: only a form
   can make a request that is not a GET, and button_to is that form. Its markup
   is a block-level <form> wrapping one button, which would otherwise break
   every row it sits in -- an action bar, a table cell, a menu panel. display:
   contents hands the parent's layout straight to the button, so all the rules
   below (and .form-actions' `margin-left: auto` on the destructive one) go on
   matching the button itself, exactly as they did when it was an <a>. */
.button_to {
  display: contents;
}

/* The other half of detached_button_to: the form itself, parked at the end of
   the document with nothing in it but its hidden method and CSRF fields. It
   must occupy no space in the page it was lifted out of. */
.detached-form {
  display: none;
}

/* Floated action buttons. `.btn ... pull-right` is the admin's toolbar idiom:
   a handful of buttons each floated individually beside a heading, most of a
   dozen of them on the contract form alone. A float takes the button out of
   flow and collapses the whitespace between them, so they render welded
   together. The float is what the templates ask for, so the separation comes
   from a margin rather than from wrapping them in a flex row. The bottom
   margin covers the second line once a toolbar wraps. */
.btn.pull-right {
  margin-left: var(--space-2);
  margin-bottom: var(--space-2);
}
.btn.pull-left {
  margin-right: var(--space-2);
  margin-bottom: var(--space-2);
}

/* A page-level action row replaces the old habit of floating each button on
   its own. It keeps separate actions together, gives them a real wrapping gap,
   and leaves a consistent beat before tabs or table content. */
.page-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.page-actions .btn {
  white-space: nowrap;
}

@media (max-width: 47.9375rem) {
  .page-actions {
    justify-content: flex-start;
  }

  .page-actions .btn {
    padding-inline: var(--space-3);
  }

  /* Residual floated buttons outside a page action row return to their
     intrinsic size and wrap like chips instead of becoming full-width bars. */
  .btn.pull-right,
  .btn.pull-left {
    display: inline-flex;
    margin-left: 0;
    margin-right: var(--space-2);
    margin-bottom: var(--space-3);
  }
}

/* --- Form controls -------------------------------------------------------- */

.form-control,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="tel"],
input[type="date"],
input[type="search"],
select,
textarea {
  display: block;
  width: 100%;
  min-height: var(--tap);
  /* base.css sets `font: inherit` on the elements so fields match the document,
     but Bootstrap's `.form-control { font-size: 14px }` outranks a bare element
     selector -- including the <768px rule that exists precisely to keep fields
     at 16px. Every simple_form field therefore still rendered at 14px, and iOS
     Safari zooms the viewport when you tap a field below 16px, which is the
     layout lurching sideways on tap. Restated here, where it wins. */
  font-size: var(--text-base);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-bg);
  color: var(--color-text);
  box-shadow: none;
}

.form-control:focus,
input:focus,
select:focus,
textarea:focus {
  border-color: var(--color-brand);
  box-shadow: 0 0 0 3px var(--color-brand-wash);
  outline: none;
}

textarea {
  min-height: 6rem;
  resize: vertical;
}

/* The dropdown arrow.
 *
 * A native <select> paints its own arrow, and it paints it hard against the
 * right border -- about 5px in Chrome -- while the rule above gives the text
 * 12px of air on the left. The field reads lopsided: padding on one side, a
 * mark jammed into the frame on the other. Padding does not fix it, because
 * the browser positions that arrow against the border box rather than inside
 * the padding it appears to sit in.
 *
 * So the arrow becomes ours, and it is drawn rather than fetched -- the same
 * choice as the popover's close cross in admin_shell.css, for a sharper
 * reason here: `background-image` cannot take `currentColor`, so an SVG or a
 * glyph would be one fixed colour, which is precisely the thing that cannot
 * follow the theme. Two gradients take --color-text-muted and resolve it
 * through light-dark() like every other colour in the system.
 *
 * The chevron is two 5px squares filled diagonally: the left keeps its
 * top-right triangle, the right its top-left, so the fills meet across the top
 * and taper to a point below. It sits --space-3 from the right edge -- the
 * same inset the text has on the left, which is the whole point.
 *
 * Single selects only. A [multiple] or [size] select is a list box with no
 * arrow to replace, and would get a chevron floating in its top corner.
 *
 * `appearance: none` also drops the native ring and the OS styling of the
 * closed field, both already replaced above. It does not touch the popup list:
 * the browser still draws that, and still themes it from `color-scheme`.
 */
select:not([multiple]):not([size]) {
  /* Named, so a state below can re-point the chevron without restating both
     gradients. */
  --select-chevron: var(--color-text-muted);

  -webkit-appearance: none;
  appearance: none;
  /* The chevron occupies 12px..22px in from the right edge, so a long option
     label has to stop before it rather than run underneath. */
  padding-right: var(--space-8);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--select-chevron) 50%),
    linear-gradient(135deg, var(--select-chevron) 50%, transparent 50%);
  background-size: 5px 5px;
  background-position:
    right calc(var(--space-3) + 5px) center,
    right var(--space-3) center;
  background-repeat: no-repeat;
}

/* Bootstrap 2 width classes. On a phone these forced fixed pixel widths that
   overflowed the viewport; they now cap width instead of setting it. */
.input-mini {
  max-width: 6rem;
}
.input-small {
  max-width: 9rem;
}
.input-medium {
  max-width: 14rem;
}
.input-xlarge {
  max-width: 22rem;
}
.input-xxlarge {
  max-width: 32rem;
}

label,
.control-label {
  display: inline-block;
  font-weight: 500;
  margin-bottom: var(--space-1);
}

.help-block,
p.help-block {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-style: normal;
  margin-top: var(--space-1);
}

.has-error .form-control,
.has-error input,
.has-error select,
.has-error textarea {
  border-color: var(--color-danger);
}
.has-error .control-label,
.has-error .help-block {
  color: var(--color-danger);
}

/* Unavailable fields.
 *
 * Only buttons had a disabled state before this; a disabled input, select or
 * textarea was left to the browser's own greying. That was survivable until
 * the rule above took `appearance` off the select -- which takes the OS
 * greying with it, so a disabled dropdown became indistinguishable from a live
 * one. Stated here for all three rather than just the select, because the
 * inconsistency was already there and a disabled text field looking editable
 * is the same bug.
 *
 * readonly joins disabled: the value still matters and is still selectable, so
 * it keeps the ordinary ink and only the surface says "not yours to change".
 * (HTML ignores readonly on a <select> -- see admin/apartments/_form.html.erb,
 * which sets it -- so that one is a no-op in the browser, not here.)
 *
 * Spelled out by type instead of a bare `input`. `:read-only` matches any input
 * that is not user-editable, which is not only the text field with the
 * attribute on it: every <input type="submit"> is read-only, and so is every
 * checkbox and radio. A bare `input:read-only` (0,1,1) therefore painted this
 * surface over .btn-primary's (0,1,0) on the Spara button while leaving the
 * label at --color-brand-ink -- near-black on dark grey in dark mode, white on
 * near-white in light -- and handed every checkbox `cursor: not-allowed`.
 * `:disabled` has the same reach, and a disabled button already has its own
 * state on .btn, so both are scoped to the same list of field types the form
 * controls above use.
 */
input:is([type="text"], [type="email"], [type="password"], [type="number"],
    [type="tel"], [type="date"], [type="search"]):is(:disabled, :read-only),
select:disabled,
textarea:disabled,
textarea:read-only {
  background-color: var(--color-surface-sunk);
  cursor: not-allowed;
}
input:is([type="text"], [type="email"], [type="password"], [type="number"],
    [type="tel"], [type="date"], [type="search"]):disabled,
select:disabled,
textarea:disabled {
  color: var(--color-text-faint);
}
/* The drawn chevron has no browser to dim it.
 *
 * The :not() pair is repeated rather than dropped: a custom property obeys the
 * cascade like any declaration, and a bare `select:disabled` (0,1,1) loses to
 * the (0,2,1) rule that sets --select-chevron above -- so the chevron stayed
 * at full strength on a field that was otherwise greyed. */
select:not([multiple]):not([size]):disabled {
  --select-chevron: var(--color-text-faint);
}

/* Checkboxes and radios need a target that is comfortable to hit. */
.checkbox,
.radio {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
}
.checkbox label,
.radio label {
  margin: 0;
  font-weight: 400;
}
input[type="checkbox"],
input[type="radio"] {
  width: 1.15rem;
  height: 1.15rem;
  min-height: 0;
  margin: 0;
  flex: none;
  accent-color: var(--color-brand);
}

.form-group {
  margin-bottom: var(--space-4);
}

/* form-horizontal is the label-beside-field layout used by all 15 admin forms.
   It was Bootstrap's float grid: .col-sm-3 on the label, .col-sm-9 on a wrapper
   round the control. Two floats in a .row, cancelled by negative margins on the
   row and matching padding on the columns -- so every rule here used to exist to
   undo one of those. Two grid tracks say the same thing directly, and the label
   column no longer has to be a fraction that happens to be 25%.

   The wrapper keeps a class of its own (.form-field) because it is a real part
   of the layout: it is what the error and hint text share a column with. */
.form-horizontal {
  /* The width of the label column, named because .form-pair below has to line
     itself up with it. One value, one place to change it. */
  --label-col: 12rem;
}

.form-horizontal .form-group {
  display: grid;
  grid-template-columns: minmax(0, var(--label-col)) minmax(0, 1fr);
  align-items: baseline;
  gap: var(--space-1) var(--space-4);
}

.form-horizontal .control-label {
  grid-column: 1;
  margin-bottom: 0;
  text-align: right;
}

.form-horizontal .form-field {
  grid-column: 2;
  min-width: 0;
}

/* A checkbox has no label of its own in column 1 -- the label rides beside the
   box -- so its wrapper is placed in column 2 explicitly rather than flowing
   into the empty label track. */
.form-horizontal .form-field--offset {
  grid-column: 2;
}

/* Two short fields sharing one row, e.g. Boarea + Tomt. Each is a whole
   form-group, so it would otherwise bring its own label column and leave the
   value in a 6-character box. Inside a pair the label goes above instead. */
.form-pair {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-column: 1 / -1;
}

/* A pair is a sibling of the .form-group rows, not a cell inside them: each row
   is its own grid, and they line up only because they all name the same label
   track. A pair has no label track, so left to itself it starts at the column's
   left edge -- three of them in a row on /admin/hus/:id/edit (Boarea, Taxerad,
   Drift) broke the label spine and re-formed it again at Byggar. Indenting by
   the label column plus the row gap puts the pair where every other row's field
   sits, so the fields stay in one vertical line down the form.

   The margin is on the pair rather than on its groups because those have theirs
   zeroed -- without it the three pair rows collapsed against each other and each
   label sat directly under the input above it. */
.form-horizontal .form-pair {
  margin-left: calc(var(--label-col) + var(--space-4));
  margin-bottom: var(--space-4);
}
.form-horizontal .form-pair .form-group {
  grid-template-columns: minmax(0, 1fr);
  margin-bottom: 0;
}
.form-horizontal .form-pair .control-label {
  text-align: left;
}
.form-horizontal .form-pair .form-field {
  grid-column: 1;
}

/* A pair cell is already the width constraint, so the Bootstrap 2 width caps
   (.input-mini and friends) only make the two halves ragged against each other.
   The markup no longer sets them inside a pair; this keeps a stray one honest. */
.form-horizontal .form-pair :is(.form-control, select, textarea) {
  max-width: none;
}

/* Below the breakpoint the two tracks become one: at 375px a 12rem label column
   leaves the input about six characters wide. */
@media (max-width: 47.9375rem) {
  .form-horizontal .form-group {
    grid-template-columns: minmax(0, 1fr);
  }
  .form-horizontal .control-label,
  .form-horizontal .form-field,
  .form-horizontal .form-field--offset {
    grid-column: 1;
    text-align: left;
  }
  /* No label column left to line up with. */
  .form-horizontal .form-pair {
    margin-left: 0;
  }
}

/* --- Long-text editor grid ------------------------------------------------
 *
 * A form that is nothing but big free-text boxes -- the house presentation tab
 * is four of them and nothing else. Two things differ from an ordinary admin
 * form, and both follow from the boxes being the page rather than one row of
 * it.
 *
 * The title goes above its box instead of into .form-horizontal's 12rem label
 * column. Beside a one-line input that column is a tidy alignment; beside a box
 * meant to hold paragraphs it spends a quarter of the width on the word
 * "Beskrivning" and leaves the prose a slot to be typed into.
 *
 * The height comes from what the window has left rather than from a `rows`
 * count. A row count is a guess at one window size -- rows: 20 is a short box
 * on a desktop and an overlong one on a laptop -- while these boxes should
 * simply fill the screen they are given. `resize: vertical` still applies, so
 * one box can be dragged taller than that for a moment.
 *
 * What is set here is a floor, not a final size: admin_forms.js raises the
 * min-height of a box whose text does not fit, so a long contract text is read
 * by scrolling the page rather than by scrolling inside a full-height box.
 *
 * Compose with a column class for the tracks: `class="grid-2 form-editor"`.
 */
/* Title above, box filling the rest of the cell. This replaces the two label
   tracks .form-horizontal gives every .form-group; a flex column rather than a
   grid because the box has to take the leftover height, which `flex: 1` states
   and a grid row of unknown height cannot. */
.form-horizontal .form-editor .form-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: 0;
}

.form-horizontal .form-editor .control-label {
  text-align: left;
}

/* The floor: half of what the window has left, so two rows of boxes fill the
   screen. 10.5rem is everything else on the page divided over those two rows --
   app bar, breadcrumb, tab row, action row, and each box's own title -- and is
   the one number here kept by eye. dvh rather than vh so a phone's retracting
   toolbar does not sit over the last box, and a plain 11rem under it so the
   one-column layout does not shrink with a short window.

   The rows are sized independently (`auto`, not `1fr`): a long contract text
   must not inflate the empty box on the row above it. Within a row the boxes do
   stay level -- the shorter one's .form-group is stretched to the row, and
   `flex: 1` spends that on the box rather than leaving a gap under it.

   min-height rather than height because admin_forms.js raises it to fit the
   text; see there for why that is the property both of these use. */
.form-editor textarea {
  flex: 1;
  min-height: max(11rem, calc(50dvh - 10.5rem));
}

/* Room between actions, and the destructive one pushed to the far end. Ta bort
   sat directly beside Spara with an 8px gap, which is both hard to read as
   three separate controls and an easy misclick on a form this long. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) 0 0;
  margin: var(--space-6) 0 0;
  border-top: 1px solid var(--color-border);
  background: none;

  & .btn-danger,
  & .btn-destroy {
    margin-left: auto;

    @media (max-width: 47.9375rem) {
      /* No room to push it aside on a phone, so separate it with space instead. */
      margin-left: 0;
      margin-top: var(--space-4);
    }
  }
}

/* --- Inline alerts --------------------------------------------------------
 *
 * A banner inside the page flow, placed by a template that has something to say
 * about the content beneath it (admin/houses/todos.erb, the inventory form).
 * Flash messages used to render as these too; they are toasts now, below. */

.alert,
.toast {
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-text-faint);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-text);
}

.alert {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  text-shadow: none;
}
.alert-warning {
  border-color: var(--status-expiring);
  color: var(--status-expiring);
}

/* A heading inside an alert or a well is that box's own title. The box already
   supplies the separation from what came before it, so the heading drops the
   top margin base.css gives it for an open page -- otherwise it pushes itself
   away from the edge it is supposed to sit against. */
.alert > :is(h1, h2, h3, h4, h5, h6),
.well > :is(h1, h2, h3, h4, h5, h6) {
  margin-top: 0;
}
.alert > :is(h1, h2, h3, h4, h5, h6):only-child {
  margin-bottom: 0;
}

/* --- Toasts ---------------------------------------------------------------
 *
 * Flash messages. Every flash in this app arrives on a redirect -- a whole page
 * load -- so the markup is server-rendered by shared/_flash and none of this
 * needs JavaScript to receive a message.
 *
 * The region is fixed and ignores the pointer; each toast takes the pointer
 * back for itself. That way a toast that has faded out, or one parked in the
 * corner waiting to be dismissed, can never swallow a click meant for the page
 * underneath it.
 *
 * Placement: pinned top-right on a desktop and sliding in from the right edge;
 * a full-width strip at the top of a phone, sliding down. A 21rem card in a
 * corner is most of a narrow screen and none of it is near the thumb.
 *
 * The exit is a second animation with a delay rather than a JS timer, and the
 * hover pause is `animation-play-state`, so the whole behaviour survives with
 * scripting off -- which matters because admin/empty.html.erb, the login
 * layout, deliberately loads none. flash.js only adds the close button and
 * clears the dead node afterwards.
 */
.toast-region {
  /* The admin's app bar is fixed and the toast outranks it, so admin_shell.css
     re-points this to clear the bar. The public site's header scrolls away with
     the page, so there the toast stays put and overlays it, which is what a
     toast is for -- hence a property one bundle overrides rather than a
     --header-h calc both would inherit. */
  --toast-top: var(--space-4);

  position: fixed;
  z-index: var(--z-toast);
  top: var(--toast-top);
  right: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: min(21rem, calc(100vw - var(--space-8)));
  pointer-events: none;
}

.toast {
  /* The entrance offset, as a pair of properties rather than two sets of
     keyframes: the direction is a question about the viewport, and the media
     queries below answer it by re-pointing these. Far enough to be a travel
     and not a twitch -- a slow animation over a short distance reads as a
     hesitation rather than as movement. */
  --toast-x: 2.5rem;
  --toast-y: 0;

  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  pointer-events: auto;
  /* forwards on the exit, not both. `both` includes the backwards fill, which
     applies an animation's first keyframe throughout its delay -- so toast-out,
     being second and therefore winning, pinned the element at `opacity: 1;
     transform: none` for the whole five seconds and the entrance never showed.
     The toast popped into place instead of sliding. forwards keeps the end
     state, which is the only half of it this needs. */
  /* Slow enough to read as a movement rather than a repaint. A toast is the
     only thing on screen that arrives without being asked for, so it has to
     announce itself; 200ms did the job invisibly and just looked like the page
     had always had it. The exit is longer than the entrance because leaving
     unprompted needs more warning than arriving does. */
  animation:
    toast-in 400ms ease-out both,
    toast-out 600ms ease-in 5s forwards;

  /* Reading it should not cost you it. Focus counts as reading: the dismiss
     button is in the tab order, and tabbing to it must not start a countdown
     that takes the button away. */
  &:hover,
  &:focus-within {
    animation-play-state: paused;
  }
}

.alert-success,
.toast--success {
  border-left-color: var(--status-active);
  background: var(--status-active-wash);
}

.alert-info,
.toast--info {
  border-left-color: var(--status-upcoming);
  background: var(--status-upcoming-wash);
}

.alert-warning,
.toast--warning {
  border-left-color: var(--status-expiring);
  background: var(--status-expiring-wash);
}

.alert-danger,
.alert-error,
.toast--danger {
  border-left-color: var(--color-danger);
  background: var(--color-danger-wash);
}

/* An error is not a receipt. "Fel vid inloggningen" that clears itself after
   five seconds is a message the reader can lose by blinking, so danger keeps
   the entrance and drops the exit -- it goes when it is dismissed, or on the
   next page load. */
.toast--persistent {
  animation: toast-in 200ms ease-out both;
}

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

/* The cross is drawn, not typed, for the reason .popover-close gives at length:
   a "\00d7" glyph centres its line box rather than itself and rides high. This
   is not .popover-close because that lives in admin_shell.css and the public
   site does not load it; the toast is shared, so it is here. */
.toast__dismiss {
  display: grid;
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  margin: -1px 0 0;
  padding: 0;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--color-text-muted);
  cursor: pointer;
  appearance: none;

  &::before,
  &::after {
    grid-area: 1 / 1;
    width: 0.625rem;
    height: 1.5px;
    border-radius: 1px;
    background: currentColor;
    content: "";
  }

  &::before {
    transform: rotate(45deg);
  }

  &::after {
    transform: rotate(-45deg);
  }

  &:hover {
    color: var(--color-text);
  }

}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translate(var(--toast-x), var(--toast-y));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes toast-out {
  from {
    opacity: 1;
    transform: none;
  }
  to {
    opacity: 0;
    /* visibility, not just opacity: a fully transparent element is still a
       target. The region's pointer-events cover the gap between toasts, this
       covers the toast itself. */
    visibility: hidden;
    transform: translate(var(--toast-x), var(--toast-y));
  }
}

@media (max-width: 47.9375rem) {
  .toast-region {
    right: 0;
    left: 0;
    width: auto;
    padding: var(--space-2);
  }

  .toast {
    --toast-x: 0;
    --toast-y: -2.5rem;
  }
}

/* The offset goes, the fade stays. Killing the animation outright would take
   the auto-dismiss with it and leave every toast on screen for good. */
@media (prefers-reduced-motion: reduce) {
  .toast {
    --toast-x: 0;
    --toast-y: 0;
  }
}


/* --- Labels and badges ---------------------------------------------------- */

.label,
.badge {
  display: inline-block;
  padding: 0.2em 0.55em;
  border-radius: var(--radius);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.4;
  text-shadow: none;
  background: var(--color-surface-sunk);
  color: var(--color-text);
  white-space: nowrap;
}
.label-success,
.badge-success {
  background: var(--status-active-wash);
  color: var(--status-active);
}
.label-warning,
.badge-warning {
  background: var(--status-expiring-wash);
  color: var(--status-expiring);
}
.label-important,
.label-danger,
.badge-danger {
  background: var(--color-danger-wash);
  color: var(--color-danger);
}
.label-info,
.badge-info {
  background: var(--status-upcoming-wash);
  color: var(--status-upcoming);
}
.label-primary,
.badge-primary {
  background: var(--color-brand-wash);
  color: var(--color-brand);
}
/* Used as a neutral colour swatch in the payments legend, so it restates the
   base .label surface rather than leaving it to inheritance. */
.label-default,
.badge-default {
  background: var(--color-surface-sunk);
  color: var(--color-text-muted);
}
/* Bootstrap 2's name for the dark chip, still produced by
   Payment#bootstrap_label for a manually entered payment. */
.label-inverse,
.badge-inverse {
  background: var(--color-inverted);
  color: var(--color-inverted-ink);
}
.badge-pill {
  border-radius: var(--radius-pill);
}

/* Deployment context is shared by all layouts and belongs in the cached
   component bundle, not an inline <style> block. */
.environment-badges {
  position: fixed;
  z-index: var(--z-environment);
  bottom: 0;
  left: 0;
  display: flex;
  overflow: hidden;
  border-radius: 0 6px 0 0;
  box-shadow: var(--environment-shadow);
  pointer-events: none;

  & .environment-badges__badge {
    padding: 5px 10px;
    font: bold var(--text-xs)/1.3 Arial, sans-serif;
    letter-spacing: 0.08em;
    text-transform: uppercase;

    &.environment-badges__badge--database {
      background: var(--environment-database-bg);
      color: var(--environment-database-ink);
    }

    &.environment-badges__badge--environment {
      background: var(--environment-default-bg);
      color: var(--environment-default-ink);
    }

    &.environment-badges__badge--environment-test {
      background: var(--environment-test-bg);
      color: var(--environment-test-ink);
    }

    &.environment-badges__badge--environment-dev {
      background: var(--environment-dev-bg);
      color: var(--environment-dev-ink);
    }
  }
}
