/**
 * components.css — what the product adds on top of Intel's Spark.
 *
 * Every component that Spark has is Spark's own, used by its class names
 * (.spark-button, .spark-input, .spark-dropdown, .spark-modal, .spark-table,
 * .spark-tabs, .spark-message-banner, .spark-badge, .spark-tag, .spark-toast,
 * .spark-tooltip, .spark-stepper, .spark-progress-indicator, .spark-shimmer,
 * .spark-upload, .spark-code-snippet, .spark-breadcrumb, .spark-pagination,
 * .spark-popover/.spark-list). Their CSS is shared/spark.css, copied verbatim
 * from vendor/spark-css/ by scripts/sync-spark.py; their markup follows
 * docs/spark-reference/.
 *
 * This file holds two things only, both on Spark's tokens:
 *   1. What Spark leaves to the page for its own components — placement,
 *      showing and hiding, a missing default — each commented as such.
 *   2. The product's own parts that Spark has no component for: the top bar
 *      and admin side nav, page header, wizard layout, app cards and icon
 *      tiles, the screenshot slideshow and lightbox, the image picker, the
 *      rich-text licence editor, the document viewer, review summaries,
 *      empty states and the install flow.
 */

/* ============================== Top bar ============================== */

.topbar {
  height: var(--layout-topbar-height);
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: var(--z-topbar);
}

.topbar__inner {
  max-width: var(--layout-max-width);
  margin-inline: auto;
  padding-inline: var(--layout-side-padding);
  height: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-7);
}

/* Above a side nav (Intel Admin): the bar spans the full width instead of
   centring in the 1440px column, and the logo's left inset matches the
   side nav links' own (space-5), so logo and nav icons share one edge.
   The right side keeps the page gutter so the user switcher lines up
   with the content's right edge. */
.topbar--shell .topbar__inner {
  max-width: none;
  padding-inline: var(--space-5) var(--layout-side-padding);
}

.topbar__brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  white-space: nowrap;
}

.topbar__product {
  color: var(--color-text-ink);
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-body);
}

.nav {
  display: flex;
  align-items: center;
  gap: var(--space-7);
  margin-inline-start: var(--space-8);
  height: 100%;
}

.nav__link {
  display: flex;
  align-items: center;
  height: 100%;
  padding-inline: var(--space-1);
  color: var(--color-text-muted);
  font-weight: var(--font-weight-medium);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.nav__link:hover {
  color: var(--color-text-ink);
}

.nav__link[aria-current="page"] {
  color: var(--color-brand);
  border-bottom-color: var(--color-brand);
}

.topbar__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-inline-start: auto;
}

.avatar {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--color-neutral-bg);
  color: var(--color-neutral-text);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* A profile photo filling the circle. Decorative (alt="") — the person's
   name is always printed beside it. The avatar clips it to the circle. */
.avatar:has(.avatar__img) {
  overflow: hidden;
}

.avatar__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A person in a list row — a size up from the top bar's. */
.avatar--md {
  width: 40px;
  height: 40px;
}

/* No photo on the account: initials instead of the generic glyph. */
.avatar--initials {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.02em;
}

/* The person a whole page is about (User detail header). */
.avatar--xl {
  width: 112px;
  height: 112px;
}

.avatar--xl.avatar--initials {
  font-size: var(--spark-font-size-350);
}

/* ============================== Breadcrumb ==============================
   Spark's breadcrumb: .spark-breadcrumb-items of .spark-breadcrumb-item,
   Spark drawing the slanted separators, each crumb a Spark hyperlink and
   the current one Spark's disabled link in its is-current item. The <nav>
   around it is placed here, and sets the trail's type size, which Spark
   leaves to the page. */

.breadcrumb {
  padding-block: var(--space-5) var(--space-2);
  font-size: var(--spark-font-size-50);
}

/* When the breadcrumb sits above the title inside .page-header__stack,
   the stack's own gap does that spacing instead. */
.page-header__stack .breadcrumb {
  padding-block: 0;
}

/* ============================== Page header ============================== */

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-5) var(--space-7);
  flex-wrap: wrap;
}

/* A page with no breadcrumb above it (e.g. My apps) has to open its own
   top gap — the tight value above assumes the breadcrumb already did. */
.page-header--standalone {
  padding-block: var(--space-8) var(--space-6);
}

/* A long list's header (My apps): title, search and filter stay under the
   top bar while the list scrolls beneath, on the page's own background. */
.page-header--sticky {
  position: sticky;
  inset-block-start: var(--layout-topbar-height);
  z-index: calc(var(--z-topbar) - 1);
  background: var(--color-bg-page);
}

/* Once the list scrolls under it, the header keeps only a little air above
   and below (32/24 → 16/16), so more of the list shows. */
.page-header--sticky.is-stuck {
  padding-block: var(--space-4);
}

/* The layout switcher and any other select sitting with the header actions
   is a control, not a form field: it takes its content's width rather than
   stretching to the column. */
.page-header .spark-dropdown {
  flex-shrink: 0;
}

/* Breadcrumb and title stacked as one block, so .page-header__actions
   (e.g. "Save draft") centers against their combined height, not just
   the title line. */
.page-header__stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.page-header__title-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
}

.page-header__title-row--stacked {
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
}

.page-header__title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-page-title);
  font-weight: var(--font-weight-display);
  color: var(--color-text-ink);
}

.page-header__subtitle {
  display: block;
  color: var(--color-text-muted);
  padding-block-start: var(--space-2);
}

.page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

/* A class-level `display` outranks the UA [hidden] rule at equal
   specificity — the header's controls are hidden wholesale on a page with
   nothing to search or filter yet. */
.page-header__actions[hidden] {
  display: none;
}

/* The real Intel logo, captured from a live builders.intel.com render at
   4x and made transparent, rather than approximated as styled text. */
.topbar__logo-img {
  height: var(--icon-size-md);
  width: auto;
}

/* ============================== Buttons ==============================
   Buttons are Spark's own: .spark-button with a variant, a size and the
   focus-visible classes, markup straight from the Spark reference
   (docs/spark-reference/a-actions-badges-header.md §2). Only what the
   package leaves to the page is here, and it uses Spark's tokens. */

/* .spark-button sets display: flex, which outranks the UA [hidden] rule. */
.spark-button[hidden] {
  display: none;
}

/* Spark buttons are max-content wide by design; the reference says to set
   inline-size yourself for a full-width one. */
.button-block {
  inline-size: 100%;
}

/* A request in flight. Spark has no loading button, so the label is hidden
   and a ring in the button's own text colour turns in its place. */
.spark-button.is-loading {
  position: relative;
  pointer-events: none;
}

.spark-button.is-loading > * {
  visibility: hidden;
}

.spark-button.is-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  inline-size: 16px;
  block-size: 16px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-inline-end-color: transparent;
  animation: btn-spin 700ms linear infinite;
}

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

/* ============================== Wizard layout ============================== */

.wizard {
  display: flex;
  /* Both columns share a top and a bottom, so the rail stretches to the
     main pane's height rather than sitting at its own natural height. */
  align-items: stretch;
  gap: var(--space-5);
  padding-block-end: var(--space-9);
}

.wizard-rail {
  display: flex;
  flex-direction: column;
  width: var(--layout-rail-width);
  flex-shrink: 0;
  /* The rail's first line sits level with the main card's first line of
     content, which is the card's own top padding in from its edge. */
  padding-block-start: var(--space-5);
}

/* The rail leads with the count and the bar sits directly under it, so the
   head adds no gap of its own. */
.wizard-rail__head {
  padding-block-end: 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
}

.wizard-rail__count {
  font-size: var(--spark-font-size-75);
  font-weight: var(--font-weight-medium);
  color: var(--spark-color-theme-light-gray700);
}

.wizard-main {
  flex: 1;
  min-width: 0;
}

.wizard-card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  /* The footer's buttons end a little closer to the card's bottom edge
     than the side padding, which is why the bottom is a step tighter. */
  padding: var(--space-7) var(--space-7) var(--space-6);
  position: relative;
}

.wizard-card__title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-pane-title);
  font-weight: var(--font-weight-display);
  color: var(--color-text-ink);
  /* No top padding of its own — the card's own padding is the only top
     inset, so the title starts level with the card's left edge rather than
     stacking another gap on top of it. */
  padding-block: 0 var(--space-1);
  line-height: 1.15;
}

.wizard-card__subtitle {
  padding-block-start: var(--space-1);
  line-height: 1.45;
  color: var(--color-text-muted);
}

.wizard-card__section {
  padding-block-start: var(--space-7);
}

/* Tighter after a warning banner: a full section gap read as too much air
   between it and the control directly under it. */
.spark-message-banner-state-warning + .wizard-card__section {
  padding-block-start: var(--space-4);
}

.wizard-card__section-label {
  display: block;
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
  padding-block-end: var(--space-4);
}

/* A section label with a trailing action (e.g. "Check again"), instead of
   .wizard-card__section-label's own bottom margin doing the spacing. */
.wizard-card__section-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block-end: var(--space-3);
}

/* A numbered sub-task within one wizard step, e.g. "1 Upload your file" /
   "2 Push your images" — a lighter-weight reading of the rail's own
   numbered-step idea, scoped to one card instead of the whole wizard. */
.substep {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-block-end: var(--space-4);
}

.substep__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

/* A heading that carries its own action ("Check again"): one line, action to
   the right, rather than a second row and a gap before the table. */
.substep--with-action {
  justify-content: space-between;
}

/* Commands to copy, and the Compose file read back, are Spark's code
   snippet: single-line with Spark's copy-button slot, or multi-line
   (docs/spark-reference/d-progress-upload-layout-globals.md §6). The copy
   button stays visible; Spark's hover-reveal would hide it from touch. */

/* Spark's single-line snippet is one line tall and lets a long line run
   under its copy button. A registry command with the image path on the end
   wraps instead, clear of the button. */
.code-snippet--wrap {
  block-size: auto;
  min-block-size: var(--spark-button-m-block-size);
  padding-inline-end: calc(var(--spark-button-m-block-size) + var(--spark-space-8));
}

.code-snippet--wrap pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A few commands in one multi-line snippet (the registry push) take the
   height of their lines, not Spark's fixed 180px, with room to breathe on
   every side and the right kept clear for the labelled Copy button. */
.spark-code-snippet.spark-code-snippet-multiline.code-snippet--wrap {
  block-size: auto;
  padding: var(--space-5) calc(var(--space-9) * 2 + var(--space-5)) var(--space-5) var(--space-5);
  line-height: 1.75;
}

.code-snippet--wrap .spark-code-snippet-multiline-copy-icon {
  padding: var(--space-3);
}

/* The Compose file is read in full, so it takes the room it needs up to
   most of the viewport rather than Spark's fixed 180px. */
.compose-code {
  block-size: auto;
  max-block-size: 60vh;
}

.wizard-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block-start: var(--space-6);
  margin-block-start: var(--space-7);
  border-top: 1px solid var(--color-border);
}

.wizard-footer__start,
.wizard-footer__end {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* ============================== Form fields ==============================
   Labels, inputs, help and error text, checkboxes, the toggle, search and
   the dropdown are Spark's own classes, marked up as the Spark reference
   says (docs/spark-reference/b-form-controls.md). What is left here is how
   fields sit on a page, and the few things the package leaves to the page —
   each on Spark's own tokens. */

/* Two fields side by side in one column of a form. */
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spark-space-20);
}

/* Fields stack down a form with room between them. Spark's wrapper spaces
   its own label, control and text (a 4px gap); the space between one field
   and the next is the page's. */
.spark-fieldtext-wrapper {
  padding-block-end: var(--space-6);
  transition:
    background-color var(--motion-duration-base) var(--motion-easing),
    outline-color var(--motion-duration-base) var(--motion-easing);
}

/* A <fieldset> is the right element for a group of checkboxes (its
   <legend> names the group), but carries UA border, padding and margin. */
fieldset.spark-fieldtext-wrapper {
  border: 0;
  margin: 0;
  min-inline-size: 0;
  padding-block-start: 0;
  padding-inline: 0;
}

fieldset.spark-fieldtext-wrapper > legend {
  padding: 0;
  margin-block-end: var(--spark-space-4);
}

/* Briefly marks the field a "needs attention" link (Review and submit)
   scrolled to — see initHashFlag in components.js. Fades on its own via the
   transition above once JS removes the class. */
.field--flagged {
  outline: 2px solid var(--spark-color-daisy-shade1);
  outline-offset: var(--spark-space-12);
}

/* Spark's error text is display: flex, which outranks the UA [hidden]. */
.spark-fieldtext-wrapper-is-invalid[hidden] {
  display: none;
}

/* A section label a size up from an ordinary field label. */
.wizard-card__section-label {
  font-size: var(--spark-font-size-100);
}

/* A line under a section label saying what the section is for, set apart
   from the first field below it. */
.wizard-card__section-label + .spark-fieldtext-wrapper-description {
  display: block;
  margin-block-start: calc(-1 * var(--space-2));
  padding-block-end: var(--space-5);
}

/* How much of a capped field is used, under it at the end. */
.field-counter {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

/* Only when a restored draft is already over the cap. */
.field-counter--over {
  color: var(--spark-input-color-invalid);
}

/* Secondary guidance inside a label, e.g. "optional", in Spark's help
   colour and size. */
.field-hint {
  margin-inline-start: var(--spark-space-4);
  color: var(--spark-color-theme-light-gray800);
  font-size: var(--spark-font-size-50);
}

/* Spark's required asterisk is absolutely positioned and wider than the
   1ch its indicator reserves, so whatever follows it needs clearance. */
.spark-fieldlabel-required-indicator + * {
  margin-inline-start: var(--spark-space-12);
}

/* Spark has no textarea: its input sizes set a fixed block-size. The
   reference's advice is the outline input with no size class, and the
   height, type size and padding set here from Spark's own l-size tokens. */
.spark-input.textarea {
  inline-size: 100%;
  block-size: auto;
  min-block-size: 140px;
  padding-block: var(--spark-space-12);
  padding-inline: var(--spark-input-l-padding-outline-inline);
  font-size: var(--spark-input-l-font-size);
  line-height: var(--spark-line-height-text);
  resize: vertical;
}

.spark-input.textarea--md {
  min-block-size: 110px;
}

/* Spark's slot styling is written for its icon font, which is not in the
   package; an inline SVG in the slot is centred here instead. */
.spark-text-field-start-slot > svg {
  margin: auto;
}

/* A search box in a toolbar: grows to fill, up to a readable width. */
.search-field {
  flex: 1;
  max-inline-size: 420px;
}

/* ---- Dropdown ----
   The popover is moved to the theme root and fixed-positioned against its
   trigger (initSelect), so a scrolling table can't clip it. Spark's popover
   carries no position, shadow or overflow of its own and defines the
   tokens for them, which are used here. */
.dropdown-popover {
  position: fixed;
  z-index: var(--spark-popover-popover-z-index);
  inline-size: auto;
  max-block-size: none;
  box-shadow: var(--spark-popover-popover-box-shadow-x) var(--spark-popover-popover-box-shadow-y)
    var(--spark-popover-popover-box-shadow-blur-radius) var(--spark-popover-popover-shadow-color);
}

/* Spark's list has no selected style; a check marks the chosen option. */
.dropdown-popover .spark-list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spark-space-12);
}

.dropdown-option-check {
  display: inline-flex;
  visibility: hidden;
}

.spark-list-item[aria-selected="true"] .dropdown-option-check {
  visibility: visible;
}

.dropdown-popover .spark-list-item[hidden],
.dropdown-popover .spark-list[hidden] {
  display: none;
}

/* The muted prefix naming what a filter narrows ("Status  All"). */
.dropdown-prefix {
  color: var(--spark-color-theme-light-gray700);
  margin-inline-end: var(--spark-space-8);
}

/* A long list (every ISV) opens as a wider panel: search, list, footer. */
.dropdown-popover--panel {
  min-inline-size: 280px;
}

.dropdown-popover__search {
  padding: var(--spark-space-8);
}

.dropdown-popover__empty {
  padding: var(--spark-space-12);
  color: var(--spark-color-theme-light-gray700);
}

.dropdown-popover__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spark-space-12);
  padding: var(--spark-space-8) var(--spark-space-12);
  border-block-start: 1px solid var(--spark-color-theme-light-gray300);
}

.dropdown-popover__count {
  color: var(--spark-color-theme-light-gray700);
  font-size: var(--spark-font-size-50);
}

.dropdown-popover__actions {
  display: flex;
  align-items: center;
  gap: var(--spark-space-4);
}

/* Spark checkbox label text, and a second line under it. */
.choice-description {
  display: block;
}

/* No bottom margin: the section that follows carries its own top padding,
   and the two used to stack into twice the gap that was intended. */
/* Spark's divider, spaced into the form. */
.form-divider {
  border: 0;
  margin-block: var(--space-6) 0;
}

/* Square picker for an app icon: an empty dashed tile until an image is
   chosen. */
.icon-picker {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  width: 175px;
  height: 175px;
  /* The empty tile keeps clear air above the "+" and below the hint — its
     content was sitting flush against the dashed border. The filled states
     set their own padding. */
  padding: var(--space-6) var(--space-5);
  /* The tile is Spark's upload zone (.spark-upload-drag-and-drop): its
     border, surface and drag-over overlay are Spark's. */
  color: var(--spark-color-theme-light-gray700);
  cursor: pointer;
}

.icon-picker__plus {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1.5px solid currentColor;
  border-radius: var(--radius-full);
}

/* Fills its column instead of sitting at a fixed square size. */
/* Fills its column instead of the base 175px square. height: auto matters
   as much as the width: the base rule fixes the height, so a second row of
   thumbnails used to spill out of the box and over the section below it —
   the --grid / --tall / --short min-heights set the floor instead. */
.icon-picker--fill {
  display: flex;
  width: 100%;
  height: auto;
}

/* min-height, not height: the empty state is a fixed target to aim a file
   at, but once images are in the box it has to be able to grow. */
.icon-picker--tall {
  min-height: 300px;
}

.icon-picker__label {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

/* The two states of the tile stack and centre their own contents; the
   tile itself only centres them as a block. */
.icon-picker__empty,
.icon-picker__filled {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
}

/* A class-level `display` outranks the UA sheet's [hidden] rule, so the
   hidden state has to be restated at the same specificity. */
.icon-picker__empty[hidden],
.icon-picker__filled[hidden] {
  display: none;
}

/* The picker's own trigger. The whole tile is clickable as a convenience,
   but this button is what the keyboard reaches. */
.icon-picker__trigger {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  background: none;
  border: 0;
  padding: 0;
  color: inherit;
  cursor: pointer;
}

/* Accepted formats and the recommended size, stated in the box rather than
   left for the ISV to discover after a rejected upload. */
/* Inside a dashed tile, so it keeps its own inset — at 36ch the line was
   running into the border on a narrow column. */
.icon-picker__specs {
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
  text-align: center;
  max-width: 30ch;
  padding-inline: var(--space-5);
}

/* Filled state: the tile stops being a dashed target and shows what was
   uploaded. */
.icon-picker--filled {
  border-style: solid;
  border-color: var(--color-border);
  background: var(--color-bg-surface);
  cursor: default;
  justify-content: flex-start;
  padding: var(--space-5);
  gap: var(--space-4);
}

.icon-picker--filled:hover {
  border-color: var(--color-border);
  color: var(--color-text-muted);
}

/* Transient upload state between picking a file and it showing filled —
   a progress bar and a live percentage, not just a delay. */
.icon-picker--uploading {
  cursor: default;
}

.icon-picker__uploading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding-inline: var(--space-7);
}

.icon-picker__uploading-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.icon-picker__uploading-track {
  width: 100%;
  max-width: 220px;
}

.icon-picker__uploading-percent {
  font-size: var(--font-size-eyebrow);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.icon-picker__preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
}

/* Square, contained: an icon is never cropped or stretched to fit. */
.icon-picker__image {
  width: 140px;
  height: 140px;
  object-fit: contain;
  border: 1px solid var(--color-border);
  background: var(--color-bg-surface-sunken);
}

.icon-picker__filename {
  font-size: var(--font-size-meta);
  color: var(--color-text-ink);
  font-weight: var(--font-weight-bold);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.icon-picker__filesize {
  font-size: var(--font-size-eyebrow);
  color: var(--color-text-muted);
}

.icon-picker__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* Screenshots: a thumbnail grid that grows a row at a time. */
.icon-picker__thumbs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
  width: 100%;
}

.icon-picker__thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--color-border);
  background: var(--color-bg-surface-sunken);
  overflow: hidden;
}

/* The clickable part of a thumbnail — a real <button>, sibling to (not
   wrapping) .icon-picker__thumb-remove, since a button can't nest inside
   another button. Opens that screenshot in the lightbox. */
.icon-picker__thumb-open {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.icon-picker__thumb-open:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: -2px;
}

.icon-picker__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Matched as specifically as Spark's focus rules, which revert position on
   a mouse press: the button dropped out of the corner between press and
   release, so the click landed on the thumbnail and nothing was removed. */
.icon-picker__thumb .icon-picker__thumb-remove.spark-focus-visible.spark-focus-visible-self {
  position: absolute;
  inset-block-start: var(--space-1);
  inset-inline-end: var(--space-1);
}

/* ==================== Progress ====================
   Spark's progress indicator. Spark reads the fill from --percentage and
   defines no default; an empty bar is the default here, and each screen's
   script sets the real value on the indicator. */
.spark-progress-indicator {
  --percentage: 0%;
}

/* ==================== Key/value meta row ==================== */

.meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-7);
}

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

/* Assigned by the system on the first save — nothing to show before that.
   Written against .meta-row__value so it wins: the base rule is declared
   after this one, and at equal specificity the later rule would take it. */
.meta-row__value.meta-row__value--pending {
  font-weight: var(--font-weight-regular);
  color: var(--color-text-disabled);
}

.meta-row__value {
  color: var(--color-text-ink);
  font-weight: var(--font-weight-bold);
  margin-inline-start: var(--space-2);
}

/* ==================== Card ==================== */

.card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
}

/* ==================== Review and submit ==================== */

/* Two-column layout: the review cards next to a narrower sidebar. */
/* The screenshots band sits above the two-column review, so it carries its
   own spacing — inside .review-layout__main the column's gap did that job. */
.wizard-card__section--band {
  margin-block: var(--space-6) var(--space-6);
}

.review-layout {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--space-6);
  align-items: start;
  margin-block-start: var(--space-6);
}

/* Class-level `display` beats the UA [hidden] rule — User detail hides
   the whole layout when the account isn't found. */
.review-layout[hidden] {
  display: none;
}

.review-layout__main,
.review-layout__side {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* The column's own gap already spaces a divider from its neighbours —
   the divider's margin on top of it doubled the space.
   .pe is the theme scope every page wrapper carries; it outlived
   pixel-exact.css, the file it was named after. See themeRoot() in
   components.js, which mounts overlays into it. */
.pe .review-layout__main > .form-divider {
  margin-block: 0;
}

/* Keeps the side column (e.g. an admin's Approve / Reject) in view while
   a long submission scrolls past it. */
.review-layout__side--sticky {
  position: sticky;
  inset-block-start: calc(var(--layout-topbar-height) + var(--space-6));
}

/* Larger screenshot slots where they're the page's own media, not a
   thumbnail inside a summary card. */
.review-card__shots--lg .review-card__shot {
  width: 168px;
  height: 104px;
}

/* Machine values — image names, digests, paths, variable names — set in
   the mono face so they read as exact strings, not prose. */
.kv-row__value--mono,
.upload-row__meta--mono {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-meta);
}

/* A table cell reads at body size. Measured off Intel's own product, whose
   table cells run ~14px while its pills run 12 — so the cell does not
   follow --font-size-meta down to Spark's 0.75rem step. */
.table__mono {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-body);
}

.review-card__title-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* ============================== Doc viewer ============================== */

/* A read-only preview opened in place, inside the section it belongs to —
   the validation PDF beside the device claim it backs, a README, a
   deployment file. Header (file, position, actions), then a sunken stage
   holding one "page". Keeping it in the page rather than a dialog lets the
   reviewer compare it with the rest of the section. */
.doc-viewer {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  overflow: hidden;
}

.doc-viewer[hidden] {
  display: none;
}

.doc-viewer__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-block-end: 1px solid var(--color-border);
  background: var(--color-bg-surface);
}

.doc-viewer__title {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  min-width: 0;
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

.doc-viewer__meta {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-regular);
  color: var(--color-text-muted);
}

.doc-viewer__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.doc-viewer__stage {
  max-height: 560px;
  overflow: auto;
  padding: var(--space-6);
  background: var(--color-neutral-bg);
}

/* Code previews run edge to edge on the dark code surface instead. */
.doc-viewer__stage--code {
  padding: 0;
  background: var(--color-text-ink);
}

.doc-viewer__page {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: 600px;
  margin-inline: auto;
  padding: var(--space-7);
  background: var(--color-bg-surface);
  box-shadow: var(--shadow-md);
}

.doc-viewer__page-title {
  font-size: var(--spark-font-size-100);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

.doc-viewer__page-meta {
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

/* Body text the prototype doesn't reproduce — drawn as grey bars, the
   same way a PDF thumbnail reads before its text is legible. */
.doc-viewer__line {
  display: block;
  height: 8px;
  background: var(--color-neutral-bg);
}

.doc-viewer__line--short {
  width: 62%;
}

.doc-viewer__page .spark-table {
  font-size: var(--spark-font-size-50);
}

/* One collapsible-looking summary of a completed wizard step, built on
   .card. Not actually collapsible — "Edit" just returns to that step. */
.review-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--color-border);
}

.review-card__header-start {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.review-card__title {
  /* inherit: a real heading element (h2) here must not pick up the UA's
     heading size — the card title reads at body size, bold. */
  font-size: inherit;
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

.review-card__body {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* The body's own padding already closes the card under a form field. */
.review-card__body > .spark-fieldtext-wrapper:last-child {
  padding-block-end: 0;
}

/* A plain fact trailing a row of chips, e.g. "Added 12 Aug 2026". */
.chip-row__note {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
  margin-inline-start: var(--space-2);
}

/* ==================== Tabs ====================
   Spark's ghost tabs: .spark-tabs-tab with the label in .spark-button-content
   and .spark-tabs-active on the open one
   (docs/spark-reference/c-overlays-feedback-data.md §9). Account types are
   links to sibling pages (aria-current); a panel's tabs are a tablist.
   Spark gives a tab no vertical padding, and the strip no space below. */
.page-tabs {
  margin-block-end: var(--space-6);
}

.page-tabs .spark-tabs-tab {
  padding-block: var(--spark-space-12);
  text-decoration: none;
}

/* A cell value that opens its own record (Audit's Object column). */
.table__link {
  outline-offset: -4px;
}

/* Complete/incomplete marker, reused at two sizes: the card header (24px)
   and the sidebar completeness list (18px). */
.review-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-none);
  flex-shrink: 0;
}

.review-status--sm {
  width: 18px;
  height: 18px;
}

.review-status--complete {
  background: var(--color-success-bg);
  color: var(--color-success-text);
}

/* A labelled value on its own line, e.g. "Licence file · Not uploaded". */
.kv-row {
  display: grid;
  grid-template-columns: minmax(140px, 200px) 1fr;
  gap: var(--space-4);
}

/* Same class-beats-[hidden] issue fixed for .spark-message-banner and
   .icon-picker__empty — a toggled .kv-row needs this restated too. */
.kv-row[hidden] {
  display: none;
}

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

/* Something required that hasn't been provided yet (Review and submit):
   the reason in place of the value, the sidebar list, and the card marker. */
.kv-row__value--missing {
  color: var(--color-danger-soft);
  font-weight: var(--font-weight-bold);
}

.completeness-list__item--missing {
  color: var(--color-danger-soft);
  font-weight: var(--font-weight-bold);
}

.review-status--missing {
  background: var(--color-danger-bg);
  color: var(--color-danger-soft);
}

/* Optional but warned (Review and submit): documentation left out. It
   does not block Submit, so it reads as a warning, not as missing. */
.kv-row__value--warning,
.completeness-list__item--warning {
  color: var(--color-warning-text);
  font-weight: var(--font-weight-bold);
}

.review-status--warning {
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}

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

/* Blank screenshot slots for a review preview that has no real image
   assets behind it — an abstract swatch, not a placeholder graphic. */
.review-card__shots {
  display: flex;
  gap: var(--space-3);
}

.review-card__shot {
  width: 120px;
  height: 80px;
  border-radius: var(--radius-none);
  background: linear-gradient(135deg, var(--color-bg-surface-sunken), var(--color-border));
  /* No-op for the placeholder <span>; once this is a real uploaded
     screenshot rendered as an <img>, this is what keeps it filling the
     same fixed tile instead of stretching or overflowing. */
  object-fit: cover;
}

/* The stand-in for .app-icon-tile until the publisher uploads an icon: a
   quiet grey tile with an outlined box (an application is a container),
   so a draft reads as "no icon yet" rather than as a block of brand blue.
   The hairline keeps the pale tile from vanishing on a white card. The
   glyph's grey is Spark's gray600, written into the SVG itself. */
.app-icon-tile--placeholder {
  background-color: var(--color-bg-page);
  background-image: url("assets/logos/placeholder.svg");
  box-shadow: inset 0 0 0 1px var(--color-border);
}

/* One of six pale tints, set by components.js from the app's name so the
   same app is the same colour everywhere. Grey above until it runs. */
.app-icon-tile--placeholder[data-tint="1"] { background-color: var(--color-icon-tint-1); }
.app-icon-tile--placeholder[data-tint="2"] { background-color: var(--color-icon-tint-2); }
.app-icon-tile--placeholder[data-tint="3"] { background-color: var(--color-icon-tint-3); }
.app-icon-tile--placeholder[data-tint="4"] { background-color: var(--color-icon-tint-4); }
.app-icon-tile--placeholder[data-tint="5"] { background-color: var(--color-icon-tint-5); }
.app-icon-tile--placeholder[data-tint="6"] { background-color: var(--color-icon-tint-6); }
/* The SI application page shown inside a modal (Review and submit): the
   page's own background around the page's own sections, which keep their
   own classes so the preview cannot drift from the real page. */
.listing-preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding: var(--space-6);
  background: var(--color-bg-page);
}

.listing-preview .review-layout {
  margin-block-start: 0;
}

/* No logo yet: the tile keeps its size and place and says what is missing. */
.listing-preview__gap-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-3);
  border: 1px dashed var(--color-border);
  background: var(--color-bg-surface-sunken);
  font-size: var(--font-size-meta);
  text-align: center;
}

/* Shown because an installer sees it; it does nothing in a preview. */
.listing-preview__inert {
  cursor: not-allowed;
}

.catalog-preview {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.catalog-preview__card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.catalog-preview__caption {
  color: var(--color-text-muted);
  font-size: var(--font-size-meta);
}

.completeness-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.completeness-list__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* A status is Spark's secondary tag with its shadow line in the state's
   colour (OEC.statusBadge). Spark's shadow draws its own inset frame on top
   of the tag's border, which reads as a doubled line, so the border goes and
   Spark's frame stays. */
.spark-tag-secondary.spark-tag-shadow {
  border: 0;
}

/* A tag that is a link (the industry on an application page) keeps the
   tag's look; the UA underline is not Spark's. */
a.spark-tag {
  text-decoration: none;
}

/* A row of read-only Spark tags (Review and submit: vertical, version,
   validated devices). */
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* A short dated log, e.g. Review and submit's sidebar "History". */
.history-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.history-list__item {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

/* One hairline threading the dots into a single timeline: it starts under
   a dot and runs into the next, bridging the list's own gap. Centred on the
   8px dot (4px), drawn behind it so a coloured dot still reads as the
   marker. */
.history-list__item:not(:last-child)::before {
  content: "";
  position: absolute;
  inset-inline-start: 3.5px;
  inset-block: 18px calc(-1 * var(--space-4));
  width: 1px;
  background: var(--color-border);
}

.history-list__dot {
  width: 8px;
  height: 8px;
  margin-block-start: 6px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-brand);
}

/* Dot tone follows the event's meaning: done/listed, rejected, waiting. */
.history-list__dot--success {
  background: var(--color-success-text);
}

.history-list__dot--danger {
  background: var(--color-danger-text);
}

.history-list__dot--warning {
  background: var(--color-warning-text);
}

.history-list__dot--neutral {
  background: var(--color-border-strong);
}

.history-list__label {
  display: block;
  color: var(--color-text-ink);
  font-weight: var(--font-weight-medium);
}

.history-list__date {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--font-size-eyebrow);
}

/* ==================== Section eyebrow ==================== */

.section-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* Sentence case needs a size step up from the all-caps eyebrow scale to
     still read as a heading — caps compensate for smallness, this can't. */
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
  padding-block-end: var(--space-4);
}

/* ==================== App icon tile ==================== */

/* A small set of flat placeholder colours distinguishing one app's icon
   from another's — decorative only, not brand-meaningful. Genuinely
   rounded, unlike the sharp-cornered UI elsewhere: these read as app
   icons (like a home-screen icon), the same exemption already given to
   the avatar and the role/status pills. */
.app-icon-tile {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  border-radius: var(--radius-app-icon);
}

/* Each app carries its publisher's logo mark. The mark is a background
   image rather than an <img> because the tile is decorative — the app's
   name is right beside it — so it stays out of the accessibility tree.

   The four background colours below are NOT theme values and are not in
   tokens.css: each is a 10% tint sampled from that one publisher's own
   artwork, so the mark sits on its own hue instead of on an unrelated
   block of colour. They belong to the PNG beside them and change only when
   that PNG does. A ninth publisher brings a ninth pair. */
.app-icon-tile {
  background-repeat: no-repeat;
  background-position: center;
  background-size: 38px 38px;
}

/* Sampled from quickserve.png. Per-asset, not a theme value. */
.app-icon-tile--quickserve {
  background-color: #feeee9;
  background-image: url("assets/logos/quickserve.png");
}

/* Sampled from safesight.png. Per-asset, not a theme value. */
.app-icon-tile--safesight {
  background-color: #f6f0ff;
  background-image: url("assets/logos/safesight.png");
}

/* Sampled from freshmart.png. Per-asset, not a theme value. */
.app-icon-tile--freshmart {
  background-color: #e9f6f0;
  background-image: url("assets/logos/freshmart.png");
}

/* Sampled from envirosense.png. Per-asset, not a theme value. */
.app-icon-tile--envirosense {
  background-color: #eaf6f4;
  background-image: url("assets/logos/envirosense.png");
}

/* Compact tile for a table row. */
.app-icon-tile--sm {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-app-icon-sm);
  background-size: 26px 26px;
}

/* Application detail's header is the one place the icon is the primary
   visual, not a small identifier next to a name — same tile, larger. */
.app-icon-tile--lg {
  width: 96px;
  height: 96px;
  border-radius: var(--radius-app-icon-lg);
  background-size: 64px 64px;
}

/* ==================== Rejection (publisher's app page) ==================== */

/* Every rejection in one shape: who and when, a summary, the numbered
   issues (found / to do / which step), then what resubmitting means. The
   red edge is the attention card's on My apps, so the two read as one
   thing; everything inside stays ink and grey so the issues are readable. */
.rejection {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin-block-start: var(--space-5);
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-inline-start: 4px solid var(--color-danger-text);
  background: var(--color-bg-surface);
}

.rejection[hidden] {
  display: none;
}

.rejection__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.rejection__icon {
  display: inline-flex;
  color: var(--color-danger-text);
  padding-block-start: 2px;
}

.rejection__title {
  font-size: var(--spark-font-size-100);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

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

.rejection__block {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.rejection__label {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
}

.rejection__issues {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
}

.rejection__issue {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-4);
  padding: var(--space-5);
}

.rejection__issue + .rejection__issue {
  border-block-start: 1px solid var(--color-border);
}

.rejection__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 24px;
  block-size: 24px;
  border-radius: 50%;
  background: var(--color-bg-page);
  border: 1px solid var(--color-border);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

.rejection__issue-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

.rejection__issue-title {
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

/* Label beside its text on a wide screen, above it on a narrow one. */
.rejection__facts {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: var(--space-2) var(--space-4);
}

.rejection__facts dt {
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
  padding-block-start: 2px;
}

.rejection__facts dd {
  color: var(--color-text-ink);
}

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

.rejection__next {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-inline-start: var(--space-5);
  list-style: disc;
  color: var(--color-text-ink);
}

@media (max-width: 640px) {
  .rejection__facts {
    grid-template-columns: 1fr;
  }

  .rejection__facts dd + dt {
    padding-block-start: var(--space-2);
  }
}

/* ==================== App identity (icon + name/id/version) ==================== */

.app-identity-row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.app-identity-row__name {
  display: block;
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

/* Name with a flag beside it (e.g. a "Resubmission" chip). */
.app-identity-row__title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}

.app-identity-row__meta {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--font-size-meta);
  padding-block-start: var(--space-1);
}

/* ==================== Application detail header ==================== */

.app-detail-head {
  display: flex;
  gap: var(--space-5);
}

.app-detail-head__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.app-detail-head__title-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.app-detail-head__name {
  font-family: var(--font-family-display);
  font-size: var(--font-size-page-title);
  color: var(--color-text-ink);
}

/* The state belongs to the app, not to the actions — it reads beside the
   name, and leaves the action area to actions alone. Baseline-ish alignment
   keeps the pill sitting with the name rather than floating above it. */
.app-detail-head__name-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.app-detail-head__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-shrink: 0;
}

/* ==================== Attention card ==================== */

.attention-card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-inline-start: 4px solid var(--color-danger-text);
  border-radius: var(--radius-none);
  padding: var(--space-6);
  margin-block-end: var(--space-8);
}

/* The card names itself — there's no section heading above it any more. */
.attention-card__label {
  display: block;
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
  padding-block-end: var(--space-4);
}

.attention-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
}

.attention-card__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* The notice opens the app it is about, the same stretched-link pattern the
   list rows use. Its own buttons and the reason's link sit above that
   overlay so they stay separately clickable. */
.attention-card--linked {
  position: relative;
}

.attention-card--linked:hover {
  background: var(--color-bg-surface-sunken);
}

.attention-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* The status sits beside the name, as it does in an application's own
   page header. */
.app-identity-row__name--with-status {
  display: flex;
  align-items: center;
  gap: var(--spark-space-12);
}

.app-identity-row__name--with-status .spark-tag {
  font-weight: var(--font-weight-regular);
}

.attention-card__actions,
.attention-card .spark-message-banner a {
  position: relative;
  z-index: 1;
}

/* A rejection reason typed as numbered points keeps its line breaks. */
.reason-text {
  white-space: pre-line;
}

/* ==================== App list (card rows, no table) ==================== */

.app-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* Same fix as .app-card's own — without it the list keeps its box when
   the page hides it, and the gap below it survives. */
.app-list[hidden] {
  display: none;
}

.app-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  padding: var(--space-5) var(--space-6);
  text-decoration: none;
}

/* A class-level `display` outranks the UA sheet's [hidden] rule (same
   fix already applied to .icon-picker__empty, .kv-row) — the
   filter/search JS sets card.hidden, and without this it's a no-op: the
   attribute lands on the element but the card stays visibly flex. */
.app-card[hidden] {
  display: none;
}

.app-card__end {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.app-card__status-block {
  text-align: end;
}

.app-card__status-meta {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--font-size-meta);
  padding-block-start: var(--space-2);
}

/* Rows joined inside one .card with a rule between them, instead of
   separate cards — for a queue read top to bottom (Pending reviews).
   Each row draws its rule on top and is pulled up 1px so the container
   clips the first one: rows get hidden by filters, so neither
   :first-child nor :last-child is a stable place to drop a rule. */
.app-list--stacked {
  gap: 0;
  overflow: hidden;
}

.app-list--stacked[hidden] {
  display: none;
}

.app-list--stacked .app-card {
  margin-block-start: -1px;
  border: 0;
  border-block-start: 1px solid var(--color-border);
  border-radius: var(--radius-none);
}

/* A lead line above the meta line — e.g. "Submitted 5 days ago" in ink
   and weight, with the exact date small and muted under it: how long
   something has waited is what gets read first. */
.app-card__status-lead {
  display: block;
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

.app-card__status-lead + .app-card__status-meta {
  padding-block-start: var(--space-1);
}

/* The whole card opens its detail page. The row's own link (Review /
   View) stretches an invisible layer over the card, so each row still
   has exactly one link for keyboard and screen reader users, and no
   click handler sits on the <li>. Anything interactive added to a linked
   card later needs position: relative to sit above that layer. */
.app-card--linked {
  position: relative;
  cursor: pointer;
}

.app-card--linked:hover {
  background: var(--color-bg-surface-sunken);
}

.app-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* A listing taken out of the catalog (Listed apps, Suspended). The logo
   greys out, as the push steps do when they no longer apply, and a note
   under the name says what suspension means for the app right now. */
.app-card--suspended .app-icon-tile {
  filter: grayscale(1);
  opacity: 0.45;
}

.app-card__note {
  display: block;
  color: var(--color-warning-text);
  font-size: var(--font-size-meta);
  padding-block-start: var(--space-1);
}

/* ==================== Menus ====================
   The ⋮ row menus and the user-type switcher open a Spark popover holding
   a Spark list (docs/spark-reference/c-overlays-feedback-data.md §7,
   b-form-controls.md §15); a rule between groups is Spark's divider.
   Spark's popover has no position and its list items are written for
   <li>, so what is here places the popover against its trigger and lets a
   <button> or <a> be a list item. */
/* Sized to its trigger, so the menu's edge lines up with the button's. */
.menu-wrap {
  position: relative;
  display: inline-flex;
}

.menu {
  position: absolute;
  inset-block-start: calc(100% + var(--spark-space-4));
  inset-inline-end: 0;
  z-index: var(--spark-popover-popover-z-index);
  inline-size: max-content;
  min-inline-size: 160px;
  max-block-size: none;
  box-shadow: var(--spark-popover-popover-box-shadow-x) var(--spark-popover-popover-box-shadow-y)
    var(--spark-popover-popover-box-shadow-blur-radius) var(--spark-popover-popover-shadow-color);
}

.menu[hidden] {
  display: none;
}

.menu__item {
  inline-size: 100%;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: start;
  text-decoration: none;
  gap: var(--spark-space-12);
}

/* Spark's list has no danger item; a destructive one reads in Spark's
   alert colour, and sits last, after a divider. */
.spark-list .menu__item--danger {
  color: var(--spark-color-coral-shade1);
}

/* An action that exists but can't apply to this row (deactivating your own
   admin account): Spark's disabled list item. */
.menu__item:disabled {
  opacity: 0.4;
  background: none;
  cursor: default;
}

.menu__separator {
  display: block;
  block-size: 1px;
  margin: var(--spark-space-4) 0;
  border: 0;
}

/* A non-interactive heading over a group of items. */
.menu__label {
  display: block;
  padding: var(--spark-space-8) var(--spark-space-8) var(--spark-space-4);
  font-size: var(--spark-font-size-25);
  font-weight: var(--font-weight-medium);
  color: var(--spark-color-theme-light-gray700);
}

/* A pick-one item (role="menuitemradio") carries the same check as the
   dropdown's chosen option, shown only on the checked item. */
.menu__item[aria-checked="true"] .dropdown-option-check {
  visibility: visible;
}

/* ==================== User switcher ==================== */

/* The signed-in user's name, their user type, and avatar as one button
   that opens a .menu to switch user type (ISV / Intel Admin). Sits in the
   top bar on every page, ISV and Intel Admin alike. */
.user-switcher {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-none);
  color: var(--color-text-ink);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.user-switcher:hover {
  background: var(--color-bg-surface-sunken);
}

.user-switcher[aria-expanded="true"] {
  background: var(--color-brand-subtle-bg);
}

.user-switcher__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: var(--line-height-tight);
}

.user-switcher__name {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

.user-switcher__role {
  font-size: var(--font-size-eyebrow);
  color: var(--color-text-muted);
}

.user-switcher__chevron {
  display: flex;
  flex-shrink: 0;
  color: var(--color-text-muted);
}

/* In the top bar the text sits left of the avatar, so it aligns toward it. */
.topbar__actions .user-switcher__text {
  align-items: flex-end;
}

/* ==================== App shell with side nav (Intel Admin) ==================== */

/* Intel Admin's frame: the shared top bar, then a fixed-width navigation
   column beside the page. The column fills the height under the top bar
   and is sticky so the user block at its foot stays reachable on a long
   page. */
.app-shell {
  display: grid;
  grid-template-columns: var(--layout-sidenav-width) minmax(0, 1fr);
  min-height: calc(100vh - var(--layout-topbar-height));
}

.app-shell__main {
  min-width: 0;
}

.side-nav {
  position: sticky;
  inset-block-start: var(--layout-topbar-height);
  height: calc(100vh - var(--layout-topbar-height));
  display: flex;
  flex-direction: column;
  padding-block-start: var(--space-4);
  background: var(--color-bg-surface);
  border-inline-end: 1px solid var(--color-border);
  /* Under the top bar, so its switcher menu draws over this column. */
  z-index: calc(var(--z-topbar) - 1);
}

.side-nav__list {
  display: flex;
  flex-direction: column;
}

/* A real <a> for a built destination, a <button> for one that isn't yet
   (shows a toast) — same split as the wizard rail's step links. */
.side-nav__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-5);
  background: transparent;
  border: 0;
  color: var(--color-text-ink);
  font: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}

.side-nav__link:hover {
  background: var(--color-bg-surface-sunken);
  color: var(--color-text-ink);
}

.side-nav__link:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: -2px;
}

/* Current page: the wizard rail's current-step treatment — tinted row
   with a brand bar at the column's edge. */
.side-nav__link[aria-current="page"] {
  background: var(--color-brand-subtle-bg);
  color: var(--color-brand);
  font-weight: var(--font-weight-bold);
}

.side-nav__link[aria-current="page"]::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 3px;
  background: var(--color-brand);
}

.side-nav__icon {
  display: flex;
  flex-shrink: 0;
  color: var(--color-text-muted);
}

.side-nav__link[aria-current="page"] .side-nav__icon {
  color: var(--color-brand);
}

/* Items waiting on the admin — amber, the warning/pending tone. */
/* The waiting count is Spark's small circular badge, pushed to the end. */
.side-nav__count {
  margin-inline-start: auto;
}

.side-nav__count[hidden] {
  display: none;
}

/* ==================== Toolbar: search + filter ==================== */

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5);
  border-bottom: 1px solid var(--color-border);
}

.filter-select {
  width: auto;
  min-width: 160px;
}

/* Sized to its label + value rather than a fixed floor — for a row of
   several prefixed filters ("ISV All", "Submitted Any date"). */
.filter-select--fit {
  min-width: 0;
}

/* A toolbar sitting directly on the page rather than as a card's header
   row: no padding or rule of its own. */
.toolbar--bare {
  padding: 0 0 var(--space-5);
  border-bottom: 0;
}

.toolbar__group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* Search sharing the filter row (Catalog): its own natural width, pushed
   to the row's right end, after the filters. */
.toolbar__group .search-field {
  flex: none;
  margin-inline-start: auto;
}

/* A filter row that stays under the top bar while the list scrolls
   (Catalog). Once stuck it takes a little air above, so the controls
   don't sit against the top bar's edge. */
.toolbar--sticky {
  position: sticky;
  inset-block-start: var(--layout-topbar-height);
  z-index: calc(var(--z-topbar) - 1);
  background: var(--color-bg-page);
}

/* The row's one group takes the full width, so it wraps only when the
   controls really run out of room, not at its first measured width. */
.toolbar--sticky .toolbar__group {
  flex: 1;
}

.toolbar--sticky.is-stuck {
  padding-block: var(--space-4);
}

/* ==================== Pagination ====================
   Spark's pagination: the summary in .spark-pagination-control, the page
   buttons (Spark buttons, the current page the action variant, since Spark
   has no current-page style of its own) in .spark-pagination-list. */

.pagination {
  padding: var(--space-4) var(--space-5);
  border-block-start: 1px solid var(--spark-color-theme-light-gray300);
}

.pagination[hidden] {
  display: none;
}

.pagination-summary {
  color: var(--spark-color-theme-light-gray700);
  font-size: var(--spark-font-size-50);
}

/* ==================== Empty state ==================== */

/* A list with nothing in it: an icon tile, what that means, and (where
   there's something to do about it) one action. The tile's tone carries
   the meaning — success when "nothing" is good news (queue cleared),
   neutral when a filter produced it. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: calc(var(--space-9) + var(--space-5)) var(--space-6);
  text-align: center;
}

.empty-state[hidden] {
  display: none;
}

.empty-state__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 80px;
  height: 80px;
  margin-block-end: var(--space-4);
  border-radius: var(--radius-none);
  background: var(--color-success-bg);
  color: var(--color-success-text);
}

.empty-state__icon--neutral {
  background: var(--color-neutral-bg);
  color: var(--color-neutral-text);
}

.empty-state__title {
  font-family: var(--font-family-display);
  font-size: var(--spark-font-size-200);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
}

.empty-state__body {
  max-width: 52ch;
  color: var(--color-text-muted);
}

.empty-state__body + .spark-button {
  margin-block-start: var(--space-4);
}

/* ==================== Table ==================== */

.table-wrap {
  overflow-x: auto;
}

/* A little breathing room before a button that adds another row to the
   table directly above it (e.g. "Add variable", "Add files") — without
   it, the button reads as glued to the last row. */
.table-wrap + .spark-button {
  margin-block-start: var(--space-4);
}

/* Trailing status/value column (e.g. "In registry") — same
   right-alignment call as .table__col-remove, just not a remove button. */
/* ---- Tables ----
   Tables are Spark's: .spark-table with a size, and Spark's classes on the
   head, head cells, body, rows and cells
   (docs/spark-reference/c-overlays-feedback-data.md §8). A table whose rows
   open a detail page is Spark's minimal variant, the one that has a row
   hover. What Spark leaves to the page: */

/* Spark gives every row a pointer cursor; only a linked row acts on click. */
.spark-table:not(.table--linked) .spark-table-row {
  cursor: default;
}

/* A row that opens its detail page from anywhere on it: the name is a
   real button whose ::after covers the row. The row's kebab menu comes
   later in the DOM and is already positioned, so it stays on top and
   keeps working on its own. */
.table--linked .spark-table-row {
  position: relative;
}

.table__row-link {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}

.table__row-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.table__empty {
  text-align: center;
  color: var(--color-text-muted);
  padding: var(--space-9);
}

/* ==================== Field info icon ==================== */

/* A small inline hint next to a field label. The copy lives in
   data-tooltip (read via attr() into ::after) rather than the native
   title attribute — title's browser tooltip is slow (~1s delay), tiny,
   and unstyled, which read as "the tooltip doesn't work" even though the
   text was there. This shows instantly and matches the rest of the UI. */
/* Spark's tooltip, on an info icon beside a label. Spark supplies the
   bubble and its tip and leaves showing, hiding and placement to the page
   (docs/spark-reference/c-overlays-feedback-data.md §4): it shows while the
   icon is hovered or focused, centred above it at Spark's own gap. */
.field-info {
  display: inline-flex;
  vertical-align: middle;
  color: var(--spark-color-theme-light-gray700);
  cursor: help;
}

.field-info > .spark-tooltip {
  inset: auto auto calc(100% + var(--spark-tooltip-m-tooltip-gap)) 50%;
  transform: translateX(-50%);
  /* Absolute inside a 16px icon, it would shrink to one word; it takes its
     text's width instead, up to Spark's own 30ch cap. */
  inline-size: max-content;
}

/* Spark's top-left placement, for an icon near the right edge: Spark puts
   the tip near the bubble's end, so the bubble ends just past the icon and
   opens leftwards, its tip over the icon's centre. */
.field-info > .spark-tooltip.spark-tooltip-left {
  inset-inline-start: auto;
  inset-inline-end: calc(50% - var(--spark-tooltip-m-gap-size) + var(--spark-tooltip-tip-border-width));
  transform: none;
}

.field-info:not(:hover):not(:focus-visible) > .spark-tooltip {
  visibility: hidden;
}

/* Spark's label is max-content wide, so a sentence runs past the bubble's
   own 30ch cap instead of wrapping inside it. */
.field-info .spark-tooltip-label {
  inline-size: auto;
}

/* ==================== Tag input ==================== */

/* Spark has no tag input. This one is Spark's outline input as the box
   (border, hover, focus ring from spark-focus-visible-within) holding Spark
   tags and a bare text field; it grows as tags wrap. */
.spark-input.tag-input {
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spark-space-8);
  inline-size: 100%;
  min-block-size: var(--spark-input-l-block-size);
  padding: var(--spark-space-4) var(--spark-space-12);
  cursor: text;
}

.tag-input__field {
  flex: 1;
  min-inline-size: 120px;
  border: none;
  background: transparent;
  font: inherit;
  font-size: var(--spark-input-l-font-size);
  color: var(--spark-input-color);
  padding: var(--spark-space-4);
}

.tag-input__field:focus {
  outline: none;
}

/* ==================== Wizard rail ====================
   The rail is a card holding the count, Spark's progress indicator and
   Spark's vertical stepper (docs/spark-reference/d-progress-upload-layout-
   globals.md §1): each step a linked circle (Spark's hyperlink variant) and
   a title, the open step active, finished ones visited, ones left with
   something missing invalid. */
.wizard-rail--card {
  background: var(--spark-color-white);
  border: 1px solid var(--spark-color-theme-light-gray300);
  padding: var(--space-6);
}

/* The count and its bar read as one unit, set apart from the steps. */
.wizard-rail--card .spark-progress-indicator {
  margin-block: var(--spark-space-4) var(--spark-space-20);
}

/* Spark shifts the text down by half a circle, which suits a title with a
   description under it. These steps are a title only, so it is centred on
   the circle's own 40px box instead. */
.wizard-stepper.spark-stepper .spark-stepper-orientation-vertical .spark-stepper-step-text-container {
  transform: none;
  justify-content: center;
  min-block-size: var(--spark-stepper-m-icon-active-size);
}

/* Spark's vertical step keeps 48px beneath every step, the last included. */
.wizard-stepper .spark-stepper-step:last-child {
  padding-block-end: 0;
}

/* The title repeats the circle's link for a pointer, and reads as the
   title, not as a link. */
.step-title-link {
  color: inherit;
  text-decoration: none;
}

.step-title-link:hover {
  text-decoration: underline;
}

/* Two Spark checkboxes side by side (a user's roles). */
.choice-group {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

/* ============================== Upload ==============================
   Drop zones are Spark's upload: a sized .spark-upload root holding
   .spark-upload-drag-and-drop and its body, with Spark's .can-drop overlay
   drawn while a file is over it (setDropState). Spark sizes its root at a
   fixed 356px for a sidebar; here the zone spans the form column, and the
   root is the overlay's positioning box, which Spark leaves unset. */
.upload-zone {
  position: relative;
  inline-size: 100%;
  min-inline-size: 0;
  min-block-size: 0;
}

.upload-zone .spark-upload-drag-and-drop {
  position: relative;
  margin-block-end: 0;
}

/* Spark sizes its drag-over overlay for its own fixed 356px zone; here it
   covers whatever size the zone is. */
.upload-zone .spark-upload-drag-and-drop .can-drop {
  inset: 0;
  min-block-size: 0;
  min-inline-size: 0;
  margin: 0;
  pointer-events: none;
}

/* The body is shown as a column here, which would outrank [hidden] once the
   image picker swaps its empty state for a preview. */
.upload-zone .spark-upload-drag-and-drop-body[hidden] {
  display: none;
}

.upload-zone .spark-upload-drag-and-drop-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spark-space-8);
  text-align: center;
}

/* Spark's zone is display: flex, which outranks the UA [hidden] rule. */
.dropzone[hidden] {
  display: none;
}

.dropzone__hint {
  color: var(--spark-color-theme-light-gray700);
  font-size: var(--spark-font-size-50);
}

.dropzone__input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* The zone's own action: the plus mark over its label, in Spark's type. */
.dropzone__trigger {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spark-space-8);
  padding: 0;
  border: 0;
  background: none;
  color: var(--spark-color-theme-light-gray900);
  font: inherit;
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

/* Spark sets every span in the zone body to display: block, which would
   uncentre the plus inside its ring. */
.spark-upload .spark-upload-drag-and-drop .spark-upload-drag-and-drop-body .icon-picker__plus {
  display: inline-flex;
  color: var(--spark-color-classic-blue);
}

/* ============================== Upload row ============================== */

.upload-row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  margin-block-start: var(--space-2);
}

/* Same class-beats-[hidden] fix as .app-card — a toggled
   row (e.g. README.md when none was supplied) needs this to disappear. */
.upload-row[hidden] {
  display: none;
}

/* Ink, not muted: this is the file itself, the most identifying thing in
   the row, and it sits beside the filename rather than under it. */
.upload-row__icon {
  color: var(--color-text-ink);
  flex-shrink: 0;
}

/* The mark reads at the same size as the two lines of text beside it. A
   real rule rather than the svg's intrinsic size, which varies per icon. */
.upload-row__icon svg {
  width: var(--icon-size-lg);
  height: var(--icon-size-lg);
}

.upload-row__body {
  flex: 1;
  min-width: 0;
}

/* The name and its meta line stack rather than running inline, so a long
   filename has the full width of the row. */
.upload-row__name {
  display: block;
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.upload-row__meta {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--font-size-meta);
}

/* A plain trailing figure (file size, mainly) on a read-only .upload-row
   — no action, so no button, just right-aligned muted text. */
.upload-row__size {
  flex-shrink: 0;
  color: var(--color-text-muted);
  font-size: var(--font-size-eyebrow);
}

/* The file mark on an uploaded deployment file, in place of the plain
   outline icon used elsewhere in the row. */
.upload-row__icon--file {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-height-m);
  height: var(--control-height-m);
  background: var(--color-brand-subtle-bg);
  color: var(--color-brand);
}

/* The row's own trailing action sits a little clear of the size beside it. */
.upload-row .spark-button-icon-only {
  margin-inline-start: var(--space-2);
}

/* ============================== Message banner ==============================
   Every notice — the callouts inside a surface, the refusals on the sign-in
   page, a rejection reason — is Spark's outlined message banner, built as
   the reference says (docs/spark-reference/c-overlays-feedback-data.md §5;
   OEC.bannerClasses for ones rendered from script). Only what the package
   leaves to the page is here. */

/* Spark sets display: flex, which outranks the UA [hidden] rule. */
.spark-message-banner[hidden] {
  display: none;
}

/* The icon column is written for Spark's icon font, absent from the
   package, and Spark pads it for that font. With an inline SVG the padding
   pushed the icon below the first line and away from the text, so the
   column is one line tall instead and the icon centres on that line —
   the description's, or the title's when the banner has one. Same
   specificity as Spark's rule, so it holds. */
.spark-message-banner .spark-message-banner-grid-column.spark-message-banner-grid-column-icon-column {
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  padding: 0;
  block-size: var(--spark-message-banner-message-content-message-description-line-height);
}

.spark-message-banner:has(.spark-message-banner-grid-column-message-column-content-message-title)
  .spark-message-banner-grid-column.spark-message-banner-grid-column-icon-column {
  padding-block-start: var(--spark-message-banner-message-content-message-title-margin-top);
  block-size: calc(
    var(--spark-message-banner-message-content-message-title-margin-top) +
      var(--spark-message-banner-message-content-message-title-line-height)
  );
}

/* Spark gives the button row no gap. */
.spark-message-banner .push-step__action {
  gap: var(--spark-space-8);
}

.spark-message-banner-grid-column-message-column {
  min-inline-size: 0;
}

/* Spark spaces every row from the next, the last one included, which made
   a banner's bottom padding look larger than its top. */
.spark-message-banner .spark-message-banner-grid-row:last-child {
  margin-bottom: 0;
}

/* A list of changes inside a banner (a resubmission, a metadata-only
   update), one per line. */
.banner-list {
  display: flex;
  flex-direction: column;
  gap: var(--spark-space-4);
}

/* ============================== Loading ==============================
   A region that is loading shimmers in place: Spark's shimmer list, one
   item per expected row (showSectionLoading). Spark's shimmer root is a
   fixed 364px; here it fills the region it stands in for. */
.section-shimmer {
  inline-size: 100%;
  padding: var(--space-5);
}

.section-shimmer .spark-shimmer-skeleton-list-item:last-child {
  margin-block-end: 0;
}

/* ============================== Toast ==============================
   Spark's toast. The page's region is the .spark-toast root (placed top
   centre by Spark's own placement class) and each toast is a
   .spark-toast-content inside it. What Spark leaves to the page: */

/* A layer above dialogs — Spark sets no z-index on the toast. */
.toast-region {
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--spark-space-8);
}

/* Several toasts stack; Spark's root is written for one. */
.toast-region:empty {
  display: none;
}

/* Spark's default toast is transparent with only a border, which reads
   through to whatever it floats over. It sits on Spark's white surface. */
.spark-toast-content-state-default {
  background: var(--spark-color-white);
}

.spark-toast-content {
  display: flex;
  align-items: center;
  gap: var(--spark-space-12);
}

/* ============================== Modal ==============================
   Every dialog is a native <dialog> opened with showModal(), so focus
   trapping, Escape, the top layer and returning focus to the trigger come
   from the platform. The <dialog> itself carries Spark's modal classes and
   holds Spark's grid of header, content and footer
   (docs/spark-reference/c-overlays-feedback-data.md §1). Spark's wrapper
   and backdrop elements are what the native element already provides, so
   only the UA's own dialog box is reset here, and its backdrop takes
   Spark's backdrop colour. */
dialog.spark-modal {
  padding: 0;
  border: 0;
  color: var(--spark-modal-text-color);
  max-block-size: calc(100dvh - 2 * var(--spark-space-32));
  overflow-y: auto;
}

dialog.spark-modal::backdrop {
  background: var(--spark-modal-backdrop-background-color);
}

/* A modal the size of the page behind it, less a margin so it plainly
   sits on top — for reading a whole page, like the listing preview. The
   dialog scrolls, its header stays, and the page underneath stays put. */
dialog.spark-modal.modal--full {
  inline-size: calc(100vw - 2 * var(--space-8));
  block-size: calc(100dvh - 2 * var(--space-8));
  max-inline-size: none;
  max-block-size: none;
  overscroll-behavior: contain;
}

.modal--full .spark-modal-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  padding-block: var(--space-4);
  background: var(--color-bg-surface);
}

html:has(dialog.modal--full[open]) {
  overflow: hidden;
}

/* Spark gives the footer no layout: actions sit at the end, in a row. */
.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--spark-space-12);
}

/* Close on the left, the action on the right: the two are not a pair of
   equal choices, and Retry should not sit next to Cancel. */
.modal-footer--split {
  justify-content: space-between;
}

/* Content whose parts (a message, a conditional warning, a field, a note)
   come and go, spaced by one gap. */
.modal-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.modal-stack .spark-fieldtext-wrapper {
  padding-block-end: 0;
}

/* Spark's content area has no max height; a long one scrolls inside. */
.modal-scroll {
  max-block-size: min(58vh, 520px);
  overflow-y: auto;
}

.dialog__section {
  padding-block-end: var(--space-6);
}

.dialog__section:last-child {
  padding-block-end: 0;
}

/* A notice's per-item list: what was rejected, and why, one line each —
   the name in ink, the reason beside it in muted text. */
.notice-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.notice-list[hidden] {
  display: none;
}

.notice-list__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-danger-bg);
  border-inline-start: 3px solid var(--color-danger-text);
  border-radius: var(--radius-none);
}

.notice-list__name {
  font-weight: var(--font-weight-bold);
  color: var(--color-text-ink);
  overflow-wrap: anywhere;
}

/* The reason is the error itself — it reads in red, not as quiet metadata,
   and states the value found against the one required. */
.notice-list__reason {
  color: var(--color-danger-text);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-medium);
}

.dialog__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  color: var(--color-text-muted);
  padding-inline-start: var(--space-5);
  list-style: disc;
}

/* A quiet consequence line under the form, e.g. "Version 1.4.1 stays live". */
.dialog__note {
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

.dialog__note[hidden] {
  display: none;
}

/* ==================== Lightbox (screenshot preview) ==================== */

/* Spark has no lightbox. This one is Spark's large modal holding a dark
   stage (so a light screenshot doesn't bleed into the white chrome) and
   prev/next paging. */

.lightbox__stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 10;
  background: var(--color-text-ink);
  overflow: hidden;
}

.lightbox__image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.lightbox__nav {
  position: absolute;
  inset-block-start: 50%;
  transform: translateY(-50%);
}

.lightbox__nav--prev {
  inset-inline-start: var(--space-4);
}

.lightbox__nav--next {
  inset-inline-end: var(--space-4);
}

.lightbox__footer {
  justify-content: space-between;
}

/* "Add more" moved up next to the close button — separate from
   Remove/Remove all, which stay together in the footer. */
.lightbox__header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

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

/* ============================== Wizard: four steps (15 Sep 2026) ============================== */

/* The status tag sits right beside the application name. */
.page-header__title-row .spark-badge {
  flex-shrink: 0;
}

/* Card title and subtitle on the left, the values the system assigns
   (publisher, application ID) on the right of the same line — read-only
   facts, so they sit apart from the fields rather than stacking under
   the subtitle as a third muted line. */
.wizard-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-6);
  flex-wrap: wrap;
}

/* Application details leads with its title and the system-assigned identity
   under it — no subtitle, no right-aligned meta column. */
.wizard-card__head--stacked {
  display: block;
}

.wizard-card__head--stacked .meta-row {
  padding-block-start: var(--space-2);
  font-size: var(--font-size-meta);
}

/* A section's opening label: Identity, Descriptions, Screenshots, Licence.
   Sentence case, so it takes the size step up that .section-eyebrow takes —
   caps compensate for smallness, sentence case can't. */
.wizard-card__section-label--eyebrow {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
  padding-block-end: var(--space-4);
}

/* The reason beside a disabled footer action — never disabled silently. */
.wizard-footer__hint {
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

/* Logo column | hairline | fields, held to a readable width. */
.identity-split {
  display: grid;
  grid-template-columns: 280px 1px minmax(0, 1fr);
  gap: var(--space-7);
  align-items: start;
}

.identity-split__rule {
  align-self: stretch;
  background: var(--color-border);
}

/* A control held narrower than its card, e.g. the device picker. */
.field__control--narrow {
  max-width: 520px;
}

/* Screenshots: a short empty strip, then a four-up grid ending in an add tile. */
.icon-picker--grid {
  min-height: 120px;
}

.icon-picker--grid.icon-picker--filled {
  align-items: stretch;
  padding: var(--space-4);
}

.icon-picker--grid .icon-picker__thumbs {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* 16:9, the shape a catalog screenshot is shown in. */
.icon-picker--grid .icon-picker__thumb {
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-none);
}

.icon-picker--grid .icon-picker__thumb-remove {
  opacity: 0;
  transition: opacity var(--motion-duration-fast) var(--motion-easing);
}

.icon-picker--grid .icon-picker__thumb:hover .icon-picker__thumb-remove,
.icon-picker--grid .icon-picker__thumb:focus-within .icon-picker__thumb-remove {
  opacity: 1;
}

/* ---- Licence editor ---- */

/* .rte — bold, italic, underline, heading, lists and link. Nothing more. */
/* No Spark equivalent: Spark has no rich-text editor or accordion. This
   stays ours, restyled with Spark tokens. See docs/spark-adoption.md. */
.rte {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
}

.rte:focus-within {
  border-color: var(--color-brand);
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: -1px;
}

.rte__toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--color-bg-surface-sunken);
  border-block-end: 1px solid var(--color-border);
}

.rte__separator {
  width: 1px;
  height: 18px;
  margin-inline: var(--space-1);
  background: var(--color-border);
}

.rte__link-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-block-end: 1px solid var(--color-border);
}

.rte__link-bar[hidden] {
  display: none;
}

.rte__link-bar .spark-input {
  flex: 1;
  min-inline-size: 200px;
}

.rte__link-bar .spark-fieldtext-wrapper-is-invalid {
  inline-size: 100%;
}

.rte__body {
  min-height: 150px;
  padding: var(--space-4);
  font-size: var(--spark-font-size-75);
  line-height: 1.65;
  color: var(--color-text-ink);
  outline: none;
}

.rte__body[data-empty]::before {
  content: attr(data-placeholder);
  color: var(--color-text-muted);
  pointer-events: none;
}

/* A long block held to a readable height until the reader asks for the
   rest — licence terms, mainly. The fade says there is more below without
   needing a scrollbar inside the page. */
.readmore {
  position: relative;
  max-height: 280px;
  overflow: hidden;
}

.readmore--open {
  max-height: none;
}

.readmore:not(.readmore--open)::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  height: 72px;
  background: linear-gradient(to bottom, transparent, var(--color-bg-surface));
  pointer-events: none;
}

/* Licence terms read back as text (review, app detail, admin review),
   indented behind a rule with the formatting kept. */
.licence-text {
  border-inline-start: 3px solid var(--color-border);
  padding-inline-start: var(--space-4);
  line-height: 1.65;
  color: var(--color-text-ink);
}

.rte__body h3,
.licence-text h3 {
  font-size: var(--spark-font-size-100);
  font-weight: var(--font-weight-medium);
  margin-block: 0 var(--space-2);
}

.rte__body p,
.licence-text p,
.rte__body div,
.licence-text div {
  margin-block: 0 var(--space-2);
}

.rte__body ul,
.rte__body ol,
.licence-text ul,
.licence-text ol {
  margin-block: 0 var(--space-2);
  padding-inline-start: var(--space-6);
}

.rte__body ul,
.licence-text ul {
  list-style: disc;
}

.rte__body ol,
.licence-text ol {
  list-style: decimal;
}

.rte__body a,
.licence-text a {
  color: var(--color-brand);
  text-decoration: underline;
}

/* ---- Selections shown as removable chips under their picker ---- */

.selection-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
}

.selection-chips:empty {
  display: none;
}

/* ---- File lists ---- */

.upload-row__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

.upload-list + .spark-button {
  margin-block-start: var(--space-3);
}

/* ---- Step 3: numbered sections ---- */

.substep__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* A flex child won't shrink below its content by default, so a wide table
   or command block pushed the whole page sideways instead of scrolling
   inside its own frame. */
.substep__body > * {
  min-width: 0;
}

/* Class-level display beats the UA [hidden] rule — step 3's sections
   hide their content until there's a Compose file. */
.substep__body[hidden] {
  display: none;
}

/* A bordered table that doesn't clip what floats above a cell (the
   digest tooltip) the way an overflow-scrolling .table-wrap would. */
/* Spark: the table carries no radius of its own — table-rows-select is 0
   and nothing else in table/properties.css rounds anything. */
.table-frame {
  border-radius: var(--radius-none);
}

.table-frame {
  max-width: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
}

/* Image references and digests are long, unbreakable strings. They wrap
   inside their cell rather than widening the table past the card — the
   frame deliberately doesn't scroll here, so the digest tooltip above the
   first row isn't clipped. */
.table-frame .spark-table {
  inline-size: 100%;
}

.table-frame .table__mono {
  overflow-wrap: anywhere;
}

/* A cell whose value can run long (a URL, a path): it truncates to one
   line with the full value on hover, rather than wrapping and making every
   row in the table taller. */
.table__truncate {
  max-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Short, fixed headings that must not wrap onto a second line. */
.table__col-nowrap {
  white-space: nowrap;
}

.table__col-w-90 { width: 90px; }
.table__col-w-260 { width: 260px; }
.table__col-w-110 { width: 110px; }
.table__col-w-150 { width: 150px; }
.table__col-w-160 { width: 160px; }
.table__col-w-190 { width: 190px; }
.table__col-w-220 { width: 220px; }

.table__cell-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* An image digest: truncated, the full value on hover, a copy pill after it. */
.digest {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.digest__value {
  position: relative;
  font-family: var(--font-family-mono);
  font-size: var(--font-size-meta);
  cursor: help;
}

.digest__value::after {
  content: attr(data-full);
  position: absolute;
  inset-block-end: calc(100% + 8px);
  /* Anchored to the right and wrapping: anchored left with nowrap, the
     71-character digest stretched the table past the card. */
  inset-inline-start: auto;
  inset-inline-end: 0;
  width: max-content;
  max-width: 280px;
  padding: var(--space-2) var(--space-3);
  background: var(--color-text-ink);
  color: var(--color-text-on-brand);
  font-family: var(--font-family-mono);
  font-size: var(--font-size-eyebrow);
  overflow-wrap: anywhere;
  z-index: 30;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--motion-duration-fast) var(--motion-easing);
}

.digest__value:hover::after,
.digest__value:focus-visible::after {
  opacity: 1;
  visibility: visible;
}



/* A digest that does not exist yet: weaker than real content, so the cell
   does not read as a filled-in value. */
.digest__pending {
  color: var(--color-text-disabled);
}

/* The note explains the table it follows. Its parent flex column already
   supplies the 16px row-gap, so the paragraph's own top padding (meant for
   help text under an input) is dropped here — otherwise the two stack. */
.table-frame + .spark-fieldtext-wrapper-description {
  padding-block-start: 0;
}

/* ---- Review and submit: screenshots as the SI sees them ---- */

/* No Spark equivalent: Spark has no carousel, slideshow or lightbox. This
   stays ours, restyled with Spark tokens so it sits alongside everything
   else. See docs/spark-adoption.md. */
.slideshow {
  display: flex;
  width: 100%;
  align-items: flex-start;
  gap: var(--space-4);
}

.slideshow__main {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
}

.slideshow__stage {
  /* Takes its width from the row and its height from that width — the old
     fixed 380px (and then the bare aspect-ratio) left the gallery ending
     mid-row with dead space beside the thumbnails. */
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface-sunken);
}

.slideshow__stage img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Step through screenshots without reaching for the thumbnails. The arrows
   sit over the stage and drop out when there is only one image. */
.slideshow__nav {
  position: absolute;
  inset-block-start: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  border-radius: var(--radius-full);
  border-color: var(--color-border);
  background: var(--color-bg-surface);
  box-shadow: var(--shadow-sm);
}

.slideshow__nav:hover {
  background: var(--color-bg-surface);
  border-color: var(--color-border-strong);
}

.slideshow__nav--prev {
  inset-inline-start: var(--space-3);
}

.slideshow__nav--next {
  inset-inline-end: var(--space-3);
}

.slideshow__nav[hidden] {
  display: none;
}

.slideshow__dots {
  display: flex;
  gap: 6px;
}

.slideshow__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-none);
  background: var(--color-border-strong);
  cursor: pointer;
  transition: width var(--motion-duration-base) var(--motion-easing);
}

.slideshow__dot[aria-current="true"] {
  width: 22px;
  background: var(--color-brand);
}

/* The full-width reading of the slideshow (Application detail): one 16:9
   preview across the column, its thumbnails in a row beneath. */
.slideshow--full {
  flex-direction: column;
  gap: var(--space-3);
}

.slideshow--full .slideshow__main,
.slideshow--full .slideshow__stage,
.slideshow--full .slideshow__thumbs {
  width: 100%;
  max-width: none;
}

.slideshow--full .slideshow__stage {
  aspect-ratio: 16 / 9;
}

.slideshow--full .slideshow__thumbs {
  flex-direction: row;
  gap: var(--space-3);
  max-height: none;
  overflow: visible;
}

.slideshow--full .slideshow__thumb {
  flex: 1 1 0;
  width: auto;
  height: auto;
  aspect-ratio: 16 / 9;
}

.slideshow__thumbs {
  display: flex;
  flex: 0 0 92px;
  flex-direction: column;
  gap: var(--space-2);
  align-self: stretch;
  max-height: none;
  overflow-y: auto;
}

.slideshow__thumb {
  flex-shrink: 0;
  width: 92px;
  height: 58px;
  padding: 0;
  overflow: hidden;
  border: 2px solid transparent;
  border-radius: var(--radius-none);
  background: var(--color-bg-surface-sunken);
  cursor: pointer;
}

.slideshow__thumb[aria-current="true"] {
  border-color: var(--color-brand);
}

.slideshow__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.completeness-list__item a {
  color: inherit;
}

/* ---- Intel Admin: security checks, the first thing on a submission ---- */

.dsec {
  overflow: hidden;
  border: 1px solid var(--color-danger-text);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
}

.dsec__header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-block-end: 1px solid var(--color-danger-text);
  background: var(--color-danger-bg);
  color: var(--color-danger-text);
  font-weight: var(--font-weight-bold);
}

.dsec__title {
  font-size: inherit;
  font-weight: inherit;
}

.dsec__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
}

.slideshow[hidden],
.table-frame[hidden] {
  display: none;
}

/* The rail stays in view down a long step. */
.wizard-rail--card {
  position: sticky;
  inset-block-start: 24px;
}

/* ============================== Application detail ============================== */

/* A page section on a read-only detail page: a sentence-case heading with
   its content beneath, spaced by the column's own gap. */
.detail-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* Class-level display beats the UA [hidden] rule — restated so a section
   with nothing to show (no screenshots in the listing preview) goes. */
.detail-section[hidden] {
  display: none;
}

.detail-section__title {
  font-size: var(--spark-font-size-100);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

/* A quiet aside beside a section heading: "What Intel reviewed and what
   installs on the device". */
.detail-section__note {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-regular);
  color: var(--color-text-muted);
  margin-inline-start: var(--space-3);
}

/* One block of the submitted package: a sunken header strip naming it, with
   the totals on the right, over a white body. */
.panel {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
  overflow: hidden;
}

.panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-5);
  border-block-end: 1px solid var(--color-border);
  background: var(--color-bg-surface-sunken);
}

.panel__title {
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-bold);
}

.panel__title {
  color: var(--color-text-ink);
}

.panel__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
}

/* A panel whose body is a table: the table meets the panel's own edges. */
.panel__body--flush {
  padding: 0;
  gap: 0;
}

/* Class-level display beats the UA [hidden] rule, so the footer has to
   restate it — it only appears when a table is actually truncated. */
.panel__footer[hidden] {
  display: none;
}

.panel__footer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-block-start: 1px solid var(--color-border);
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

/* At a glance: label left, value right, one fact per line. */
/* One fact per line, separated by hairlines rather than by empty space. */
.glance {
  display: flex;
  flex-direction: column;
}

.glance__row,
.glance__block {
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--color-border);
}

.glance__row:last-child,
.glance__block:last-child {
  border-block-end: 0;
  padding-block-end: 0;
}

.glance__row:first-child,
.glance__block:first-child {
  padding-block-start: 0;
}

.glance__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
}

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

.glance__value {
  color: var(--color-text-ink);
  font-weight: var(--font-weight-medium);
  text-align: end;
}

.glance__block {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The devices block closes the same ruled list, so it takes a rule above
   it — without one it read as part of the tags above. */
.glance + .glance__block {
  padding-block: var(--space-3) 0;
  border-block-start: 1px solid var(--color-border);
}

/* The count over a slideshow's own preview, e.g. "1 of 4". */
.slideshow__stage {
  position: relative;
}

.slideshow__count {
  position: absolute;
  inset-block-end: var(--space-3);
  inset-inline-end: var(--space-3);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-none);
  background: var(--spark-color-theme-dark-gray200);
  color: var(--color-text-on-brand);
  font-size: var(--font-size-eyebrow);
}

/* The counter inside a filled glyph on the sign-in refusals, punched out in
   the surface colour. */
.alert__glyph {
  fill: var(--spark-color-white);
}

/* ============================== Auth screens ============================== */

/*
 * Sign-in is the one place in the product with a background treatment.
 * Everything past the licence page uses the standard shell on the flat
 * page background, so these rules are deliberately scoped to .auth and
 * nothing reaches out of it.
 *
 * The gradient is mixed from --color-brand at paint time rather than
 * written as fixed colours, so swapping the brand token carries the whole
 * backdrop with it. Treat the treatment as provisional.
 */
.auth {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding: var(--space-8) var(--layout-side-padding);
  background-color: var(--color-auth-backdrop-shade);
  background-image: radial-gradient(
    ellipse 110% 95% at 24% 16%,
    color-mix(in srgb, var(--color-brand) 88%, var(--color-auth-backdrop-shade)) 0%,
    color-mix(in srgb, var(--color-brand) 44%, var(--color-auth-backdrop-shade)) 44%,
    color-mix(in srgb, var(--color-brand) 6%, var(--color-auth-backdrop-shade)) 100%
  );
}

.auth[hidden] {
  display: none;
}

/* The dot grid. A pseudo-element rather than a second background layer so
   its opacity is independent of the gradient underneath. */
.auth::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image: radial-gradient(
    circle,
    var(--color-text-on-brand) 1px,
    transparent 1px
  );
  background-size: 26px 26px;
  opacity: 0.05;
  pointer-events: none;
}

/* Everything above the dot grid. */
.auth > * {
  position: relative;
}

/* The card and its alert, centred as one column, so a refusal appearing
   above the card pushes the pair up together instead of shifting the card
   down the screen. Nothing else is on the screen — no footer, no corner
   mark — so the stack owns the whole viewport. */
.auth__stack {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
}

.auth__stack > * {
  width: 100%;
  max-width: 440px;
}

.auth__card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5);
  padding: var(--space-8);
  background: var(--color-bg-surface);
  border-radius: var(--radius-none);
  box-shadow: var(--shadow-md);
}

/* The product mark as the top bar carries it. */
.auth__mark {
  height: 22px;
  width: auto;
}

.auth__title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-page-title);
  font-weight: var(--font-weight-display);
  line-height: var(--line-height-tight);
  color: var(--color-text-ink);
}

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

.auth__actions {
  display: flex;
  justify-content: flex-end;
  width: 100%;
}

/* ============================== IDP interstitial ============================== */

/*
 * Screen 4 stands in for Intel's own sign-in page. It is not a recreation
 * of it: no form, no branding, no help links. The hatching and the dashed
 * border say "not our surface" — the same visual language a placeholder
 * uses everywhere else in this prototype.
 */
.idp {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--space-8) var(--layout-side-padding);
  background-color: var(--color-bg-page);
  background-image: repeating-linear-gradient(
    135deg,
    var(--color-bg-surface) 0 10px,
    var(--color-bg-page) 10px 20px
  );
}

.idp[hidden] {
  display: none;
}

.idp__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  max-width: 560px;
  padding: var(--space-9) var(--space-8);
  text-align: center;
  background: var(--color-bg-surface);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-none);
}

.idp__title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-page-title);
  font-weight: var(--font-weight-display);
  line-height: var(--line-height-tight);
  color: var(--color-text-ink);
}

.idp__address {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

.idp__body {
  max-width: 46ch;
  color: var(--color-text-muted);
}

/* An indeterminate bar rather than a spinner: the wait is a redirect with
   no progress to report, and a bar reads as "in transit" rather than as
   "working". Reduced motion stops it dead — the JS skips the delay too, so
   it is never the only thing on screen for long. */
.idp__progress {
  width: 120px;
  height: 3px;
  margin-block-start: var(--space-3);
  overflow: hidden;
  background: var(--color-neutral-bg);
}

.idp__progress::after {
  content: "";
  display: block;
  width: 40%;
  height: 100%;
  background: var(--color-brand);
  animation: idp-progress 1100ms ease-in-out infinite;
}

@keyframes idp-progress {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(300%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .idp__progress::after {
    animation: none;
    width: 100%;
  }
}

/* ============================== Platform licence ============================== */

.licence {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-width: 720px;
  margin-inline: auto;
  padding: var(--space-9) var(--layout-side-padding);
}

.licence__header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  text-align: center;
}

.licence__title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-page-title);
  font-weight: var(--font-weight-display);
  line-height: var(--line-height-tight);
}

.licence__lede {
  max-width: 46ch;
  color: var(--color-text-muted);
}

/* The document. Its lower edge fades into the card so the text reads as
   continuing past the frame rather than stopping at it — the clauses are
   still scrollable, so nothing is put out of reach by the fade. */
.licence__doc {
  position: relative;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
}

.licence__doc::after {
  content: "";
  position: absolute;
  inset-inline: 1px;
  bottom: 1px;
  height: 80px;
  border-radius: 0 0 var(--radius-none) var(--radius-none);
  background: linear-gradient(to bottom, transparent, var(--color-bg-surface));
  pointer-events: none;
}

.licence__scroll {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  /* Sized so the four clauses always overflow it: the fade has to be
     covering something real, not greying out the end of the document. */
  max-height: 360px;
  overflow-y: auto;
  /* The tall bottom padding keeps the last line clear of the fade once the
     reader has scrolled to it. */
  padding: var(--space-7) var(--space-7) var(--space-9);
}

/* A size up from body, the same step every other section heading in this
   system takes (.field__label--section, .wizard-card__section-label) —
   rather than leaving an <h2> on the browser's own 1.5em. */
.licence__clause-title {
  font-size: var(--spark-font-size-100);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.licence__clause-body {
  color: var(--color-text-muted);
  padding-block-start: var(--space-2);
}

/* The acceptance sits in its own box, separated from the document, so
   ticking it is plainly a second and deliberate act. */
.licence__accept {
  padding: var(--space-5) var(--space-6);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
}

.licence__actions {
  display: flex;
  justify-content: flex-end;
}

/* ============================== Responsive ============================== */

/* Two breakpoints, not a fluid layout — CLAUDE.md's own 1440-wide desktop
   design is the baseline (.page-container already caps at
   --layout-max-width and centers), so these two tiers exist to keep that
   same design from breaking, not to reflow it into something new:
   tablet ≤1024px (iPad-class devices, this app's shell portrait/landscape)
   and mobile ≤640px (phones). Kept last in the file on purpose — a
   same-specificity override earlier in source order loses to a plain
   rule declared after it, media query or not, so anything meant to win
   here has to come after everything it's overriding. */

@media (max-width: 1024px) {
  .wizard {
    flex-direction: column;
  }

  .wizard-rail {
    width: 100%;
  }

  .choice-group {
    grid-template-columns: 1fr;
  }

  /* minmax(0, …) + min-width: 0 — a plain 1fr track grows to its widest
     unbreakable child (a mono image name, a row of screenshots) and
     pushes the page wider than the screen. */
  .review-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .review-layout__main,
  .review-layout__side {
    min-width: 0;
  }

  /* Stacked under the main column, there's nothing for it to stick beside. */
  .review-layout__side--sticky {
    position: static;
  }

  /* No room for a 248px column beside the page: the side nav becomes a
     bar across the top — brand and user switcher on one line, the links
     as a scrollable row under them. */
  /* minmax(0, …): a plain 1fr track grows to the nowrap link row's full
     width and scrolls the whole page sideways. */
  /* auto 1fr: on a short page the shell's min-height would otherwise be
     shared between both rows, stretching the nav bar taller than its links. */
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr;
  }

  .side-nav {
    min-width: 0;
    position: static;
    height: auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    border-inline-end: 0;
    border-block-end: 1px solid var(--color-border);
  }

  .side-nav {
    padding-block-start: 0;
  }

  .side-nav__nav {
    order: 3;
    width: 100%;
    min-width: 0;
    overflow-x: auto;
  }

  .side-nav__list {
    flex-direction: row;
  }

  .side-nav__link {
    width: auto;
    white-space: nowrap;
  }

  .side-nav__link[aria-current="page"]::before {
    inset-block: auto 0;
    inset-inline: 0;
    width: auto;
    height: 3px;
  }

}

@media (max-width: 640px) {
  :root {
    /* 40px a side is over a fifth of a 375px phone's width gone to
       nothing but margin — this keeps content readable without the page
       reading as cramped against the glass. */
    --layout-side-padding: 16px;
  }

  /* The brand wordmark, the nav link, the org name and the avatar in one
     60px row is more than a phone's width can hold without wrapping the
     bar itself (which would break its fixed height) — trimmed to the
     marks that still carry meaning alone: the logo, and the avatar. */
  .topbar__inner {
    gap: var(--space-3);
  }

  .topbar__product {
    display: none;
  }

  .nav {
    margin-inline-start: var(--space-4);
    gap: var(--space-4);
  }

  .page-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .page-header__actions {
    width: 100%;
    flex-wrap: wrap;
  }

  .search-field {
    max-inline-size: none;
  }

  .wizard-card {
    padding: var(--space-5);
  }

  .wizard-footer {
    flex-wrap: wrap;
    row-gap: var(--space-4);
  }

  .field-row {
    grid-template-columns: 1fr;
  }

  .app-card {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-4);
  }

  .app-card__end {
    width: 100%;
    justify-content: space-between;
  }

  .attention-card__head {
    flex-direction: column;
    align-items: flex-start;
  }

  .attention-card__actions {
    width: 100%;
    flex-wrap: wrap;
  }

  .modal-footer {
    flex-wrap: wrap;
  }

  /* A fixed 140-200px label column leaves little room for the value next
     to it on a phone — stacked reads better than both columns cramped. */
  .kv-row {
    grid-template-columns: 1fr;
    gap: var(--space-1);
  }

  .review-card__header {
    flex-wrap: wrap;
  }

  .toolbar--bare {
    flex-direction: column;
    align-items: flex-start;
  }

  /* Name and status pill, screenshot slots, and a file row's name / size /
     action each get their own line rather than running off the side, as do
     the header's own actions. */
  .app-detail-head__title-row,
  .app-detail-head__actions,
  .review-card__shots,
  .upload-row {
    flex-wrap: wrap;
  }

  /* The logo moves above the text so the body gets the full width: the
     actions row doesn't shrink, and beside a 112px logo it was wider than
     the screen. */
  .app-detail-head {
    flex-direction: column;
  }

  .app-detail-head__actions {
    flex-shrink: 1;
    min-width: 0;
  }

  /* At tablet the large tile drops to the default tile's size, so it takes
     the default tile's radius with it. */
  .app-icon-tile--lg {
    width: 64px;
    height: 64px;
    border-radius: var(--radius-app-icon);
    background-size: 42px 42px;
  }

  .doc-viewer__stage {
    padding: var(--space-3);
  }

  .doc-viewer__page {
    padding: var(--space-5);
  }

  .pagination {
    padding-inline: var(--space-4);
  }

  .user-switcher__text {
    display: none;
  }

  /* Columns that can go on a phone without losing the row's meaning. */
  .table__col--hide-sm {
    display: none;
  }

  /* A list table in a card keeps name, state and the row kebab inside
     the screen: tighter cells, and the icon tile — decoration beside the
     name — dropped. */
  .card > .spark-table .app-icon-tile--sm,
  .card > .spark-table .avatar--md {
    display: none;
  }
}

/* Wizard (four steps) at tablet and phone widths. */
@media (max-width: 1024px) {
  .identity-split {
    grid-template-columns: minmax(0, 1fr);
  }

  .identity-split__rule {
    display: none;
  }

  .identity-split > :first-child {
    max-width: 320px;
  }
}

@media (max-width: 640px) {
  .icon-picker--grid .icon-picker__thumbs {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .slideshow {
    flex-direction: column;
  }

  .slideshow__thumbs {
    flex-direction: row;
    max-height: none;
    max-width: 100%;
    overflow-x: auto;
  }

  .substep__body {
    padding-inline-start: 0;
  }

  /* A phone can't show these tables at their desktop widths. They keep
     readable columns and scroll sideways inside their own frame; the card
     clips its overflow so the page itself never scrolls sideways. */
  .table-frame,
  .table-wrap {
    overflow-x: auto;
  }

  .table-frame .spark-table {
    min-inline-size: 560px;
  }

  /* The table scrolls here, so machine values keep their own width rather
     than breaking mid-word to fit a narrow column. */
  .table-frame .table__mono {
    overflow-wrap: normal;
  }

  .wizard-card {
    overflow-x: hidden;
  }

  .table__cell-actions {
    flex-wrap: wrap;
  }

  .form-divider[hidden] {
    display: none;
  }
}

@media (max-width: 1024px) {
  .wizard-rail--card {
    position: static;
  }
}

@media (max-width: 640px) {
  .glance__row {
    flex-direction: column;
    gap: var(--space-1);
  }

  .glance__value {
    text-align: start;
  }
}

/* ==================== Catalog (SI) ==================== */

/* The SI's catalog home: listings grouped by industry, four to a row at
   1440. Each card is one link — icon, name, publisher, one-line summary,
   industry and region tags — so the whole surface opens the listing. */
.catalog-group + .catalog-group {
  margin-block-start: var(--space-7);
}

.catalog-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-5);
}

/* A catalog card is Spark's vertical card as a link. Spark gives the card
   no surface colour of its own; it sits on white, fills its grid cell, and
   keeps its tags on the bottom edge so every card's tags line up. */
.catalog-card {
  background: var(--spark-color-white);
  color: inherit;
}

.catalog-card[hidden],
.catalog-group[hidden] {
  display: none;
}

.catalog-card .spark-card-vertical {
  display: flex;
  flex-direction: column;
  inline-size: 100%;
}

.catalog-card .spark-card-vertical-information-container {
  flex: 1;
  max-block-size: none;
}

.catalog-card .chip-row {
  margin-block-start: auto;
}

@media (max-width: 1200px) {
  .catalog-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .catalog-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 600px) {
  .catalog-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ==================== Container images: per-image provide ==================== */

/* Step 3's Container images table. Each private image is provided on its
   own row — a tar upload through the page, or a push from the ISV's
   terminal with a token scoped to that one repository path (4.11) — so the
   route is chosen per image rather than once for all of them.
   Everything below is the row action, the panel it opens, and the two tabs
   inside it. The table, pills, dropzone, upload row, code block and
   callouts are the existing components. */

/* Status is a pill with an optional muted line under it saying how the
   image arrived, so "Present" and "how" read as one answer. */
.image-status {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
}

.image-status__note {
  font-size: var(--font-size-eyebrow);
  color: var(--color-text-muted);
}

/* The open row is tinted so it is obvious which image the panel beneath it
   belongs to — the panel spans the whole table, so without this the pairing
   is guesswork. */
.image-row--open > td {
  background: var(--color-brand-subtle-bg);
  border-bottom-color: var(--color-brand-subtle-border);
}

/* The panel is a table row so it stays inside the table's own borders and
   moves with its image; the cell carries no padding of its own because the
   panel sets it. */
/* Spark's button is display: flex, so the cell's end alignment alone
   leaves it short of the edge the header and the open panel line up to. */
.image-row > .spark-table-cell-align-end > .spark-button {
  margin-inline-start: auto;
}

.image-panel-row > td {
  padding: 0;
  background: var(--color-bg-surface-sunken);
}

.image-panel {
  padding: var(--space-6);
}

.image-panel:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: -4px;
}

.image-tab {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.image-tab[hidden] {
  display: none;
}

/* ---- Tar upload ---- */

/* The upload row and its progress bar read as one object, so they share a
   border rather than the row carrying its own. */
.upload-progress {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
}

.upload-progress .upload-row {
  margin-block-start: 0;
  border: 0;
  border-radius: var(--radius-none);
}

/* ---- Push from the terminal ---- */

.token-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
}

.token-row__dot {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--color-success-text);
}

.token-row--expired .token-row__dot {
  background: var(--color-text-disabled);
}

.token-row__body {
  flex: 1;
  min-width: 0;
}

.token-row__title {
  display: block;
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.token-row__meta {
  display: block;
  font-size: var(--font-size-eyebrow);
  color: var(--color-text-muted);
}

/* An expired token leaves the commands on screen — they are still what the
   ISV will run — but dimmed, so it is clear a new token comes first. */
.push-steps--expired .push-step__body {
  opacity: 0.45;
}

.push-steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  counter-reset: push-step;
}

.push-step {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  column-gap: var(--space-4);
  row-gap: var(--space-3);
}

.push-step__number {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-brand-subtle-border);
  background: var(--color-brand-subtle-bg);
  color: var(--color-brand);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-medium);
}

.push-step__title {
  align-self: center;
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.push-step__body {
  grid-column: 2;
  min-width: 0;
}

/* The last step is an action, not a command: the button and the reason it exists
   sit on one line. */
.push-step__action {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* ==================== Install CTA (SI application page) ==================== */

/* The one action area on the application page, top right of the listing
   head. It holds the same width and the same top edge in every state, so
   the page does not reflow as the SI connects a device: the message simply
   becomes the button.
   Four states are foreseen — no device, device ready, and (once the install
   flow is designed) incompatible and already installed. Two are built. */
.listing-head__cta {
  flex-shrink: 0;
  align-self: flex-start;
  width: 320px;
}

.install-cta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

/* The same class-level `display` beating the UA [hidden] rule as .callout
   and .upload-row — the two states toggle, so it has to be restated. */
.install-cta[hidden] {
  display: none;
}

/* Nothing to act on yet, so it reads as a card of explanation rather than a
   disabled button. Not an error: a device simply has not been connected. */
.install-cta--none {
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface-sunken);
}

.install-cta__state {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.install-cta__body {
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

/* The device the button would install onto, directly under it, so the
   target is never a guess. */
.install-cta__device {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--font-size-meta);
  color: var(--color-success-text);
}

.install-cta .spark-button-action {
  inline-size: 100%;
}

/* Link-looking buttons: the not-connected panel's way to the setup
   instructions, and a listing's "Clear all" beside its filters (shown only
   while a search or filter narrows the list). Links in look, buttons
   underneath because they act on the page rather than go anywhere. The
   button's own chrome is cleared so only Spark's hyperlink styling shows. */
.install-cta__link,
.filter-clear {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.filter-clear {
  align-self: center;
  white-space: nowrap;
}

/* A bare state dot beside a line of text: Spark's text-less badge. */
.state-dot {
  flex-shrink: 0;
}

/* A command the development team has still to supply: the shape of the code
   block, without pretending there is anything to copy. */
.command-placeholder {
  padding: var(--space-4);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface-sunken);
  font-size: var(--font-size-meta);
  font-style: italic;
  color: var(--color-text-disabled);
}

@media (max-width: 900px) {
  .listing-head__cta {
    width: 100%;
  }
}

/* The quietest line on a catalog card: whether this listing can be
   installed right now. It decides what happens on the next screen, so it
   belongs on the card — but under the tags, at meta size, never competing
   with the name. */
/* Spark's information container has no bottom padding and its footer no
   top padding, so the status line sat against the tags. */
.catalog-card .spark-card-vertical-footer-container.catalog-card__device {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  padding-block-start: var(--spark-space-12);
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-eyebrow);
  color: var(--color-text-muted);
}

/* ==================== SI install flow ==================== */

/* Story 8 has exactly two device states — connected or not — and forbids a
   waiting state, a spinner or a disabled Install button at device
   connection: there is nothing for the platform to report until the device
   signs in. The install area therefore holds four states in all, and they
   share one width and one top edge so the page never reflows:
     not-connected  no Install button at all, a link to the instructions
     ready          Install on this device, the device named beneath
     installing     a spinner and status, in place, never a dialog (8.4)
     installed      the indicator, with the publisher's link beneath (8.8)
*/

/* A spinner that is not inside a button. Same geometry as a loading
   .spark-button's, which is an ::after and cannot be reused. */
.spinner {
  display: inline-block;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  border: 2px solid var(--color-brand-subtle-border);
  border-block-start-color: var(--color-brand);
  border-radius: var(--radius-full);
  animation: btn-spin 700ms linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: 2400ms;
  }
}

/* 8.4: the wait sits in the install area like every other state, because
   the SI is told they can leave the page and come back. */
.install-cta--installing {
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
}

/* 8.8: the indicator replaces the button and persists on return. Tinted,
   because it is the one state that reports an outcome rather than offering
   an action. */
.install-cta--installed {
  padding: var(--space-5);
  border: 1px solid var(--color-success-bg);
  border-radius: var(--radius-none);
  background: var(--color-success-bg);
}

.install-cta__heading {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.install-cta--installed .install-cta__heading {
  color: var(--color-success-text);
}

/* A hairline between the indicator and the publisher's link, so the link
   reads as the next thing to do rather than as more of the same sentence. */
.install-cta__rule {
  width: 100%;
  height: 1px;
  margin-block: var(--space-2);
  background: color-mix(in srgb, var(--spark-color-moss-shade2) 20%, transparent);
}

/* 8.4, again: the SI can leave this page, so the catalog says what is still
   running and offers a way back to it. */
.install-strip {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  margin-block-end: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
  font-size: var(--font-size-meta);
  color: var(--color-text-ink);
}

.install-strip[hidden] {
  display: none;
}

.install-strip__link {
  margin-inline-start: auto;
}

/* ---- Dialogs: the two-step run-up, then the outcome ---- */

/* Two short dialogs before a long wait, so the SI can see how far in they
   are. Not from the stories — see the open question. */
.dialog__steps {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-6) 0;
}

.dialog__step {
  flex: 1;
  height: 3px;
  border-radius: var(--radius-full);
  background: var(--color-neutral-bg);
}

.dialog__step--done {
  background: var(--color-brand);
}

/* A line under a dialog title that qualifies it — whose terms these are,
   or why only the installer can supply these values. */
.dialog__lede {
  padding-block-start: var(--space-2);
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}

/* An outcome dialog: one mark, one heading, one sentence, then whatever the
   SI has to read. Three tones, one shape. */
.outcome {
  text-align: center;
  padding-block: var(--space-6) var(--space-5);
}

.outcome__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-full);
  margin-block-end: var(--space-5);
}

.outcome__icon--success {
  background: var(--color-success-bg);
  color: var(--color-success-text);
}

.outcome__icon--error {
  background: var(--color-danger-bg);
  color: var(--color-danger-text);
}

.outcome__icon--warning {
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}

.outcome__title {
  font-family: var(--font-family-display);
  font-size: var(--font-size-pane-title);
  font-weight: var(--font-weight-display);
  color: var(--color-text-ink);
}

.outcome__body {
  max-width: 44ch;
  margin-inline: auto;
  padding-block-start: var(--space-3);
  color: var(--color-text-muted);
}

/* What went wrong, or what to check: a quiet panel, read left-aligned even
   though the outcome above it is centred. */
.note-panel {
  text-align: start;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-none);
  background: var(--color-bg-surface-sunken);
}

.note-panel__title {
  display: block;
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.note-panel__body {
  display: block;
  padding-block-start: var(--space-2);
  color: var(--color-text-muted);
}

.note-panel__path {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-meta);
  color: var(--color-text-ink);
}

/* 8.5: the platform cannot know how to open someone else's application, so
   the publisher's documentation is the only route to it. It is given the
   weight of an action because on the success screen it is the only one. */
.doc-link {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  text-align: start;
  padding: var(--space-4);
  border: 1px solid var(--color-brand-subtle-border);
  border-radius: var(--radius-none);
  background: var(--color-brand-subtle-bg);
  color: inherit;
  text-decoration: none;
}

.doc-link:hover {
  border-color: var(--color-brand);
}

.doc-link__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: var(--radius-none);
  background: var(--color-bg-surface);
  color: var(--color-brand);
}

.doc-link__body {
  flex: 1;
  min-width: 0;
}

.doc-link__title {
  display: block;
  font-weight: var(--font-weight-medium);
  color: var(--color-brand-emphasis);
}

.doc-link__url {
  display: block;
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
}

.doc-link__arrow {
  flex-shrink: 0;
  color: var(--color-brand);
}

/* 8.3: the variable's name is the label. The SI never types a name, so it
   is set in the type the Compose file uses, not in sentence case. */
.setting-field__name {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-ink);
}

.install-cta__rule[hidden] {
  display: none;
}

/* Same class-level `display` beating the UA [hidden] rule as .app-card and
   .install-cta — the panel is dropped where the publisher supplied no
   documentation, so it has to be restated. */
.doc-link[hidden] {
  display: none;
}

/* The 8.7 outcome, kept after its dialog closes: installed, on this device,
   and not running. Not on any board — the only installed state supplied is
   the green one, which would tell an SI whose application has stopped that
   all is well. */
.install-cta--stopped {
  border-color: var(--color-warning-bg);
  background: var(--color-warning-bg);
}

.install-cta--stopped .install-cta__heading {
  color: var(--color-warning-text);
}

.install-cta--stopped .install-cta__rule {
  background: color-mix(in srgb, var(--spark-color-daisy-shade2) 20%, transparent);
}

/* .licence-text's rule marks a licence out from the page it is read on.
   Inside a panel that already does that, it is a second frame around the
   same thing. */
.licence-text--plain {
  border-inline-start: 0;
  padding-inline-start: 0;
}

