/* ==========================================================================
   Key Lair — Admin Dashboard
   Requires tokens.css to be loaded first.
   ========================================================================== */

body{
  background:var(--bg-void);
  color:var(--text-primary);
  font-family:var(--font-body);
  display:flex;
  min-height:100vh;
}

/* A button/input/select inside a disabled <fieldset> (demo's "visible but
   can't submit" controls throughout the admin area) already can't be
   focused or clicked -- native fieldset-disabled inheritance handles
   that -- but with no rule here it still looked fully active, defeating
   the point of showing it instead of hiding it. :disabled matches an
   element disabled this way even though its own `disabled` IDL
   property/attribute stays false (the fieldset carries it, not the
   descendant), so this needs its own selector rather than relying on
   [disabled]. Plain element selectors (not .btn-scoped) so it also
   catches the several inline-styled action buttons (Delete/Remove
   icon/etc.) that never had a class to begin with. Lives here, not in
   admin-product-editor.css, so it still applies on pages (keys.php,
   claim_links.php, products.php) that don't load that stylesheet. */
button:disabled{opacity:0.4; cursor:not-allowed;}
input:disabled, select:disabled, textarea:disabled{opacity:0.5; cursor:not-allowed;}

/* Sidebar */
.sidebar{
  width:236px;
  flex-shrink:0;
  background:var(--sidebar);
  border-right:1px solid var(--line);
  padding:22px 14px;
  display:flex;
  flex-direction:column;
  position:sticky;
  top:0;
  height:100vh;
}
.brand{
  display:flex;
  align-items:center;
  gap:9px;
  padding:6px 10px 26px;
  font-family:var(--font-display);
  font-weight:600;
  font-size:15px;
  letter-spacing:0.02em;
}
.brand-icon{width:20px; height:20px; border-radius:5px; object-fit:contain; flex-shrink:0;}
nav{display:flex; flex-direction:column; gap:2px;}
.nav-item{
  display:flex;
  align-items:center;
  gap:11px;
  padding:10px 12px;
  border-radius:var(--radius-md);
  color:var(--text-muted);
  font-size:13.5px;
  font-weight:500;
  cursor:pointer;
  transition:background .12s ease, color .12s ease;
}
.nav-item svg{width:17px;height:17px; flex-shrink:0;}
.nav-item:hover{background:var(--bg-panel); color:var(--text-primary);}
.nav-item.active{
  background:var(--bg-panel);
  color:var(--signal);
  box-shadow:inset 2px 0 0 var(--signal);
}
.nav-group-toggle{
  appearance:none; background:none; border:none; width:100%; font:inherit;
}
.nav-group-chevron{width:13px !important; height:13px !important; margin-left:auto; transition:transform .12s ease;}
.nav-group-toggle[aria-expanded="true"] .nav-group-chevron{transform:rotate(180deg);}
.nav-group{display:none; flex-direction:column; gap:2px; padding-left:17px; margin-top:2px;}
.nav-group.open{display:flex;}
.sidebar-bottom{margin-top:auto; display:flex; flex-direction:column;}
.sidebar-account-group{display:none; flex-direction:column; gap:2px;}
.sidebar-account-group.open{display:flex;}
.sidebar-footer{
  appearance:none; background:none; border:none; border-top:1px solid var(--line);
  width:100%; font:inherit; color:inherit; text-align:left; cursor:pointer;
  padding:16px 10px;
  display:flex;
  align-items:center;
  gap:10px;
  border-radius:0 0 var(--radius-md) var(--radius-md);
}
.sidebar-footer:hover{background:var(--bg-panel);}
.avatar{
  width:30px;height:30px;border-radius:50%;
  background:linear-gradient(135deg, var(--signal), var(--key-gold));
  flex-shrink:0;
}
.who{font-size:12.5px;}
.who .name{font-weight:600; color:var(--text-primary);}
.who .role{color:var(--text-faint); font-size:11px;}

/* Main */
.main{flex:1; min-width:0; padding:26px 32px 60px;}
.topbar{
  display:flex;
  align-items:center;
  justify-content:space-between;
  margin-bottom:26px;
  gap:16px;
  flex-wrap:wrap;
}
.topbar h1{
  font-family:var(--font-display);
  font-size:22px;
  font-weight:600;
  margin:0;
}
.campaign-select{
  background:var(--bg-panel);
  border:1px solid var(--line);
  color:var(--text-primary);
  font-family:var(--font-body);
  font-size:13px;
  font-weight:500;
  /* A <button> and an <a> using this same class rendered at different
     heights despite identical padding/font-size -- a <button>'s UA
     default line-height doesn't automatically match surrounding text
     the way it does for other elements, so the two ended up computing
     different total heights from the same padding. An explicit
     line-height removes that per-tag difference. */
  line-height:1.2;
  padding:9px 14px;
  border-radius:var(--radius-md);
  display:flex;
  align-items:center;
  gap:8px;
  cursor:pointer;
  /* A compact pill-shaped button/link -- its own text wrapping onto a
     second line inside it (e.g. "Export" / "(CSV)") reads as broken, not
     compact. When it doesn't fit a flex row's remaining space, it should
     move to its own new line instead (every place this sits already
     allows that via flex-wrap on its parent) rather than folding text
     within its own box. */
  white-space:nowrap;
  flex-shrink:0;
}
.campaign-select .badge{
  color:var(--signal);
  font-size:11px;
  background:rgba(90,168,221,0.12);
  padding:2px 7px;
  border-radius:99px;
}

/* A page-level filter row's selects (Redemptions' campaign/product, ...)
   size to their own text by default -- "All campaigns" and "All products"
   ending up visibly different widths next to each other. A shared floor
   lines them up regardless of which option is currently selected. */
.filter-row select{min-width:180px;}

/* Design page's "Download default template" / "Download active" links --
   sized to their own text by default, "default template" being the
   longer label leaves the two visibly uneven widths side by side. flex:1
   1 0 makes them split evenly instead, same rule as every other two-item
   row here -- but only across the width .design-download-spacer leaves
   them, not the full row: that row's real full width also includes
   Upload's column right below it (Choose-file/Preview/Upload), and these
   two links are meant to line up with Choose-file+Preview only, not
   reach under Upload too. */
.design-download-link{flex:1 1 0; min-width:190px; text-align:center;}
/* Invisible same-row sibling reserving Upload's own width (a per-page
   card's Upload button is always just that one word) so the two links
   above never grow into the space Upload occupies one row down. */
.design-download-spacer{flex:0 0 88px; visibility:hidden;}

.stat-grid{
  display:grid;
  grid-template-columns:repeat(4, 1fr);
  gap:14px;
  margin-bottom:26px;
}
.stat-card{
  background:var(--bg-panel);
  border:1px solid var(--line);
  border-radius:var(--radius-lg);
  padding:18px 18px 16px;
  /* .stat-grid is a grid, which already stretches every card in a row to
     match that row's tallest card (its default align-items:stretch) --
     turning each card into a column here lets the value anchor to the
     bottom of that shared height via margin-top:auto below, instead of
     guessing a fixed reserved height for the label above it. A row where
     every label fits on one line (e.g. the Dashboard's 2x2 grid) ends up
     exactly as tall as its content needs, no wasted gap; a row with a
     mixed 1-line/2-line label (e.g. Analytics' "Redeemed" next to "Total
     keys"/"Redemption rate") still gets every value lining up, because
     the shorter cards simply stretch to match, not because of a magic
     number tuned to one specific layout. */
  display:flex;
  flex-direction:column;
}
.stat-label{
  font-size:11.5px;
  line-height:1.3;
  text-transform:uppercase;
  letter-spacing:0.08em;
  color:var(--text-faint);
  font-weight:600;
  margin-bottom:10px;
}
.stat-value{
  font-family:var(--font-display);
  font-size:26px;
  font-weight:600;
  margin-top:auto;
}
.stat-value.gold{color:var(--key-gold);}
.stat-value.signal{color:var(--signal);}
.stat-sub{
  font-size:11.5px;
  color:var(--text-faint);
  margin-top:6px;
}

.row-2{
  display:grid;
  grid-template-columns:1.1fr 1.6fr;
  gap:16px;
  margin-bottom:16px;
}
/* Wide-list-plus-narrow-form layout used by Campaigns, Creator Groups,
   Platforms, Admins. A CSS class (not an inline style, which would always
   win over the responsive rule below regardless of screen width). */
.row-2.row-2-list-form{grid-template-columns:1.4fr 1fr; align-items:start;}
/* Analytics' "Redemptions by platform" / "Top products" row -- both are
   the same kind of short stat list, so unlike the wide-list-plus-
   narrow-form pages above there's no reason for one to be wider. */
.row-2.row-2-even{grid-template-columns:1fr 1fr;}
.panel{
  background:var(--bg-panel);
  border:1px solid var(--line);
  border-radius:var(--radius-lg);
  padding:20px 20px 18px;
  /* A wide table (e.g. several columns of email/role/status) can need more
     width than a narrow screen has. Scroll just this panel horizontally
     instead of blowing out the whole page's layout. */
  overflow-x:auto;
}
.panel-title{
  font-family:var(--font-display);
  font-size:14.5px;
  font-weight:600;
  margin:0 0 16px;
}
/* A flash/error message reused .panel's own padding (20px top, 18px
   bottom -- a deliberate asymmetry elsewhere, to visually balance a
   .panel-title's own line-height against the content below it) and
   whatever margin the page around it happened to have, rather than a
   value of its own -- so the same message could end up with visibly
   uneven space above vs. below depending on which page it sat on, or
   between its border and its own text. Equal padding on all four sides,
   and equal margin above and below, wherever this class is used. */
.flash-panel{
  padding:16px 20px;
  margin:16px 0;
}
/* Grid, not flex: a fixed/guessed label-column width would either clip a
   long platform name or waste space next to short ones. As a CSS Grid, the
   first column auto-sizes to the widest .bar-plat among every .bar-row in
   the same .bar-list -- since each row is display:contents (dissolves into
   the grid instead of being its own flex box), all its bars start at
   exactly the same x position regardless of label length, with zero JS. */
.bar-list{
  display:grid;
  grid-template-columns:max-content 1fr max-content;
  column-gap:12px;
  row-gap:13px;
  font-size:12.5px;
}
.bar-row{display:contents;}
.bar-plat{color:var(--text-muted); font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:220px;}
.bar-track{
  align-self:center;
  height:8px;
  background:var(--bg-void);
  border-radius:99px;
  overflow:hidden;
}
.bar-fill{height:100%; border-radius:99px; background:linear-gradient(90deg, var(--signal-dim), var(--signal));}
.bar-num{text-align:right; font-family:var(--font-mono); font-size:11.5px; color:var(--text-faint);}

table{width:100%; border-collapse:collapse; font-size:13px;}
thead th{
  text-align:left;
  font-size:11px;
  text-transform:uppercase;
  letter-spacing:0.06em;
  color:var(--text-faint);
  font-weight:600;
  padding:0 10px 10px;
  border-bottom:1px solid var(--line);
}
tbody td{
  padding:11px 10px;
  border-bottom:1px solid var(--line);
  color:var(--text-primary);
}
tbody tr:last-child td{border-bottom:none;}
.creator{font-weight:500;}
.plat-tag{
  display:inline-block;
  font-size:11px;
  font-weight:600;
  padding:3px 9px;
  border-radius:99px;
  background:rgba(90,168,221,0.1);
  color:var(--signal);
}
.time-cell{color:var(--text-faint); font-family:var(--font-mono); font-size:11.5px;}

.link-cell{display:flex; flex-direction:column; gap:6px;}
.link-row{display:flex; align-items:center; gap:6px; min-width:0;}
.link-row .link-label{color:var(--text-faint); font-size:10.5px; white-space:nowrap; flex-shrink:0;}
.claim-link-a{
  font-family:var(--font-mono);
  font-size:10.5px;
  color:var(--signal);
  text-decoration:none;
  max-width:220px;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  flex-shrink:1;
  min-width:0;
}
.claim-link-a:hover{text-decoration:underline;}
.link-copy-btn{
  background:none;
  border:1px solid var(--line);
  color:var(--text-muted);
  font-size:10px;
  padding:2px 7px;
  border-radius:5px;
  cursor:pointer;
  flex-shrink:0;
}
.link-copy-btn:hover{color:var(--signal); border-color:var(--signal-dim);}
.link-copy-btn.copied{color:var(--signal); border-color:var(--signal-dim);}

/* Same pill shape/colors as the read-only .plat-tag it replaces on pages
   where the status is now editable inline -- deliberately keeps the
   native <select> arrow rather than hiding it (`appearance:none`) and
   drawing a custom one, since color-scheme (see tokens.css) already makes
   that native arrow follow the current theme for free. */
.status-select{
  display:inline-block;
  font-size:11px;
  font-weight:600;
  font-family:inherit;
  padding:3px 22px 3px 9px;
  border-radius:99px;
  border:1px solid transparent;
  cursor:pointer;
  background-color:rgba(136,144,164,0.12);
  color:var(--text-muted);
}
.status-select.status-active{background-color:rgba(90,168,221,0.1); color:var(--signal);}
.status-select:hover{border-color:var(--line);}
.status-select:focus{outline:none; border-color:var(--signal-dim);}

@media (max-width:980px){
  .stat-grid{grid-template-columns:1fr 1fr;}
  .row-2, .row-2.row-2-list-form{grid-template-columns:1fr;}
}
@media (max-width:720px){
  body{flex-direction:column;}
  .sidebar{
    width:100%; height:auto; position:relative;
    flex-direction:row; align-items:center;
    overflow-x:auto; padding:12px 14px;
    gap:4px;
  }
  .brand{padding:0 14px 0 0;}
  nav{flex-direction:row; gap:4px;}
  .nav-item{white-space:nowrap; padding:8px 12px;}
  .sidebar-bottom{margin-top:0; flex-direction:row;}
  .sidebar-account-group{display:flex !important; flex-direction:row; gap:4px;}
  /* The Admin group's toggle button still needs to actually toggle here
     -- .nav-group{display:flex !important} (the original mobile rule)
     forced it permanently open regardless of the .open class JS adds/
     removes on click, which is what made every admin icon (Migrations,
     Updates, Mail Server, Branding, Legal & Imprint) show up in the top
     bar all the time instead of collapsing behind the toggle the way it
     does on desktop. Only the layout (row instead of desktop's column)
     needs overriding here -- visibility stays driven by .open, same as
     desktop. */
  .nav-group-toggle{width:auto;}
  .nav-group-toggle .label{display:none;}
  /* Desktop's chevron points down when closed / up when open, matching
     a vertical dropdown that opens downward. Here the group opens into
     a horizontal row to the *right* of the toggle instead, so the same
     down/up chevron pointed the wrong way -- rotate the base icon
     -90deg so closed points right (the direction it opens) and 90deg
     so open points left (the direction it collapses back to). */
  .nav-group-chevron{transform:rotate(-90deg);}
  .nav-group-toggle[aria-expanded="true"] .nav-group-chevron{transform:rotate(90deg);}
  .nav-group{
    flex-direction:row;
    gap:4px;
    margin-top:0;
    /* Once expanded, these icons sit inline in the same row as the
       regular top-level nav -- nothing set them apart as belonging to
       "Admin" the way desktop's indentation under the toggle does.
       A border/background reads as "these go together" instead. */
    border:1px solid var(--line);
    border-radius:var(--radius-md);
    padding:4px;
    background:var(--bg-panel);
  }
  .nav-group.open{display:flex;}
  .nav-item span.label{display:none;}
  .sidebar-footer{display:none;}
  .main{padding:20px 16px 50px;}
  .stat-grid{grid-template-columns:1fr 1fr; gap:10px;}
  .stat-value{font-size:21px;}
  table{font-size:12px;}

  /* Every table becomes a stack of cards (one per row) instead of a grid
     that needs horizontal scrolling to read -- a wide table (many columns,
     or a column with inherently long content like a full claim-link URL
     or a regex key format) would otherwise force the whole row wider than
     a phone screen. Each <td> that has a data-label attribute (added per
     table in the PHP templates, matching its column header) gets that
     label as a small caption above its value; a <td> without one (an
     icon-only or actions-only cell) just shows its content plain. */
  table thead{display:none;}
  table, table tbody, table tr{display:block; width:100%;}
  table tr{
    border:1px solid var(--line);
    border-radius:var(--radius-md);
    padding:4px 12px;
    margin-bottom:10px;
  }
  table tbody tr:last-child{margin-bottom:0;}
  /* A row that's really a section divider (e.g. a product-name heading
     between groups of platform rows) uses a single colspan cell -- don't
     box it like a real data row, just let it read as a plain heading
     line above the cards that follow. */
  table tr:has(td[colspan]){border:none; padding:0; margin-bottom:0; background:none;}
  table tr:has(td[colspan]) td{padding:8px 0 4px; border-bottom:none;}
  table td{
    display:block;
    width:auto;
    padding:8px 0;
    border-bottom:1px solid var(--line-soft);
    /* A long unbreakable value (a migration filename, a full URL not
       already handled by .claim-link-a's own ellipsis, a long email) had
       nowhere to shrink to in a card only as wide as the phone screen and
       just ran off the edge instead of wrapping. */
    overflow-wrap:break-word;
    word-break:break-word;
  }
  /* The actions cell (always the last column: Edit/Delete, Upload icon/
     Delete, Edit/Disable/Delete, ...) reads left-to-right as primary
     action(s) then Delete on desktop, where it's a narrow table cell --
     but stacked full-width here, that leaves Delete sitting right next
     to the primary action instead of using the row. row-reverse flips
     the visual order (Delete ends up first/left, the primary action
     last/right, with anything in between staying in the middle) without
     touching the DOM order these forms/links are already in. */
  table tr td:last-child{
    border-bottom:none;
    display:flex;
    flex-direction:row-reverse;
    justify-content:space-between;
    align-items:center;
    flex-wrap:wrap;
    gap:8px;
  }
  table td:empty{display:none;}
  table td[data-label]::before{
    content:attr(data-label);
    display:block;
    font-size:10px;
    text-transform:uppercase;
    letter-spacing:0.06em;
    color:var(--text-faint);
    font-weight:600;
    margin-bottom:3px;
  }
  /* Key pool list: with up to hundreds of keys on a page, the generic
     8px-padding/label-caption card style (fine for a handful of rows on
     other pages) added up to a lot of scrolling. Tighter padding and a
     smaller caption gap keep each row's real content the same, just
     packed closer together. */
  #pool-table td{padding:5px 0 !important;}
  #pool-table td[data-label]::before{margin-bottom:1px !important;}

  /* Claim Links' link-cell: a fixed max-width on the URL plus a label and
     a Copy button side by side (fine in a full-width table cell) can
     still be too wide for a narrow stacked card -- let each row's pieces
     wrap onto their own lines here instead. */
  .link-row{flex-wrap:wrap;}
  .claim-link-a{max-width:100%;}

  /* Design page's per-theme "Default [90px]  Preview" row: a fixed pixel
     width (set inline, so it needs !important to actually override here)
     made sense to keep every theme name lined up in a column on desktop,
     but on a narrow screen it just left an uneven, off-balance row.
     Split it evenly instead -- same 50/50 rule as every other two-button
     row on mobile. */
  /* The theme button sits inside a <form> (needed to submit which theme
     was picked) -- that form, not the button, is the row's actual flex
     child, so a flex rule on the button alone did nothing to the form
     around it. display:contents makes the form disappear from the box
     model so the button becomes the real flex item directly. */
  .theme-row-form{display:contents;}
  .theme-row-btn{width:auto !important; flex:1 1 0; text-align:center;}
  .theme-row-preview{flex:1 1 0; text-align:center;}

  /* Account page's Backend theme row: same "the form, not the button, is
     the actual flex item" issue, but no Preview link to balance against
     here -- so all three buttons should just grow evenly and fill the
     full row, not keep to a fixed width. */
  .theme-select-btn{flex:1; text-align:center;}

  /* Design page's "Download active"/"Download default template" links --
     full width and stacked instead of competing for room side by side on
     a narrow screen (their order in the markup already puts default
     ahead of active). */
  .design-download-link{width:100%; box-sizing:border-box; text-align:center;}
  .design-download-spacer{display:none;}

  /* Design page's background-image upload row: Choose-file claims its own
     full-width line same as everywhere else on this page. Upload (and
     Remove, when a background image already exists to remove) share the
     next line -- a lone Upload fills it (flex-grow with nothing to
     share against), and Upload+Remove together split it 50/50, same
     rule as every other one-or-two-button row on the site. (flex-basis
     100% on each, tried first, doesn't actually force separate lines
     here -- percentage flex-basis in a wrapping flex container doesn't
     reserve a full line the way it sounds like it should, it just sets
     each item's own hypothetical size without preventing a shrunk
     second item from sharing that line -- 0 avoids the ambiguity.) */
  .bg-upload-choose{flex:1 1 100%;}
  .bg-upload-btn,
  .bg-remove-btn{flex:1 1 0; text-align:center;}

  /* Design page's Choose-file/Preview/Upload row (per-page cards): Choose-
     file claims its own full-width line; Preview and Upload share the
     next line and split it 50/50, same reasoning as the background-image
     row above. The actual upload <form> renders separately and
     invisibly now (see design.php) -- Choose-file/Preview/Upload are
     genuine children of the .page-style-actions fieldset itself, no
     display:contents involved. */
  .page-style-choose{flex:1 1 100%;}
  .page-style-preview{flex:1 1 0; text-align:center;}
  .page-style-upload-btn{flex:1 1 0; text-align:center;}

  /* Platforms' key-format regex field + Save: the input's fixed 170px
     desktop width leaves the rest of the row empty on mobile instead of
     filling it, and Save just sits wherever that leaves it rather than
     flush right. Growing the input to fill the row naturally pushes the
     fixed-size Save button to the row's right edge. */
  .key-format-input{flex:1; width:auto !important;}

  /* Same row's "Upload icon" label ends up last in this action cell's
     reversed mobile order (see table tr td:last-child above), so it's
     the one now sitting at the row's right edge -- its own margin-right
     (there to space it apart from what follows it in normal desktop
     flow) just pushes it away from that edge instead, out of line with
     Save's flush-right position in the row above it. The gap between
     flex items already comes from td:last-child's own `gap`, so this
     margin is redundant here anyway. */
  .icon-upload-label{margin-right:0 !important;}

  /* A product's status pill + "Preview claim page" link wraps onto its
     own line under the page title here (the generic .topbar > div rule
     above already gives that line the row's full width). Split to the
     two edges of that full-width line -- status left, Preview right --
     instead of grouped together on one side. */
  .topbar-status-row{justify-content:space-between;}

  .toolbar-2col-row{width:100%;}
  .toolbar-2col-row > *{flex:1 1 0; min-width:0;}

  /* Users page's Roles table: its last column is a plain description,
     not Edit/Delete-style buttons, but the action-cell rule above
     (table tr td:last-child{display:flex; flex-direction:row-reverse;
     justify-content:space-between; ...}) doesn't know that -- it
     applies to every table's last cell, so it was right-aligning the
     "Permissions" label above the paragraph and scrambling the text
     flow. Opt this column back into normal block flow. */
  table tr td.role-desc{
    display:block !important;
    border-bottom:1px solid var(--line-soft) !important;
  }

  /* Redemptions' last column happens to be a plain "Date" value, not
     Edit/Delete-style actions -- same mismatch as .role-desc above, just
     wherever any table's last column is a .time-cell (a timestamp is
     never actions). Opt every one of those back into normal block flow
     -- label above value, left-aligned, like every other cell in the
     card -- instead of the row-reverse actions layout. */
  table tr td.time-cell:last-child{
    display:block !important;
    border-bottom:1px solid var(--line-soft) !important;
  }

  /* A lone topbar action (e.g. Feedback's "View feedback board" link) is
     just as content-sized as the theme-row buttons above -- once the
     topbar wraps it onto its own line (flex-wrap, see .topbar), it's left
     sitting at a fraction of the row's width instead of filling it. */
  .topbar > a.btn, .topbar > .btn, .topbar > a.campaign-select, .topbar > .campaign-select, .topbar > div{
    width:100%;
    box-sizing:border-box;
  }
  .topbar > a.btn, .topbar > .btn{justify-content:center; text-align:center;}

  /* A page-level filter row (Redemptions' campaign/product selects, ...)
     sized each <select> to its own text on mobile, same as any other
     unstretched control -- one alone fills the row, two split it evenly. */
  .filter-row > select{
    flex:1 1 0;
    min-width:0;
  }

  /* Updates page's "Current → Available" connector arrow is meant to sit
     between two blocks on the same row -- once they wrap onto separate
     rows here, it has nothing left to connect and just floats oddly next
     to whichever block it landed beside. flex-basis:100% forces it onto
     its own full-width row instead (a flex-wrap line break), landing
     between the two now-stacked blocks the same way it sits between them
     side-by-side on desktop. It's an *update*, so stacked it should read
     bottom-to-top like the version number is climbing: Available on top,
     Current underneath, arrow pointing up between them -- the reverse of
     the left-to-right desktop order (Current, arrow, Available), which
     stays document order and untouched. */
  /* The row's own 24px gap (sized for Current/arrow/Available sitting
     side by side on desktop) becomes a 24px gap above AND below the
     arrow once they're stacked -- 48px of empty space around a 20px
     icon. Shrink it to something sized for a stack instead; the arrow's
     own small margin (below) is enough breathing room either side of it. */
  .version-row{gap:4px;}
  /* The arrow (margin:2px auto below) already centers itself in its own
     full-width row -- Current/Available's own text stayed left-aligned
     though, so the arrow sat centered under nothing in particular
     instead of under the version number it's meant to connect. Centering
     both blocks lines everything up under the same midpoint. */
  .version-current, .version-available{text-align:center;}
  .version-current{order:3; margin-top:4px;}
  .version-arrow{
    order:2;
    flex-basis:100%;
    width:20px;
    height:20px;
    margin:2px auto;
    transform:rotate(-90deg);
  }
  .version-available{order:1;}

  /* The Update button isn't inside a .form-actions row (which already
     gets this treatment elsewhere) -- same "the only thing here should
     fill the row" rule applied directly. */
  #update-now-btn{width:100%; box-sizing:border-box;}
}
