/**
 * Copyright 2026 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * OFE components: buttons, menus, message banners, status chips, tables and
 * empty states. Scoped under `.ofe` like shell.css, and reading only the
 * tokens in tokens.css, so a palette change stays a one-file change.
 */

/*
 * `hidden` must win over every component rule here. Any class rule that sets
 * `display` (`.btn { display: inline-flex }`, for one) otherwise defeats the
 * user-agent rule for [hidden].
 */
.ofe [hidden] { display: none !important; }

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

.ofe .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.5rem;
  padding: 0.5rem 1.5rem;
  border: 0;
  border-radius: var(--md-sys-shape-corner-full);
  background: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: filter var(--md-sys-motion-duration-short2) var(--md-sys-motion-easing-standard);
}

.ofe .btn:hover { filter: brightness(1.08); }

.ofe .btn[disabled],
.ofe .btn[aria-disabled="true"] {
  background: var(--md-sys-color-surface-container-highest);
  color: var(--md-sys-color-outline);
  cursor: not-allowed;
  filter: none;
}

.ofe .btn--outlined {
  background: none;
  border: 1px solid var(--md-sys-color-outline);
  color: var(--md-sys-color-primary);
}

.ofe .btn--outlined:hover,
.ofe .btn--text:hover {
  background: var(--md-sys-color-surface-container-high);
  filter: none;
}

.ofe .btn--text {
  background: none;
  color: var(--md-sys-color-primary);
  padding-inline: 0.75rem;
}

.ofe .btn--small { min-height: 2rem; padding: 0.25rem 0.875rem; font-size: 0.8125rem; }

/* A destructive action: the submit of a delete or destroy confirmation. */
.ofe .btn--danger {
  background: var(--md-sys-color-error);
  color: var(--md-sys-color-on-error);
}

/*
 * In a row of actions a button never makes the page wider: on a very
 * narrow screen (a phone at 200% zoom) its label wraps instead.
 */
.ofe .btn-row > .btn {
  max-width: 100%;
  white-space: normal;
  text-align: center;
  overflow-wrap: break-word;
}

.ofe .btn-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* ------------------------------------------------------------------ menu --- */

/*
 * A menu button and its menu (the WAI-ARIA menu button pattern), driven by
 * js/ofe/shell.js. The menu is fixed-positioned when open, so the card a table
 * sits in cannot clip it.
 *
 * Without JavaScript the button is not shown and the menu items are listed in
 * place, so every action stays reachable.
 */
.ofe .menu { display: inline-block; }

:root:not(.js) .ofe .menu__button { display: none; }

/* The disclosure caret, drawn in CSS so the button needs no extra markup. */
.ofe .menu__button::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 0.3125rem solid transparent;
  border-right: 0.3125rem solid transparent;
  border-top: 0.3125rem solid currentColor;
  transition: transform var(--md-sys-motion-duration-short2) var(--md-sys-motion-easing-standard);
}

.ofe .menu__button[aria-expanded="true"]::after { transform: rotate(180deg); }

.ofe .menu__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.js .ofe .menu__list {
  position: fixed;
  z-index: 50;
  /*
   * Sized by its items, capped by the viewport, and never by where it was
   * last placed, so shell.js measures the size it will have once moved.
   */
  width: max-content;
  min-width: min(12rem, calc(100vw - 1rem));
  max-width: calc(100vw - 1rem);
  max-height: calc(100vh - 1rem);
  overflow-y: auto;
  padding: 0.5rem 0;
  border-radius: var(--md-sys-shape-corner-extra-small);
  background: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-level2);
  text-align: left;
}

.ofe .menu__item {
  display: block;
  width: 100%;
  padding: 0.5rem 1rem;
  border: 0;
  background: none;
  color: var(--md-sys-color-on-surface);
  font: inherit;
  font-size: 0.875rem;
  line-height: 1.5;
  text-align: left;
  text-decoration: none;
  /* One line where the screen allows; on a very narrow one a long item wraps rather than scrolling the menu sideways. */
  overflow-wrap: break-word;
  cursor: pointer;
}

.ofe .menu__item:hover,
.ofe .menu__item:focus {
  background: var(--md-sys-color-surface-container-highest);
}

/* Inside the menu the focus ring sits inside the item, not over its neighbours. */
.ofe .menu__item:focus-visible { outline-offset: -3px; }

.ofe .menu__form { margin: 0; }

.ofe .menu__divider {
  height: 1px;
  margin: 0.5rem 0;
  background: var(--md-sys-color-outline-variant);
}

/* A divider with nothing after it, or two in a row, draws nothing. */
.ofe .menu__divider:last-child,
.ofe .menu__divider:first-child,
.ofe .menu__divider + .menu__divider { display: none; }

/* --------------------------------------------------------------- banners --- */

.ofe .banner {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.875rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-left-width: 4px;
  border-radius: var(--md-sys-shape-corner-medium);
  background: var(--md-sys-color-surface-container-low);
  font-size: 0.875rem;
}

.ofe .banner__text { margin: 0; color: var(--md-sys-color-on-surface); }

/* A banner with a heading line above its text. */
.ofe .banner__body {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
  overflow-wrap: break-word;
}

.ofe .banner__title {
  margin: 0;
  font-weight: 600;
  color: var(--md-sys-color-on-surface);
}

.ofe .banner--error .banner__title { color: var(--md-sys-color-on-error-container); }

.ofe .banner--info { border-left-color: var(--md-sys-color-primary); }
.ofe .banner--warn {
  border-left-color: var(--ofe-sys-color-warning);
  background: var(--ofe-sys-color-warning-container);
}
.ofe .banner--error {
  border-left-color: var(--md-sys-color-error);
  background: var(--md-sys-color-error-container);
}
.ofe .banner--error .banner__text { color: var(--md-sys-color-on-error-container); }
.ofe .banner--success {
  border-left-color: var(--ofe-sys-color-success);
  background: var(--ofe-sys-color-success-container);
}

/* ----------------------------------------------------------- status chips --- */

/*
 * Record status, from templatetags/status_extras.py. Every chip carries a
 * glyph as well as a colour, and "deleted" is visually distinct from every
 * live state.
 */
.ofe .pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.125rem 0.625rem;
  border: 1px solid transparent;
  border-radius: var(--md-sys-shape-corner-full);
  background: var(--md-sys-color-surface-container-highest);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}

.ofe .pill::before { content: "\2022"; }

.ofe .pill--ready {
  background: var(--ofe-sys-color-success-container);
  color: var(--ofe-sys-color-success);
}
.ofe .pill--ready::before { content: "\2713"; }

.ofe .pill--busy {
  background: var(--md-sys-color-primary-container);
  color: var(--md-sys-color-on-primary-container);
}
.ofe .pill--busy::before { content: "\25D0"; }

.ofe .pill--error {
  background: var(--md-sys-color-error-container);
  color: var(--md-sys-color-on-error-container);
}
.ofe .pill--error::before { content: "!"; }

/* Needs attention, but has not failed: an unknown cloud state, a registry awaiting setup. */
.ofe .pill--warn {
  background: var(--ofe-sys-color-warning-container);
  color: var(--ofe-sys-color-warning);
}
.ofe .pill--warn::before { content: "\25B2"; }

.ofe .pill--stopped {
  background: var(--md-sys-color-surface-container-highest);
  color: var(--md-sys-color-on-surface-variant);
}
.ofe .pill--stopped::before { content: "\25FC"; }

/* Deleted: outlined not filled, and greyed. */
.ofe .pill--deleted {
  border-color: var(--md-sys-color-outline);
  background: none;
  color: var(--md-sys-color-outline);
}
.ofe .pill--deleted::before { content: "\2715"; }

/*
 * A confirmation's table of what is affected or in the way (ofe/_confirm.html),
 * between the warning and the buttons.
 */
.ofe .confirm__details { margin: 0 0 1.5rem; }

/* ---------------------------------------------------------------- tables --- */

/*
 * Data tables sit in a card that never scrolls. The card is a size container,
 * so a table lays itself out for the room it actually has (rail open or
 * collapsed, any window size or zoom), not for the window width:
 *
 * - wide: an ordinary table; cells wrap their text when space runs short;
 * - narrow (`.table--reflow` only): each row becomes a stacked block whose
 *   cells flow into as many columns as fit, every cell under its column label.
 *
 * The open actions menu is fixed-positioned (js/ofe/shell.js), and the card
 * does not clip, so the menu is never cut off by the card.
 */
.ofe .table-card {
  container: table-card / inline-size;
  /*
   * Positioned, so absolutely positioned descendants (the screen-reader-only
   * text in captions and buttons) are placed against this box rather than
   * widening the page.
   */
  position: relative;
  min-width: 0;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-large);
  background: var(--md-sys-color-surface);
}

.ofe .table {
  --ofe-table-radius: calc(var(--md-sys-shape-corner-large) - 1px);
  width: 100%;
  /* Separate borders, so the corner cells can follow the card's rounding. */
  border-collapse: separate;
  border-spacing: 0;
  font-size: 0.875rem;
}

.ofe .table th,
.ofe .table td {
  padding: 0.6875rem clamp(0.5rem, 2cqi, 1rem);
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
  /*
   * Text wraps only at its natural break points (spaces, hyphens). Breaking
   * inside a word is the last resort, for an unbroken string that could not
   * fit otherwise.
   */
  overflow-wrap: break-word;
}

.ofe .table thead th {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--md-sys-color-on-surface-variant);
  background: var(--md-sys-color-surface-container-low);
  white-space: nowrap;
}

/* Where a page lets its column headings wrap, they wrap only between words. */
.ofe .main .table thead th { overflow-wrap: normal; }

.ofe .table thead th:first-child { border-top-left-radius: var(--ofe-table-radius); }
.ofe .table thead th:last-child { border-top-right-radius: var(--ofe-table-radius); }
.ofe .table tbody tr:last-child > :first-child { border-bottom-left-radius: var(--ofe-table-radius); }
.ofe .table tbody tr:last-child > :last-child { border-bottom-right-radius: var(--ofe-table-radius); }

.ofe .table tbody th { font-weight: 400; }

.ofe .table tbody tr:last-child > * { border-bottom: 0; }
.ofe .table tbody tr:hover > * { background: var(--md-sys-color-surface-container-low); }

.ofe .table__name { font-weight: 600; }
.ofe .table__actions { text-align: right; }
.ofe .table .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.8125rem; }

/* A secondary line under a cell's main text, such as the generated cluster id under its name. */
.ofe .table .table__sub {
  display: block;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--md-sys-color-on-surface-variant);
  /*
   * Caps how wide an id can make its column, so an unusually long id wraps
   * (at its hyphens, or as a last resort inside it) rather than widening the
   * table. Generated cluster ids (name plus eight hex digits) fit within it.
   */
  max-width: 30ch;
}

/* A status chip in a table may wrap between its words when space is short. */
.ofe .table .pill { white-space: normal; }

/* A deleted row is dimmed as well as chipped, and offers no live actions. */
.ofe .table tr[data-deleted] { opacity: 0.6; }
.ofe .table tr[data-deleted] .table__name { text-decoration: line-through; }

/*
 * Narrow card: stacked rows. The markup keeps its table elements, with
 * explicit ARIA table roles, because some browsers drop a table's semantics
 * once its display changes. The column headers stay in the accessibility
 * tree (visually hidden), and each cell shows its column label from
 * `data-label`, hidden from assistive technology so it is not read twice.
 */
@container table-card (max-width: 47.99rem) {
  .ofe .table--reflow,
  .ofe .table--reflow tbody {
    display: block;
  }

  .ofe .table--reflow thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .ofe .table--reflow tbody tr {
    display: grid;
    /* As many columns of cells as fit, each at least 10rem or the full row. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 10rem), 1fr));
    gap: 0.75rem 1rem;
    padding: 0.875rem clamp(0.25rem, 4cqi, 1rem);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .ofe .table--reflow tbody tr:last-child {
    border-bottom: 0;
    border-radius: 0 0 var(--ofe-table-radius) var(--ofe-table-radius);
  }

  .ofe .table--reflow tbody tr:first-child {
    border-radius: var(--ofe-table-radius) var(--ofe-table-radius) 0 0;
  }

  .ofe .table--reflow tbody tr:only-child { border-radius: var(--ofe-table-radius); }

  .ofe .table--reflow tbody tr:hover { background: var(--md-sys-color-surface-container-low); }
  .ofe .table--reflow tbody tr:hover > * { background: none; }

  .ofe .table--reflow tbody th,
  .ofe .table--reflow tbody td {
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    text-align: left;
  }

  /* The name heads the block and the actions close it, each across the row. */
  .ofe .table--reflow tbody th[scope="row"],
  .ofe .table--reflow tbody .table__actions {
    grid-column: 1 / -1;
  }

  .ofe .table--reflow tbody [data-label]::before {
    content: attr(data-label);
    content: attr(data-label) / "";
    display: block;
    margin-bottom: 0.125rem;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--md-sys-color-on-surface-variant);
  }

  .ofe .table--reflow tbody .table__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.5rem;
  }

  .ofe .table--reflow tbody .table__actions::before {
    flex-basis: 100%;
    margin-bottom: 0;
  }
}

/*
 * Tables with many columns, or with a long value such as an email beside
 * several others, switch to the stacked layout above at a wider card than
 * 48rem, rather than squeezing their values until words split:
 * .table-card--wide below 60rem and .table-card--wider below 68rem. The
 * rules are the ones above, repeated for each width.
 */
@container table-card (max-width: 59.99rem) {
  .ofe .table-card--wide > .table--reflow,
  .ofe .table-card--wide > .table--reflow tbody { display: block; }

  .ofe .table-card--wide > .table--reflow thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .ofe .table-card--wide > .table--reflow tbody tr {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 10rem), 1fr));
    gap: 0.75rem 1rem;
    padding: 0.875rem clamp(0.25rem, 4cqi, 1rem);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .ofe .table-card--wide > .table--reflow tbody tr:last-child {
    border-bottom: 0;
    border-radius: 0 0 var(--ofe-table-radius) var(--ofe-table-radius);
  }

  .ofe .table-card--wide > .table--reflow tbody tr:first-child { border-radius: var(--ofe-table-radius) var(--ofe-table-radius) 0 0; }

  .ofe .table-card--wide > .table--reflow tbody tr:only-child { border-radius: var(--ofe-table-radius); }

  .ofe .table-card--wide > .table--reflow tbody tr:hover { background: var(--md-sys-color-surface-container-low); }
  .ofe .table-card--wide > .table--reflow tbody tr:hover > * { background: none; }

  .ofe .table-card--wide > .table--reflow tbody th,
  .ofe .table-card--wide > .table--reflow tbody td {
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    text-align: left;
  }

  .ofe .table-card--wide > .table--reflow tbody th[scope="row"],
  .ofe .table-card--wide > .table--reflow tbody .table__actions { grid-column: 1 / -1; }

  .ofe .table-card--wide > .table--reflow tbody [data-label]::before {
    content: attr(data-label);
    content: attr(data-label) / "";
    display: block;
    margin-bottom: 0.125rem;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--md-sys-color-on-surface-variant);
  }

  .ofe .table-card--wide > .table--reflow tbody .table__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.5rem;
  }

  .ofe .table-card--wide > .table--reflow tbody .table__actions::before { flex-basis: 100%; margin-bottom: 0; }

  .ofe .table-card--wide > .table--reflow tbody .value-wrap::after { display: none; }
}

@container table-card (max-width: 67.99rem) {
  .ofe .table-card--wider > .table--reflow,
  .ofe .table-card--wider > .table--reflow tbody { display: block; }

  .ofe .table-card--wider > .table--reflow thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .ofe .table-card--wider > .table--reflow tbody tr {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 10rem), 1fr));
    gap: 0.75rem 1rem;
    padding: 0.875rem clamp(0.25rem, 4cqi, 1rem);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .ofe .table-card--wider > .table--reflow tbody tr:last-child {
    border-bottom: 0;
    border-radius: 0 0 var(--ofe-table-radius) var(--ofe-table-radius);
  }

  .ofe .table-card--wider > .table--reflow tbody tr:first-child { border-radius: var(--ofe-table-radius) var(--ofe-table-radius) 0 0; }

  .ofe .table-card--wider > .table--reflow tbody tr:only-child { border-radius: var(--ofe-table-radius); }

  .ofe .table-card--wider > .table--reflow tbody tr:hover { background: var(--md-sys-color-surface-container-low); }
  .ofe .table-card--wider > .table--reflow tbody tr:hover > * { background: none; }

  .ofe .table-card--wider > .table--reflow tbody th,
  .ofe .table-card--wider > .table--reflow tbody td {
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    text-align: left;
  }

  .ofe .table-card--wider > .table--reflow tbody th[scope="row"],
  .ofe .table-card--wider > .table--reflow tbody .table__actions { grid-column: 1 / -1; }

  .ofe .table-card--wider > .table--reflow tbody [data-label]::before {
    content: attr(data-label);
    content: attr(data-label) / "";
    display: block;
    margin-bottom: 0.125rem;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--md-sys-color-on-surface-variant);
  }

  .ofe .table-card--wider > .table--reflow tbody .table__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.5rem;
  }

  .ofe .table-card--wider > .table--reflow tbody .table__actions::before { flex-basis: 100%; margin-bottom: 0; }

  .ofe .table-card--wider > .table--reflow tbody .value-wrap::after { display: none; }
}

/*
 * A very narrow card (a phone at high zoom): tighter buttons, and a status
 * chip that flows as inline text, so even its longest word can wrap.
 */
@container table-card (max-width: 14rem) {
  .ofe :is(.table--reflow, .table--list) .btn--small { gap: 0.25rem; padding-inline: 0.25rem; }

  .ofe .table--reflow .pill {
    display: inline;
    padding-inline: 0.375rem;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
  }

  .ofe .table--reflow .pill::before { margin-right: 0.25rem; }
}

/* --------------------------------------------------------- record values --- */

/*
 * Values that come from records or from Google Cloud (names, emails, ids,
 * paths, URLs) can be long with no place to break, so the elements that
 * hold them may break inside a word: a record's name under the page title,
 * anything marked .value-wrap, and the headline and supporting text of a
 * list item. Headings, status chips, buttons and numbers never do.
 */
.ofe .main .page-head__sub,
.ofe .main .value-wrap { overflow-wrap: anywhere; }

/*
 * A table cell holding a .value-wrap keeps at least 10ch, so a short word
 * next to it is never squeezed. The 10ch is set by an empty block after the
 * value: a browser ignores min-width on a table cell, and the block makes
 * the column's smallest width at least that. It is dropped in the stacked
 * layouts.
 */
.ofe .main .table .value-wrap::after {
  content: "";
  display: block;
  width: 10ch;
}

@container table-card (max-width: 47.99rem) {
  .ofe .main .table--reflow tbody .value-wrap::after { display: none; }
}

/* ------------------------------------------------------------ list tables --- */

/*
 * Wide lists (filesystems, images, applications, jobs, containers) as
 * tables whose first column is a Material 3 two-line list item: the name
 * as the headline (body large, on-surface, underlined on hover when it is
 * a link) and the secondary values as supporting text (body medium,
 * on-surface-variant), each with a visually hidden label and separated by
 * middle dots. Two-line rows are at least 72dp tall, with 8dp steps of
 * padding. Each supporting value moves to the next line as a whole, and
 * breaks inside only when it is longer than the line. Below a 42rem card
 * the rows stack, as other tables do below 48rem. How the supporting values
 * flow (as text or as a wrapping row) is set by each page stylesheet.
 */
.ofe .table--list tbody > tr { height: 4.5rem; }

.ofe .table--list tbody > tr > * {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

.ofe .list-item__headline {
  display: block;
  font-size: 1rem;
  line-height: 1.5rem;
  font-weight: 400;
  letter-spacing: 0.03125rem;
  color: var(--md-sys-color-on-surface);
  overflow-wrap: anywhere;
}

.ofe a.list-item__headline { text-decoration: none; }
.ofe a.list-item__headline:hover { text-decoration: underline; }
.ofe .table tr[data-deleted] .list-item__headline { text-decoration: line-through; }

.ofe .list-item__supporting {
  margin-top: 0.125rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 400;
  letter-spacing: 0.015625rem;
  color: var(--md-sys-color-on-surface-variant);
  overflow-wrap: anywhere;
}

.ofe .list-item__supporting a { color: inherit; }

.ofe .list-item__supporting > * {
  position: relative;
  max-width: 100%;
}

/*
 * Middle dots between the supporting values; not read out. Each value but
 * the last keeps a 1rem right margin, and its dot is drawn in that margin at
 * the value's end, level with its last line. The dot belongs to the value's
 * box, so no line can start with it, however a long value wraps, and
 * nothing is clipped, so a focus outline is drawn in full.
 */
.ofe .list-item__supporting > :not(:last-child) {
  max-width: calc(100% - 1rem);
  margin-right: 1rem;
}

.ofe .list-item__supporting > :not(:last-child)::after {
  content: "\00B7";
  content: "\00B7" / "";
  position: absolute;
  right: -1rem;
  bottom: 0;
  width: 1rem;
  line-height: 1.25rem;
  text-align: center;
  pointer-events: none;
}

.ofe .list-item__mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.8125rem; }
.ofe .list-item__nowrap { white-space: nowrap; }

/* A date and its time each stay on one line. */
.ofe .table--list time > span { white-space: nowrap; }

/* Status chips and fixed vocabulary (an application's type) never break inside a word. */
.ofe .main .table .table__fixed { overflow-wrap: normal; min-width: 0; }

@container table-card (min-width: 42rem) {
  .ofe .table.table--list th.list-item { width: 50%; min-width: 16rem; }
}

@container table-card (max-width: 41.99rem) {
  .ofe .table--list,
  .ofe .table--list tbody { display: block; }

  .ofe .table--list thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .ofe .table--list tbody tr {
    display: grid;
    height: auto;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 10rem), 1fr));
    gap: 0.5rem 1rem;
    padding: 0.75rem clamp(0.25rem, 4cqi, 1rem);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .ofe .table--list tbody tr:last-child { border-bottom: 0; }
  .ofe .table--list tbody tr:hover > * { background: none; }

  .ofe .main .table--list tbody :is(th, td) {
    display: block;
    width: auto;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    text-align: left;
  }

  .ofe .table--list tbody th[scope="row"],
  .ofe .table--list tbody .table__actions { grid-column: 1 / -1; }

  .ofe .table--list tbody td[data-label]::before {
    content: attr(data-label);
    content: attr(data-label) / "";
    display: block;
    margin-bottom: 0.125rem;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--md-sys-color-on-surface-variant);
  }

  .ofe .table--list tbody .table__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
}

/*
 * The controls above a filtered table (js/ofe/table-filter.js): side by
 * side where they fit, stacked where they do not, with the count after them.
 */
.ofe .filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem 1rem;
  margin: 0 0 1rem;
}

.ofe .filter-bar > * { flex: 1 1 12rem; min-width: 0; margin-bottom: 0; }

.ofe .filter-bar__count {
  flex: 0 0 100%;
  margin: 0;
  font-size: 0.8125rem;
  color: var(--md-sys-color-on-surface-variant);
}

/* ------------------------------------------------------------ empty state --- */

.ofe .state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 2.5rem 1.5rem;
  text-align: center;
  color: var(--md-sys-color-on-surface-variant);
}

/* The same drawing the rail uses, at 40dp. */
.ofe .state__icon {
  display: grid;
  place-items: center;
  margin: 0 auto;
  color: var(--md-sys-color-on-surface-variant);
  opacity: 0.7;
}

.ofe .state__icon svg {
  display: block;
  width: 2.5rem;
  height: 2.5rem;
}

.ofe .state__title { margin: 0; font-size: 1rem; font-weight: 600; color: var(--md-sys-color-on-surface); }
.ofe .state__text { margin: 0; max-width: 46ch; font-size: 0.875rem; }

/* ---------------------------------------------------------------- detail --- */

/*
 * Detail pages: cards of key-value rows. A card is a size container, so its
 * rows stack label over value when the card itself is narrow, whatever the
 * window width.
 */
.ofe .card {
  container: card / inline-size;
  position: relative;
  min-width: 0;
  margin: 0 0 1rem;
  padding: clamp(1rem, 2.5vw, 1.5rem);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-large);
  background: var(--md-sys-color-surface);
}

.ofe .card > :last-child { margin-bottom: 0; }

.ofe .card__title {
  margin: 0 0 0.25rem;
  font-size: 1rem;
  font-weight: 600;
  overflow-wrap: break-word;
}

.ofe .card__sub {
  margin: 0 0 1rem;
  font-size: 0.8125rem;
  color: var(--md-sys-color-on-surface-variant);
  overflow-wrap: break-word;
}

/*
 * Key-value rows: a <dl class="kv"> of <dt> and <dd> pairs, optionally
 * wrapped in a <div> per pair. The label column takes what its longest
 * label needs, up to two fifths of the width; values wrap at their break
 * points.
 */
.ofe .kv {
  display: grid;
  grid-template-columns: fit-content(40%) minmax(0, 1fr);
  gap: 0.625rem 1.5rem;
  margin: 0 0 1rem;
  font-size: 0.875rem;
}

.ofe .kv > div { display: contents; }

.ofe .kv dt {
  min-width: 0;
  font-weight: 600;
  color: var(--md-sys-color-on-surface-variant);
  overflow-wrap: break-word;
}

.ofe .kv dd {
  min-width: 0;
  margin: 0;
  color: var(--md-sys-color-on-surface);
  overflow-wrap: break-word;
}

.ofe .kv--tight { gap: 0.25rem 1rem; font-size: 0.8125rem; }

/* A narrow card: each label above its value. */
@container card (max-width: 30rem) {
  .ofe .kv { grid-template-columns: minmax(0, 1fr); row-gap: 0.125rem; }
  .ofe .kv dd + dt,
  .ofe .kv > div + div > dt { margin-top: 0.625rem; }
}

/* Secondary text, and an identifier in it. */
.ofe .sub {
  font-size: 0.8125rem;
  color: var(--md-sys-color-on-surface-variant);
  overflow-wrap: break-word;
}

.ofe .sub--mono,
.ofe .kv .sub--mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* A rule between sections of a card: <hr class="divider">. */
.ofe .divider {
  height: 1px;
  margin: 1rem 0;
  border: 0;
  background: var(--md-sys-color-outline-variant);
}

/* ------------------------------------------------------------------- log --- */

/*
 * A log file (ofe/_log.html, js/ofe/log-stream.js). Long lines wrap rather
 * than scroll sideways; the log scrolls up and down within its own box.
 */
.ofe .log-viewer { min-width: 0; margin: 0 0 1rem; }

.ofe .log {
  min-width: 0;
  max-height: min(32rem, 70vh);
  margin: 0;
  padding: 0.75rem 1rem;
  overflow-x: hidden;
  overflow-y: auto;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  background: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.ofe .log__empty {
  margin: 0;
  padding: 1rem;
  border: 1px dashed var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 0.875rem;
}

.ofe .log__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-top: 0.5rem;
  font-size: 0.8125rem;
}

.ofe .log-details { min-width: 0; margin: 0 0 1rem; }

.ofe .log-details__summary {
  padding: 0.5rem 0;
  font-weight: 600;
  cursor: pointer;
}

/* ---------------------------------------------------------------- fields --- */

/*
 * Form controls. These style what Django renders by default (bare inputs,
 * selects and textareas, `ul.errorlist`, help text), the `.form-control`
 * class some templates hard-code, and the classless inputs of the sign-in
 * pages, so a page moved to the new shell needs no widget changes.
 *
 * Every element selector is wrapped in :where(), so these rules weigh no
 * more than `.ofe` alone and any component class (the app bar switch, say)
 * still wins over them.
 *
 * ofe/_field.html renders one field: `.field` holds the label, help text,
 * control and errors in a column.
 */
.ofe .field,
.ofe .form-group {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
  margin: 0 0 1.25rem;
}

.ofe .field__label,
.ofe .form-group > label {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--md-sys-color-on-surface);
  overflow-wrap: break-word;
}

/* The required marker is visual only: the control itself is `required`. */
.ofe .field__required {
  margin-left: 0.25rem;
  color: var(--md-sys-color-error);
  font-weight: 600;
}

/* Text-like inputs, selects, textareas and the hard-coded .form-control. */
.ofe :where(
  input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="file"],
    [type="submit"], [type="button"], [type="reset"], [type="image"],
    [type="range"], [type="color"]),
  select,
  textarea,
  .form-control
) {
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  min-height: 2.75rem;
  margin: 0;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--md-sys-color-outline);
  border-radius: var(--md-sys-shape-corner-extra-small);
  background: var(--md-sys-color-surface);
  color: var(--md-sys-color-on-surface);
  font: inherit;
  font-size: 0.9375rem;
  line-height: 1.5;
  transition: border-color var(--md-sys-motion-duration-short2) var(--md-sys-motion-easing-standard);
}

.ofe :where(select[multiple], select[size]) { min-height: 6rem; padding: 0.25rem; }

.ofe :where(textarea) {
  min-height: 6rem;
  resize: vertical;
}

.ofe :where(input, select, textarea, .form-control):hover:not(:disabled, [readonly]) {
  border-color: var(--md-sys-color-on-surface);
}

.ofe :where(input, select, textarea, .form-control):focus {
  border-color: var(--md-sys-color-primary);
}

/* A file input: the native button stays, styled to sit with the other controls. */
.ofe :where(input[type="file"]) {
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  padding: 0.375rem 0;
  font: inherit;
  font-size: 0.875rem;
  color: var(--md-sys-color-on-surface);
}

.ofe :where(input[type="file"])::file-selector-button {
  margin-right: 0.75rem;
  padding: 0.375rem 1rem;
  border: 1px solid var(--md-sys-color-outline);
  border-radius: var(--md-sys-shape-corner-full);
  background: none;
  color: var(--md-sys-color-primary);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* Checkboxes and radios stay native, in the primary colour. */
.ofe :where(input[type="checkbox"], input[type="radio"]) {
  flex: 0 0 auto;
  width: 1.125rem;
  height: 1.125rem;
  margin: 0.1875rem 0 0;
  accent-color: var(--md-sys-color-primary);
  cursor: pointer;
}

/* Disabled and read-only controls: still legible, plainly not editable. */
.ofe :where(input, select, textarea, .form-control):disabled {
  border-color: var(--md-sys-color-outline-variant);
  background: var(--md-sys-color-surface-container-highest);
  color: var(--md-sys-color-on-surface-variant);
  cursor: not-allowed;
}

.ofe :where(input, textarea, .form-control)[readonly] {
  border-style: dashed;
  background: var(--md-sys-color-surface-container-low);
}

/* An invalid control: a thicker error border, and the message below says why. */
.ofe :where(input, select, textarea, .form-control)[aria-invalid="true"] {
  border-color: var(--md-sys-color-error);
  box-shadow: inset 0 0 0 1px var(--md-sys-color-error);
}

/* Django's error list, for one field or the whole form. */
.ofe :where(ul.errorlist) {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.8125rem;
  color: var(--md-sys-color-error);
  overflow-wrap: break-word;
}

.ofe :where(ul.errorlist) > li + li { margin-top: 0.125rem; }

/* Help text: ofe/_field.html, Django's default `.helptext`, and `.form-text`. */
.ofe .field__help,
.ofe .helptext,
.ofe .form-text {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--md-sys-color-on-surface-variant);
  overflow-wrap: break-word;
}

/* A group of choices (RadioSelect and similar), as a fieldset and legend. */
.ofe .field--group {
  border: 0;
  padding: 0;
  margin-inline: 0;
}

.ofe .field--group > legend {
  padding: 0;
  margin-bottom: 0.375rem;
}

/* Each choice of a group: the box, then its text, on one line. */
.ofe .field--group label,
.ofe .form-check label {
  display: inline-flex;
  align-items: flex-start;
  gap: 0.5rem;
  min-width: 0;
  font-size: 0.875rem;
  overflow-wrap: break-word;
  cursor: pointer;
}

.ofe .field--group label + label,
.ofe .field--group div + div { margin-top: 0.375rem; }

/* A single checkbox: the box before its label, help and errors below. */
.ofe .field--check,
.ofe .form-check {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.25rem 0.5rem;
  min-width: 0;
  margin: 0 0 1.25rem;
}

.ofe .field--check > label { flex: 1 1 0; min-width: 0; font-weight: 600; }

.ofe .field--check > .field__help,
.ofe .field--check > .errorlist,
.ofe .form-check > .form-text,
.ofe .form-check > .errorlist {
  flex-basis: 100%;
  padding-left: 1.625rem;
}

/* A shown, not editable, value (ofe/_field.html with static=True). */
.ofe .field__static {
  margin: 0;
  overflow-wrap: break-word;
  color: var(--md-sys-color-on-surface);
}

/* A control with text or a button joined to it. */
.ofe .input-group {
  display: flex;
  align-items: stretch;
  min-width: 0;
}

.ofe .input-group > :where(input, select, textarea, .form-control) {
  flex: 1 1 auto;
  width: 1%;
  min-width: 0;
}

.ofe .input-group-text,
.ofe .input-group__text {
  display: flex;
  align-items: center;
  padding: 0 0.75rem;
  border: 1px solid var(--md-sys-color-outline);
  background: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 0.875rem;
  white-space: nowrap;
}

.ofe .input-group > .input-group-text:not(:first-child),
.ofe .input-group > .input-group__text:not(:first-child) { border-left: 0; }
.ofe .input-group > .input-group-text:not(:last-child),
.ofe .input-group > .input-group__text:not(:last-child) { border-right: 0; }
.ofe .input-group > * { border-radius: 0; }
.ofe .input-group > :first-child { border-radius: var(--md-sys-shape-corner-extra-small) 0 0 var(--md-sys-shape-corner-extra-small); }
.ofe .input-group > :last-child { border-radius: 0 var(--md-sys-shape-corner-extra-small) var(--md-sys-shape-corner-extra-small) 0; }
.ofe .input-group > :only-child { border-radius: var(--md-sys-shape-corner-extra-small); }

/* Controls inside table cells (formset rows): compact, and never wider than their cell. */
.ofe td :where(input, select, textarea, .form-control):not([type="checkbox"], [type="radio"]) {
  min-height: 2.25rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.875rem;
}

.ofe td :where(.field, .form-group, .field--check, .form-check) { margin-bottom: 0; }

/* --------------------------------------------------------------- tooltip --- */

/*
 * The "?" tooltip (ofe/_tip.html). A real <button>, so it is in the tab
 * order and opens on keyboard focus as well as on hover; a click or tap
 * toggles it (js/ofe/tip.js), which is what a touch screen has.
 *
 * A tooltip only explains. No error and no limit the user must know lives
 * only in here.
 */
.ofe .tip {
  position: relative;
  display: inline-flex;
  margin-left: 0.375rem;
  vertical-align: middle;
}

/*
 * A label with its tip beside it (ofe/_field.html with tip=). The tip is
 * the label's neighbour, not its child, so the label's text stays the
 * field's name alone.
 */
.ofe .field__label-row {
  display: flex;
  align-items: center;
  min-width: 0;
}

.ofe .field__label-row > label {
  min-width: 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--md-sys-color-on-surface);
  overflow-wrap: break-word;
}

.ofe .field__label-row > .tip { flex: none; }

/* A step's or a group's title with its tooltip beside it. */
.ofe .panel-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  margin-bottom: 0.25rem;
}

.ofe .panel-head > :where(h2, h3, h4) { margin: 0; }
.ofe .panel-head--group { margin: 1rem 0 0.75rem; }
.ofe .panel-head > .tip { flex: none; margin-left: 0; }

.ofe .field--check > .field__label-row {
  flex: 1 1 0;
}

.ofe .tip__button {
  display: grid;
  place-items: center;
  width: 1.125rem;
  height: 1.125rem;
  padding: 0;
  border: 1px solid var(--md-sys-color-outline);
  border-radius: 50%;
  background: none;
  color: var(--md-sys-color-on-surface-variant);
  font: inherit;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
  cursor: help;
}

.ofe .tip__button:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}

.ofe .tip__bubble {
  position: absolute;
  bottom: calc(100% + 0.5rem);
  left: 50%;
  z-index: 40;
  width: max-content;
  max-width: min(20rem, 70vw);
  padding: 0.5rem 0.75rem;
  border-radius: var(--md-sys-shape-corner-small);
  background: var(--md-sys-color-on-surface);
  color: var(--md-sys-color-surface);
  font-size: 0.8125rem;
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
  overflow-wrap: break-word;
  transform: translateX(-50%);
  /*
   * A closed bubble is display:none, not just invisible: an absolutely
   * positioned element still counts towards scrollable overflow while it is
   * only `visibility: hidden`, and a bubble near the right edge would give
   * the page a horizontal scrollbar with every tooltip closed. The fade-in
   * is kept through @starting-style.
   */
  display: none;
  opacity: 1;
  transition: opacity var(--md-sys-motion-duration-short2) var(--md-sys-motion-easing-standard);
}

/*
 * Open on hover, on keyboard focus, or when clicked open. Escape and a
 * second click set data-tip-dismissed (tip.js), which closes a bubble that
 * hover or focus alone would keep open.
 */
.ofe .tip:not([data-tip-dismissed]) .tip__button:hover + .tip__bubble,
.ofe .tip:not([data-tip-dismissed]) .tip__button:focus-visible + .tip__bubble,
.ofe .tip__button[aria-expanded="true"] + .tip__bubble {
  display: block;

  @starting-style {
    opacity: 0;
  }
}

/* Near the right edge: open leftwards so the bubble stays on screen. */
.ofe .tip--end .tip__bubble {
  left: auto;
  right: 0;
  transform: none;
}

/*
 * Narrow screens: the bubble spans its field instead of centring on the
 * button. A bubble centred on a button two-thirds across a 360px row runs
 * past the viewport; one that takes the field's width cannot, at any size.
 * It keeps its place in the flow (top and bottom auto) and is pushed just
 * below the button, so it also works for a tip that is not in a field.
 */
@media (max-width: 900px) {
  .ofe .tip { position: static; }

  .ofe .field,
  .ofe .field--check,
  .ofe .form-group,
  .ofe .tip-anchor { position: relative; }

  .ofe .tip__bubble,
  .ofe .tip--end .tip__bubble {
    top: auto;
    bottom: auto;
    left: 0;
    right: 0;
    width: auto;
    max-width: none;
    margin-top: 1.5rem;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ofe .tip__bubble { transition: none; }
}

/* ------------------------------------------------------------------ misc --- */

.ofe .helper {
  margin: 0 0 0.75rem;
  font-size: 0.8125rem;
  color: var(--md-sys-color-on-surface-variant);
}

/* Screen-reader-only text. */
.ofe .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
