/* GENERATED by tools/minigen.py for icebear. Do not edit.
   Loaded after zenon.css by app/main.py brand_css(), which asks for
   it only when the brand slug is not zenon and this file exists.
   Custom property overrides only: zenon.css is a committed build
   artefact and is never touched. */
:root,
:root[data-theme] {
  --color-base-300: #0d111f;
  --color-base-200: #151b2e;
  --color-base-100: #21283e;
  --color-base-content: #d8dff7;
  --color-primary: #6aa8e7;
  --color-accent: #b5e4f8;
}

/* ---- resellers/overrides/icebear/brand.append.css ---- */
/* icebear: Zenon's artwork does not appear on this site.
 *
 * WHY THIS IS CSS AND NOT A DELETION. Every one of these is a decorative
 * wrapper in a template, `<div class="gem-bg" aria-hidden="true"><img ...></div>`,
 * and the templates are shared. Deleting the image files alone would leave a
 * broken-image glyph in the middle of every page, which is worse than the
 * borrowed art it replaced. So the wrapper is hidden here and the files are
 * excluded from the tree in resellers/MANIFEST.toml: together nothing renders
 * and nothing 404s.
 *
 * WHAT THIS LEAVES. The pages fall back to the brand's own surface colours,
 * which is a deliberate flat look rather than an accident: --color-base-300 is
 * the page and 200 and 100 are the cards, all three derived from the configured
 * blue. It reads as intentional and it is honest, which a borrowed battle scene
 * is not.
 *
 * TO PUT ART BACK, drop the brand's own file into
 * resellers/overrides/icebear/app/static/ and delete the matching line here.
 * They are listed one per rule rather than as a group selector so a single one
 * can come back without touching the others.
 */
.pagebg      { display: none !important; }  /* landing, canvas: page-bg-map */
.gem-bg      { display: none !important; }  /* gem console: page-bg-gem */
.gem-pagebg  { display: none !important; }
.gem-dots    { display: none !important; }
.shop-bg     { display: none !important; }  /* shop: page-bg-shop */
.farm-bg     { display: none !important; }  /* farm console: page-bg-farm */
.ft-bg       { display: none !important; }  /* fort console: page-bg-fort */
.dash-hero   { display: none !important; }  /* hub: hero-dash */

/* The cloth banner, which is three images and unmistakably Zenon's lockup.
 * icebear's landing page does not use it at all; this catches anywhere else
 * that does. */
.banner, .banner-cap, .banner-mid { display: none !important; }

/* ---------------------------------------------------------------- the scene
 * THE SAME BACKGROUND AS THE FRONT PAGE, ON EVERY PAGE.
 *
 * SCOPED TO body.bg-base-300, which is the class base.html puts on every page
 * it renders and the landing page does not have, because the landing carries
 * its own .scene and .veil tuned for a single panel on an empty screen. Without
 * the scope both would paint and the landing would get two copies of the same
 * photograph at different opacities.
 *
 * TWO PSEUDO ELEMENTS RATHER THAN A body BACKGROUND. A background-image on body
 * scrolls with the document, so on a long console the snow walks off the top
 * and the page ends on flat colour. Fixed pseudo elements stay put. body's own
 * background-color has to go transparent or it would paint over both, so the
 * page colour moves up to html where it still covers the viewport.
 *
 * THE GRADIENT IS DOING REAL WORK, NOT DECORATION. A dashboard is text on
 * cards, and a battle scene behind small type is the classic way to make a site
 * look designed and read badly. So it is dark: the wash alone takes the picture
 * to roughly a quarter of its brightness, and the vertical pass drives the top
 * and bottom nearly to the page colour, which is where the chrome and the
 * footer sit. What survives is the middle, behind the content, as texture
 * rather than as a picture anyone is asked to look at.
 */
/* THE ONE LINE THAT SCALES ZENON'S OWN CSS. zenon.css is Tailwind built, so its
 * type and spacing are in rem and follow the root. 17px is a 6.25% lift: enough
 * that the site stops asking to be zoomed, small enough that no layout that fits
 * at 16 stops fitting. The px values in this file are scaled separately, since
 * rem would have let a reader's own browser setting resize the chrome. */
html { background: var(--color-base-300, #0d1120); font-size: 17px; }

body.bg-base-300 { background-color: transparent !important; }

body.bg-base-300::before {
  content: ""; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background: url("/static/ib-bg-siege.webp") center / cover no-repeat;
}

body.bg-base-300::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(13,17,32,.97) 0%,  rgba(13,17,32,.88) 14%,
      rgba(13,17,32,.80) 42%, rgba(13,17,32,.88) 80%,
      rgba(13,17,32,.97) 100%),
    linear-gradient(rgba(13,17,32,.55), rgba(13,17,32,.55));
}

/* ------------------------------------------------------------------ the rail
 * Layout for the sidebar that _chrome.html's page_header draws in place of
 * Zenon's top bar. The markup is there; only the geometry is here, because
 * geometry is the half that has to agree with base.html, which is NOT
 * overridden.
 *
 * THE CONTENT IS SHIFTED, NOT RESTRUCTURED. base.html wraps the page in
 * .flex-1 and puts .z-footer after it. Making those a flex sibling of the rail
 * would mean overriding base.html too, and every one of the fifteen templates
 * sets its own container inside it. A margin costs nothing and needs neither.
 */
/* --ib-ice IS THE WHITE, and it is a third type tier rather than a brighter
 * base-content. Body copy at 13.3:1 on a navy card would be shouting; a
 * heading and a figure are the two things that should. So base-content stays
 * where it is for prose and this sits above it on the four elements a reader
 * scans for: the page title, a product name, a price, and who is signed in.
 * 13.33:1 on a card, which is as close to white as a dark UI should go before
 * the type starts to glare. */
:root { --ib-rail: 296px; --ib-rail-sm: 74px; --ib-ice: #f0f7fc; }

.ib-rail {
  position: fixed; inset: 0 auto 0 0; width: var(--ib-rail); z-index: 60;
  background: var(--color-base-200, #161c2f);
  display: flex; flex-direction: column;
}
body.bg-base-300 > .flex-1,
body.bg-base-300 > .z-footer { margin-left: var(--ib-rail); }

/* ------------------------------------------------------- the footer, on screen
 * base.html already asks for this and has never got it. The body carries
 * `flex min-h-full flex-col` -- display:flex, flex-direction:column and
 * min-height:100% -- and the last of those does nothing at all: a percentage
 * min-height resolves against the containing block's height, <html> has no
 * height set, so it resolves against auto and computes to zero. The rule was
 * there, it parsed, it applied, and it bought nothing.
 *
 * So every short page -- terms on a tall monitor, an empty shop, the dashboard
 * of somebody who owns one bot -- ended with the footer floating wherever the
 * content happened to stop and the page ground showing under it. Giving <html>
 * a height makes the class that was always there start working. The explicit
 * dvh is for phones, where 100vh is measured with the address bar retracted and
 * would put the footer just off the bottom of the screen.
 *
 * NOT position:fixed. A pinned bar would sit on top of the content on every
 * long page and steal a strip of the console somebody is reading. This is the
 * other meaning of pinned: at the bottom of the screen when the page is short,
 * at the bottom of the page when it is not.
 */
html { height: 100%; }
body.bg-base-300 { min-height: 100dvh; }
body.bg-base-300 > .flex-1 { flex: 1 0 auto; }
body.bg-base-300 > .z-footer { flex-shrink: 0; }

/* The mark, on every page, on the brand's own scene. */
.ib-brand {
  height: 86px; display: flex; align-items: center; gap: 14px; padding: 0 16px;
  flex-shrink: 0; overflow: hidden; text-decoration: none;
  color: var(--color-base-content, #e6ecf5);
  background-image:
    linear-gradient(rgba(13,17,32,.62), rgba(13,17,32,.86)),
    url("/static/ib-bg-siege.webp");
  background-size: cover; background-position: center 40%;
}
.ib-brand img { width: 60px; height: 60px; flex-shrink: 0; display: block; }
.ib-brand b {
  font-size: 14px; letter-spacing: .1em; text-transform: uppercase;
  white-space: nowrap; font-weight: 700;
}

.ib-nav { flex: 1; padding: 12px 10px 8px; display: flex; flex-direction: column; gap: 3px; overflow-y: auto; }
.ib-spacer { flex: 1; min-height: 14px; }
/* READABLE, NOT DECORATIVE. The whole rail was drawn in #979eb5 at the body's
 * default weight, which is the grey a dark UI uses for a caption -- and every
 * word in here is navigation, the thing somebody is looking FOR rather than
 * reading around. The section headings were the worst of it at 3.92:1 against
 * an active row, under the 4.5:1 floor for text that size.
 *
 * Contrast against the rail, and against the lighter panel an active row sits
 * on, which is the one that has to hold up:
 *
 *     rows      #979eb5 -> #c2c9db     6.34 -> 10.21   (8.79 active)
 *     headings  #7c849e -> #9aa8c6     4.55 ->  7.08   (6.10 active)
 *     bot state #8890ab -> #a2abc4     5.34 ->  7.38   (6.36 active)
 *
 * Weight does the other half. 550 is a real step up from 400 without becoming
 * the semibold that would make every row shout as loudly as the one that is
 * open, which is still the brightest thing in the column.
 */
.ib-nav a {
  display: flex; align-items: center; height: 58px; padding: 0 12px;
  border-radius: 3px; color: #c2c9db; text-decoration: none; white-space: nowrap;
  font-weight: 550; transition: background .14s ease, color .14s ease;
}
.ib-nav .lb { font-size: 15px; }
.ib-nav a:hover { background: var(--color-base-100, #22293f); color: var(--color-base-content, #e6ecf5); }
.ib-nav a.on {
  background: var(--color-base-100, #22293f); color: var(--color-base-content, #e6ecf5);
  border-right: 2px solid var(--color-accent, #b5e4f8);
}
.ib-nav .ic { width: 46px; height: 46px; margin-right: 13px; flex-shrink: 0; display: grid; place-items: center; }
/* The one line drawing left, on the Dashboard row. It sits under the cell the
 * art fills rather than at the same 40px, and its stroke goes up with it: a
 * 1.6px line scaled to the full cell is a wireframe standing beside painted
 * objects, and line art always reads smaller than a filled shape of the same
 * measure, so equal boxes would leave it looking like the odd one out. */
.ib-nav .ic svg { width: 38px; height: 38px; stroke-width: 1.8; }
/* MORE AIR ABOVE A HEADING THAN BELOW IT, and more than there was. Bots sat
 * directly under Dashboard with 18px between them, so the heading read as part
 * of that row rather than as the start of the next group, and the bots looked
 * pinned to the top of the rail. The gap is what separates the groups; the
 * heading only names them. */
.ib-nav .sec {
  padding: 30px 12px 8px; font-size: 11.5px; letter-spacing: .14em;
  text-transform: uppercase; color: #9aa8c6; font-weight: 700; white-space: nowrap;
}
/* A bot row is two lines, so the row grows rather than the type shrinking. */
.ib-nav a:has(.bot) { height: 66px; }
.ib-nav .bot { display: flex; flex-direction: column; line-height: 1.25; }
.ib-nav .bn { font-size: 15px; font-weight: 650; }
.ib-nav .bm { font-size: 12.5px; color: #a2abc4; font-weight: 550; }
.ib-nav .bm i { font-style: normal; font-weight: 600; }
/* FOUR COLOURS BECAUSE THERE ARE FOUR ANSWERS, not two. Stale and Error are
   the ones worth a colour: both mean the bot is not doing the job and neither
   is something the customer switched off, so they must not share the grey that
   says "you stopped this". */
.ib-nav .bm .run  { color: #7fd6a2; }
.ib-nav .bm .warm { color: #e3c07b; }
.ib-nav .bm .bad  { color: #e5919b; }
.ib-nav .bm .off  { color: #a2abc4; }

/* Whatever the page passed through caller(), usually the user menu. Its own
 * styles come from zenon.css and are left alone; this only gives it a home at
 * the foot of the rail. */
.ib-actions {
  flex-shrink: 0; padding: 10px 12px; background: var(--color-base-300, #0d1120);
  display: flex; flex-direction: column; gap: 6px;
}
/* AN EMPTY ACTIONS STRIP IS NOT A STRIP. With nothing passed through caller()
   this was still 20px of the darker panel sitting between the last nav row and
   the avatar, which reads as a divider nobody drew. :has(> *) rather than
   :empty because the slot always holds whitespace. */
.ib-actions:not(:has(> *)) { display: none; }
.ib-actions .ml-auto { margin-left: 0 !important; }
.ib-actions a { white-space: nowrap; }

@media (max-width: 1100px) {
  .ib-rail { width: var(--ib-rail-sm); }
  body.bg-base-300 > .flex-1,
  body.bg-base-300 > .z-footer { margin-left: var(--ib-rail-sm); }
  .ib-brand { padding: 0; justify-content: center; }
  .ib-brand b, .ib-nav .lb, .ib-nav .sec { display: none; }
  .ib-nav a, .ib-nav a:has(.bot) { justify-content: center; padding: 0; height: 52px; }
  .ib-nav .ic { margin-right: 0; }
  .ib-actions { padding: 8px 6px; align-items: center; }
}

/* ------------------------------------------------- the person, and the way out
 * Zenon hides sign out inside the avatar dropdown, which is right on a top bar
 * where there is no room and wrong here, where there is. A customer who wants
 * to leave should not have to open a menu to find out how.
 */
.ib-who {
  flex-shrink: 0; display: flex; align-items: center; gap: 10px;
  height: 64px; padding: 0 13px 0 15px;
  background: var(--color-base-300, #0d1120);
}
.ib-av {
  width: 34px; height: 34px; border-radius: 50%; overflow: hidden; flex-shrink: 0;
  background: linear-gradient(180deg, var(--color-primary, #6aa8e7), #306ba6);
  color: #080d1c; display: grid; place-items: center; font-weight: 700; font-size: 13px;
}
.ib-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ib-me { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.ib-nm {
  font-size: 14.5px; font-weight: 600; color: var(--ib-ice, #f0f7fc);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ib-tr { font-size: 12px; color: #a2abc4; font-weight: 600;
         text-transform: uppercase; letter-spacing: .08em; }
.ib-out {
  margin-left: auto; flex-shrink: 0; width: 36px; height: 36px; border-radius: 4px;
  display: grid; place-items: center; color: #c2c9db; text-decoration: none;
  transition: background .14s ease, color .14s ease;
}
.ib-out svg { width: 21px; height: 21px; }
.ib-out:hover { background: var(--color-base-100, #22293f); color: #e5919b; }

/* ------------------------------------------------------- the language switcher
 * IT WAS TOO QUIET. A translucent pill the colour of the page, in a corner, on
 * a site whose whole point is that its customers do not read English. The one
 * control a Vietnamese visitor needs FIRST was the least visible thing on the
 * screen.
 *
 * Louder, deliberately: the brand accent as a border, a solid surface rather
 * than a wash of the page colour, the language written out rather than a two
 * letter code, and a caret so it reads as something that opens. It is the only
 * element on the page allowed to shout, because it is the only one that has to
 * be found by somebody who cannot read the rest.
 */
.ib-lang { position: fixed; top: 16px; right: 20px; z-index: 70; }
.ib-lang > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 9px;
  height: 44px; padding: 0 16px; border-radius: 9px;
  background: var(--color-base-100, #22293f);
  border: 1px solid var(--color-accent, #b5e4f8);
  color: var(--color-base-content, #e6ecf5);
  font-size: 14px; font-weight: 650; letter-spacing: .02em;
  box-shadow: 0 6px 20px rgba(0,0,0,.45);
  transition: background .14s ease, box-shadow .14s ease;
}
.ib-lang > summary::-webkit-details-marker { display: none; }
.ib-lang > summary::after {
  content: ""; width: 7px; height: 7px; margin-left: 1px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  opacity: .75;
}
.ib-lang[open] > summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.ib-lang > summary:hover {
  background: #32384e; box-shadow: 0 8px 26px rgba(0,0,0,.55);
}
.ib-lang .fl { font-size: 18.5px; line-height: 1; }
.ib-lang .sh { letter-spacing: .06em; }
.ib-lang-menu {
  position: absolute; top: 47px; right: 0; min-width: 196px; padding: 6px;
  border-radius: 9px; background: var(--color-base-200, #161c2f);
  border: 1px solid rgba(181,228,248,.22);
  box-shadow: 0 18px 44px rgba(0,0,0,.6);
  display: flex; flex-direction: column; gap: 2px;
}
.ib-lang-menu a {
  display: flex; align-items: center; gap: 10px; padding: 9px 11px;
  border-radius: 6px; color: #c0c5d6; text-decoration: none; font-size: 14.5px;
  white-space: nowrap;
}
.ib-lang-menu a:hover { background: var(--color-base-100, #22293f); color: var(--color-base-content, #e6ecf5); }
.ib-lang-menu a.on { color: var(--color-accent, #b5e4f8); background: rgba(181,228,248,.10); }

@media (max-width: 1100px) {
  .ib-who { padding: 0; justify-content: center; gap: 0; }
  .ib-me { display: none; }
  .ib-out { margin-left: 0; display: none; }
  .ib-lang { top: 10px; right: 12px; }
  .ib-lang > summary { height: 40px; padding: 0 12px; }
}

/* ------------------------------------------------------- the doubled avatar
 * THE DROPDOWN IS HIDDEN, not the rail's own block. Every template passes
 * user_menu() through caller(), and user_menu draws its own avatar, so the rail
 * showed the person twice: once as the dropdown's summary and once in .ib-who
 * directly beneath it.
 *
 * SAFE ONLY BECAUSE ITS CONTENTS MOVED FIRST. The dropdown held Billing, Shop,
 * Affiliate, Admin and Sign out. Billing is off on this brand, Shop and Sign
 * out were already in the rail, Admin is now, and Affiliate is gone entirely:
 * ZENON_SHOW_AFFILIATE=0 404s the routes, so there is nothing left to link to.
 * Nothing in the dropdown is reachable by no other route. Hiding it before
 * moving Admin would have left the owner's own console reachable only by
 * typing the URL.
 */
.ib-actions .z-switch-wrap { display: none !important; }
/* With the menu gone the wrapper is usually empty; collapse it rather than
   leaving a band of padding above the person. :empty does not match whitespace,
   so the padding goes and the gap is the flex gap. */
.ib-actions { padding: 0 12px; gap: 0; }
.ib-actions:not(:has(> *:not(.z-switch-wrap))) { padding: 0; }

/* ==================================================================== cards
 * THE CONTENT-SIDE HALF OF THE SIDEBAR DESIGN. The rail above established the
 * vocabulary: surfaces rather than borders, 8px corners, the accent used once
 * per element and never twice. This carries it onto the page itself, starting
 * with the shop, because the shop is the first page a customer with no bot
 * ever sees -- /dash redirects there the moment it finds nothing to show.
 *
 * NOT SCOPED TO .ib-shop. These names are the brand's card language and the
 * hub is the next page that wants them; scoping them to one page would mean
 * copying them to the second caller.
 *
 * NO BORDERS ANYWHERE. base-100 on base-300 is a two-step tonal lift and that
 * is what separates a card from the page. The one exception is the lead card's
 * top rule, which is not decoration: with several products it is the only mark
 * saying which one this brand is actually about.
 */
.ib-h1 {
  margin: 0 0 6px; font-size: 26px; font-weight: 700; letter-spacing: -.01em;
  color: var(--ib-ice, #f0f7fc);
}
.ib-sub { margin: 0; color: #979eb5; font-size: 15px; max-width: 62ch; }

.ib-sec-h {
  margin: 34px 0 14px; font-size: 12px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: #7c849e;
}

/* AUTO-FIT WITH A CEILING, and the ceiling is the point. This brand sells one
 * product. auto-fill would stretch a lone card across a 1200px page, which
 * puts its glyph and its button a hand-span apart with nothing between them;
 * auto-fit alone does the same. The max-width on the track holds a single card
 * at a readable measure and still lets a second and third sit beside it. */
.ib-cards {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(320px, 450px));
  align-items: start;
}

.ib-card {
  position: relative; display: flex; flex-direction: column;
  background: var(--color-base-100, #22293f);
  border-radius: 9px; padding: 20px 22px 18px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .28);
}
/* The one product this brand is about. A 2px accent edge, the same device the
 * rail uses for the current row, so "this one" means the same thing in both
 * halves of the site. */
.ib-card.is-lead::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  border-radius: 8px 8px 0 0; background: var(--color-accent, #b5e4f8);
}

/* CENTRED, not top-aligned. With the tagline gone the head is a mark beside a
 * name and nothing else, so there is no second line for the glyph to hang from
 * and flex-start just left it sitting high. */
.ib-card-top { display: flex; align-items: center; gap: 15px; }
.ib-card-mark {
  flex-shrink: 0; width: 78px; height: 78px; border-radius: 12px;
  display: grid; place-items: center;
  background: var(--color-base-200, #161c2f);
}
/* THE GEM IS THE ONLY PICTURE ON THIS CARD, so it carries the product the way
 * a photograph carries a listing, and at 28px it was a bullet point. The tile
 * grew with it rather than the gem being crammed into the old one: a 46px
 * square around a 46px gem is a frame, not a mark.
 *
 * SHARP AT 2x AGAIN. The first cut of this used the 53x56 gems.webp and was
 * being upscaled about 1.9x by the time it reached this size. The source is
 * the 100x100 original now, so 54px is under 2x and nothing is invented. That
 * is the ceiling: past 50px tall this starts upscaling too. */
.ib-card-mark img { height: 58px; width: auto; display: block; }
.ib-card-id { min-width: 0; }
/* The name is the card's headline now that nothing sits under it. 20px and
 * balanced wrapping, because "IceBear Bots Gem Bot" breaks over two lines in
 * the narrow column and the default break leaves one word stranded. */
.ib-card-id h3 {
  margin: 0; font-size: 21.5px; font-weight: 650; line-height: 1.25;
  text-wrap: balance; color: var(--ib-ice, #f0f7fc);
}
.ib-card-id p { margin: 0; font-size: 14px; line-height: 1.5; color: #979eb5; }

/* The bullets. A dot in the accent rather than a list marker, because the
 * default marker inherits the text colour and at this size reads as a speck of
 * dirt rather than as punctuation. */
.ib-card-feat { margin: 16px 0 0; padding: 0; list-style: none; }
.ib-card-feat li {
  position: relative; padding: 0 0 0 18px; margin-bottom: 7px;
  font-size: 14px; line-height: 1.45; color: #c0c5d6;
}
/* The last bullet carries a line of its own in the operator's copy, the one
 * that qualifies the access rather than adding a feature. It sits under its
 * sentence at a step down in size and tone, so the list still reads as seven
 * bullets and not eight. */
.ib-card-feat li small {
  display: block; margin-top: 2px;
  font-size: 12.5px; line-height: 1.4; color: #8e97b0;
}

.ib-card-feat li::before {
  content: ""; position: absolute; left: 3px; top: 8px;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--color-accent, #b5e4f8); opacity: .85;
}

/* PINNED TO THE FOOT with margin-top:auto, so two cards of different heights
 * still put their buttons on one line. The separator is a tonal band rather
 * than a rule, for the same reason the rail has no borders. */
.ib-card-foot {
  margin-top: auto; padding-top: 16px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.ib-card-feat + .ib-card-foot,
.ib-card-top + .ib-card-foot { margin-top: 18px; }

.ib-price {
  font-size: 21.5px; font-weight: 700; white-space: nowrap;
  color: var(--ib-ice, #f0f7fc);
}
.ib-price i {
  font-style: normal; font-size: 12px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: #8890ab; margin-right: 5px;
}
/* What stands where a price would. Not styled as a price: it is a fact about
 * how this brand sells, and setting it at 20px bold would make an absent
 * number look like a number. */
.ib-price-note {
  font-size: 13px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: #8890ab;
}

.ib-btn {
  display: inline-flex; align-items: center; gap: 8px; height: 44px;
  padding: 0 20px; border: 0; border-radius: 7px; cursor: pointer;
  background: var(--color-primary, #6aa8e7); color: #080d1c;
  font: inherit; font-size: 14.5px; font-weight: 700; letter-spacing: .01em;
  text-decoration: none; white-space: nowrap;
  transition: background .14s ease, transform .14s ease;
}
.ib-btn:hover { background: #91bfed; }
.ib-btn:active { transform: translateY(1px); }

/* The missing-channel case, and it deliberately does not look like a control.
 * Same treatment landing.html gives a missing invite: amber, plain text, no
 * affordance, so nobody clicks it and nobody mistakes it for the way to buy. */
.ib-unset { font-size: 13.5px; color: #d8b27a; }

.ib-fineprint { margin: 30px 0 0; font-size: 13px; color: #8990a8; }

@media (max-width: 1100px) {
  .ib-cards { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
}

/* ======================================================= the consoles
 * THE RAIL REACHES THE CONSOLES NOW. gem.html, like farm, fb1, fort and the
 * admin shell, drew its own z-topbar and never called page_header, so this
 * brand had a sidebar on every page except the ones a customer actually lives
 * in. gem.html asks `sidebar` now and calls page_header instead; what is left
 * here is the geometry of what that leaves behind.
 *
 * The content needs no shift: base.html puts {% block body %} inside .flex-1,
 * which the rail rule above already moves.
 */

/* Room for the dock. A fixed control that covers the last card is the usual
 * way this pattern goes wrong, and the config tab ends in a Save row. */
body.bg-base-300 .gem-page { padding-bottom: 150px; }

/* The tabs, over the content they switch rather than inside chrome that is no
 * longer there. The negative top pulls them up against the hero. */
.ib-contabs { margin: 18px auto 0; }
.ib-contabs .z-nav { border-bottom: 1px solid rgba(255,255,255,.07); }

/* ----------------------------------------------------------------- the dock
 * Start and Stop are the only irreversible controls on this page. In the bar
 * they scrolled away and shared an edge with a dropdown and a brand mark.
 *
 * CENTRED ON THE CONTENT, not the viewport: centring on the viewport pushes it
 * right by half the rail, which reads as a rendering fault rather than as a
 * choice. The forms inside are untouched, so this changes where the button is
 * and nothing about what it posts.
 */
.ib-dock {
  position: fixed; z-index: 45;
  left: calc(var(--ib-rail) + ((100vw - var(--ib-rail)) / 2));
  transform: translateX(-50%);
  bottom: 22px; max-width: calc(100vw - var(--ib-rail) - 48px);
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 14px;
  background: rgba(22,28,47,.94);
  border: 1px solid rgba(181,228,248,.16);
  box-shadow: 0 18px 44px rgba(0,0,0,.55);
  backdrop-filter: blur(9px);
}
.ib-dock form { margin: 0; }
.ib-dock-state { display: flex; align-items: center; padding-left: 4px; }

.ib-dbtn {
  appearance: none; border: 0; cursor: pointer; font: inherit;
  font-size: 14.5px; font-weight: 700; height: 46px; padding: 0 22px;
  border-radius: 10px; display: inline-flex; align-items: center; gap: 8px;
  white-space: nowrap; transition: background .14s ease, transform .12s ease;
}
.ib-dbtn:active { transform: translateY(1px); }
.ib-dbtn.is-start { background: var(--color-primary, #6aa8e7); color: #080d1c; }
.ib-dbtn.is-start:hover { background: #91bfed; }
/* Stop is not a primary action wearing red. It is the destructive one, so it
 * is quieter than Start and only fills in on hover: a customer reaching for it
 * has decided, and a customer who has not should not be drawn to it. */
.ib-dbtn.is-stop { background: rgba(229,145,155,.15); color: #e5919b; }
.ib-dbtn.is-stop:hover { background: rgba(229,145,155,.26); }

/* ------------------------------------------------------------- the surfaces
 * z-card carries a hairline border everywhere on Zenon. This brand separates
 * surfaces with a tonal step instead, which is the one rule worth taking
 * wholesale from the reference design: a 1px line between two dark navies is a
 * scar, a tonal step is a room. Same radius and shadow as the shop's own card,
 * so the two halves of the site are one place.
 */
body.bg-base-300 .z-card {
  background: var(--color-base-100, #22293f);
  border: 0; border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0,0,0,.28);
}

/* THE CONTROL BAR BECOMES THE HERO. Same markup, same governor line, same
 * conduct chip; it just stops being a strip and becomes the card the page
 * starts with, with the brand's own tint across it the way the preview drew
 * it. */
body.bg-base-300 .z-ctrlbar.gem-bar {
  border: 0; border-radius: 10px; padding: 20px;
  background-color: var(--color-base-100, #22293f);
  background-image:
    linear-gradient(135deg, rgba(181,228,248,.10) 0%, rgba(106,168,231,.07) 48%, rgba(0,0,0,0) 100%),
    radial-gradient(circle at top right, rgba(181,228,248,.13), transparent 52%);
  box-shadow: 0 10px 30px rgba(0,0,0,.28);
}

@media (max-width: 1100px) {
  .ib-dock { left: calc(var(--ib-rail-sm) + ((100vw - var(--ib-rail-sm)) / 2));
             max-width: calc(100vw - var(--ib-rail-sm) - 24px); }
}

/* ==================================================== the attach-a-governor board
 * NO PLANK, NO KNIGHT. .gem-board and .gem-modal.is-panel draw
 * /static/panel-board.png behind the sign-in form: a torn parchment with a
 * suit of armour occupying its left third, and an ink palette tuned for wood.
 * It is unmistakably Zenon's, and it is the FIRST SCREEN a new customer of
 * this brand ever sees, straight after paying.
 *
 * THIS IS NOT A NEW DESIGN, it is a branch the trunk already has. Below 560px
 * _gem_board_css.html drops the board for exactly the reason it should be
 * dropped here -- an image with a fixed 1024/609 aspect cannot hold a heading,
 * two labelled inputs and two buttons -- and falls back to a plain card with
 * the console's own ink. All this does is take that branch at every width and
 * dress it in the brand's card language.
 *
 * ONE CONSEQUENCE WORTH STATING: with ::before gone the browser never fetches
 * panel-board.png, so nothing here needs a MANIFEST exclusion to stop it being
 * served. The file ships and is simply never asked for.
 */
.gem-board::before,
.gem-modal.is-panel::before { display: none !important; }

.gem-board,
.gem-modal.is-panel {
  aspect-ratio: auto !important;
  width: min(560px, 100%) !important;
  margin: 0 auto !important;
  padding: 26px 26px 24px !important;
  border: 0 !important;
  border-radius: 10px;
  background: var(--color-base-100, #22293f) !important;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .28);
  filter: none !important;
}

/* The inner box was absolutely positioned into the parchment's tan field and
   scrolled inside it. With no parchment it is just the card's contents. */
.gem-board .gem-panel-in,
.gem-modal.is-panel .gem-panel-in {
  position: static !important; inset: auto !important;
  padding: 0 !important; overflow: visible !important;
}

/* INK, BACK TO THE CONSOLE'S OWN. The dark browns only ever made sense on
   wood, and on a navy card they are the reason the second button in the
   operator's screenshot could not be read at all. The trunk's own comment on
   that colour says it was "reported as barely visible, and it was". */
.gem-board h2, .gem-modal.is-panel h2 {
  color: var(--ib-ice, #f0f7fc) !important;
  font-size: 19px !important; margin-bottom: 6px;
}
.gem-board .gem-modal-s, .gem-modal.is-panel .gem-modal-s {
  color: #94acb8 !important; font-size: 14px !important; font-weight: 500 !important;
}
.gem-board .gem-modal-n, .gem-modal.is-panel .gem-modal-n { color: #8990a8 !important; }
.gem-board label, .gem-modal.is-panel label,
.gem-board .gem-modal-f label, .gem-modal.is-panel .gem-modal-f label {
  color: #c0c5d6 !important; font-weight: 600 !important;
}
.gem-board .gem-pick b, .gem-modal.is-panel .gem-pick b { color: var(--ib-ice, #f0f7fc) !important; }
.gem-board .gem-pick span, .gem-modal.is-panel .gem-pick span { color: #94acb8 !important; }

/* The fields, which were lit boxes sitting on tan. */
.gem-board input, .gem-board select,
.gem-modal.is-panel input, .gem-modal.is-panel select {
  background: var(--color-base-200, #161c2f) !important;
  border: 1px solid rgba(181, 228, 248, .16) !important;
  color: var(--color-base-content, #e6ecf5) !important;
  border-radius: 8px !important;
}
.gem-board input::placeholder, .gem-modal.is-panel input::placeholder { color: #7c849e !important; }

/* TWO BUTTONS, AND THE SECOND ONE HAS TO BE READABLE. "No password? Email a
   code" is the only way in for an account that has none, and on the parchment
   it rendered as dark text on dark and was effectively a dead control. */
.gem-board .gem-modal-row, .gem-modal.is-panel .gem-modal-row {
  display: flex; gap: 10px; flex-wrap: wrap; margin-top: 4px;
}
.gem-board .gem-modal-row > *, .gem-modal.is-panel .gem-modal-row > * { flex: 1 1 180px; }
.gem-board .z-btn-start, .gem-modal.is-panel .z-btn-start {
  background: var(--color-primary, #6aa8e7) !important; color: #080d1c !important;
  border: 0 !important; border-radius: 9px !important; font-weight: 700 !important;
  min-height: 46px;
}
.gem-board .z-btn-ghost, .gem-modal.is-panel .z-btn-ghost {
  background: var(--color-base-200, #161c2f) !important;
  border: 1px solid rgba(181, 228, 248, .22) !important;
  color: var(--color-base-content, #e6ecf5) !important;
  border-radius: 9px !important; font-weight: 600 !important; min-height: 46px;
}

/* ============================================== the Config tab, as /render/gem
 * THE MARKUP ALREADY HAS THE RIGHT SHAPE. _gem_config.html emits
 *   <div class="cfg-row">
 *     <span class="cfg-lbl"><b>label</b><span>help</span></span>
 *     <span class="cfg-ctl">...control...</span>
 *   </div>
 * which is exactly the row the preview drew: name and help on the left, the
 * control hard right on a shared edge. So this is presentation only. The form,
 * the field names, the CSRF token and the POST to /dash/gem/<id>/config are
 * untouched, which is the whole reason it is done here rather than by forking
 * a 700 line template: nothing about saving can break in a stylesheet.
 *
 * WHAT ACTUALLY CHANGES. Zenon rules each row with a border on TOP and shouts
 * its short labels in letter-spaced uppercase mono. Thirty-three rows of that
 * is a wall. The preview uses a hairline UNDER each row so a section reads as a
 * list that ends, sentence-case labels so the eye can skim them, and one fixed
 * control column so the right edge never moves.
 */
body.bg-base-300 .cfg-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px 24px; flex-wrap: wrap;
  margin-top: 0; padding: 13px 0;
  border-top: 0; border-bottom: 1px solid rgba(255, 255, 255, .065);
}
body.bg-base-300 .cfg-row:last-child { border-bottom: 0; }

body.bg-base-300 .cfg-row .cfg-lbl { flex: 1 1 260px; gap: 3px; }
body.bg-base-300 .cfg-row .cfg-lbl b {
  font: 500 15px var(--font-sans); letter-spacing: 0;
  color: var(--color-base-content, #e6ecf5);
}
body.bg-base-300 .cfg-row .cfg-lbl > span {
  font: 400 13.5px var(--font-sans); line-height: 1.45; color: #979eb5;
}

/* THE EDGE THAT MUST NOT MOVE. Thirty-three rows only stay scannable if every
 * control starts at the same x; a column that shrinks to its content makes the
 * page look ragged and makes a toggle hard to find twice. */
body.bg-base-300 .cfg-row .cfg-ctl {
  margin-left: auto; flex: 0 0 auto; min-width: 128px;
  display: flex; align-items: center; justify-content: flex-end;
}

/* THE ONE ROW THAT GIVES UP THE SHARED EDGE, and it has to be said again here
 * because the rule above is `body.bg-base-300 .cfg-row .cfg-ctl` and outweighs
 * the trunk's `.cfg-row.is-stacked .cfg-ctl`. flex: 0 0 auto is what makes the
 * overflow visible rather than merely tight: the column cannot shrink, so a
 * sentence set beside the buttons carries both past the card's right edge.
 * A pick that explains its answers stacks: label, buttons, sentence. */
body.bg-base-300 .cfg-row.is-stacked { align-items: flex-start; }
body.bg-base-300 .cfg-row.is-stacked .cfg-lbl { flex: 1 1 100%; }
body.bg-base-300 .cfg-row.is-stacked .cfg-ctl {
  margin-left: 0; margin-top: 8px; width: 100%; min-width: 0; flex: 1 1 100%;
  flex-direction: column; align-items: flex-start; justify-content: flex-start;
}

/* A SHORT LABEL IS STILL A LABEL. cfg-k is Zenon's uppercase mono, used for
 * one-word names like "March from" and "slot". At 12px with .13em tracking it
 * reads as a system tag rather than as the name of a setting. */
body.bg-base-300 .cfg-k {
  font: 500 14px var(--font-sans); letter-spacing: 0; text-transform: none;
  color: var(--color-base-content, #e6ecf5);
}

/* Subsection heads, which the preview sets small, tracked and faint so they
 * separate without competing with the settings under them. */
body.bg-base-300 .cfg-h {
  font: 600 11.5px var(--font-sans); letter-spacing: .14em;
  text-transform: uppercase; color: #8990a8; margin: 4px 0 2px;
}

/* Each section is one card, in the brand's own surface, rather than a panel
 * with its own border and its own centred blurb. */
body.bg-base-300 .cfg-sect {
  background: var(--color-base-100, #22293f);
  border: 0; border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .28);
}
body.bg-base-300 .cfg-sect .z-blurb { text-align: left; color: #979eb5; }
body.bg-base-300 .cfg-sect .z-cardfoot {
  border-top: 1px solid rgba(255, 255, 255, .065); color: #8990a8;
}
body.bg-base-300 .cfg-head { text-align: left; }

/* The controls. Raised off the card rather than outlined on it, which is the
 * same step the shop's own fields take. */
body.bg-base-300 .cfg-sel {
  background: var(--color-base-200, #161c2f);
  border: 1px solid rgba(181, 228, 248, .16); border-radius: 8px;
  padding: 5px 10px 5px 12px;
}
body.bg-base-300 .cfg-sel > span {
  font: 500 11.5px var(--font-sans); letter-spacing: .06em; color: #8990a8;
}
body.bg-base-300 .cfg-sel select,
body.bg-base-300 .z-num, body.bg-base-300 select.z-num {
  color: var(--color-base-content, #e6ecf5); min-height: 38px;
}
body.bg-base-300 .z-num, body.bg-base-300 select.z-num {
  background: var(--color-base-200, #161c2f);
  border: 1px solid rgba(181, 228, 248, .16); border-radius: 8px;
}
body.bg-base-300 .cfg-sel select option,
body.bg-base-300 .z-num option { background: #161c2f; }

/* THE SAVE BAR IS THE DOCK. The console already grew a fixed dock for Start and
 * Stop; a second bar with its own geometry sitting near it would read as two
 * competing footers. Same surface, same blur, same radius -- it simply sits
 * above the power dock rather than beside it, because Save is the frequent
 * action and Stop is the one that must never be hit by accident. */
body.bg-base-300 .z-savebar {
  position: fixed; z-index: 44;
  left: calc(var(--ib-rail) + ((100vw - var(--ib-rail)) / 2));
  transform: translateX(-50%);
  bottom: 86px; width: min(620px, calc(100vw - var(--ib-rail) - 48px));
  border: 1px solid rgba(181, 228, 248, .16); border-radius: 14px;
  background: rgba(22, 28, 47, .94);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .55);
  backdrop-filter: blur(9px);
}
body.bg-base-300 .z-savebar-in { padding: 10px 12px; }

@media (max-width: 1100px) {
  body.bg-base-300 .z-savebar {
    left: calc(var(--ib-rail-sm) + ((100vw - var(--ib-rail-sm)) / 2));
    width: min(620px, calc(100vw - var(--ib-rail-sm) - 24px));
  }
}

/* ======================================== the gem Overview, as /render/gem
 * The markup is resellers/overrides/icebear/app/templates/_gem_overview.html.
 * This is only its geometry, in the same vocabulary the shop card and the rail
 * already use, so the console and the storefront read as one place.
 */
.ib-hero {
  display: flex; align-items: stretch; justify-content: space-between; gap: 32px;
  background-color: var(--color-base-100, #22283e);
  background-image:
    linear-gradient(135deg, rgba(181,228,248,.10) 0%, rgba(106,168,231,.07) 48%, rgba(0,0,0,0) 100%),
    radial-gradient(circle at top right, rgba(181,228,248,.13), transparent 52%);
  border-radius: 10px; padding: 20px; margin-bottom: 16px;
  box-shadow: 0 10px 30px rgba(0,0,0,.28);
}
.ib-hero-main { min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.ib-hero-id { display: flex; align-items: center; gap: 16px; }
.ib-hero-tile { width: 66px; height: 66px; border-radius: 14px; flex-shrink: 0;
  display: grid; place-items: center; background: var(--color-base-200, #161c2f); }
/* A SQUARE BOX RATHER THAN A HEIGHT. The gem that sits here is square art in
 * a square file; the cluster it replaced was wide, which is why this was sized
 * on height and left to find its own width. object-fit keeps either honest. */
.ib-hero-tile img { height: 52px; width: 52px; object-fit: contain; display: block; }
.ib-hero h1 { margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -.01em;
  color: var(--ib-ice, #f0f7fc); }
.ib-hero-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 5px; }
.ib-kind { font-size: 13px; font-weight: 500; color: #979eb5;
  text-transform: uppercase; letter-spacing: .05em; }

/* A dot and a word, not a filled block. The state changes while somebody is
 * looking at it, and a solid badge flashing across the header is louder than
 * the change deserves. */
.ib-badge { display: inline-flex; align-items: center; gap: 7px; height: 25px;
  padding: 0 11px; border-radius: 999px; font-size: 12.5px; font-weight: 600; }
.ib-badge .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.ib-badge.is-run { color: #7fd6a2; background: rgba(127,214,162,.13); }
.ib-badge.is-off { color: #979eb5; background: rgba(151,158,181,.13); }
.ib-badge.is-trial { color: #e3c07b; background: rgba(227,192,123,.14);
  font-size: 11px; letter-spacing: .1em; }
.ib-badge .dot.pulse { animation: ibpulse 1.8s ease-in-out infinite; }
@keyframes ibpulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }

.ib-pills { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ib-pill { background: var(--color-base-200, #161c2f); color: #979eb5;
  font-size: 12.5px; padding: 3px 9px; border-radius: 5px; white-space: nowrap; }
.ib-pill.is-cond { color: var(--color-accent, #b5e4f8); background: rgba(181,228,248,.12); }
.ib-tiny { margin: 0; font-size: 12px; font-weight: 500; color: #8990a8; }
.ib-lbl { font-size: 12.5px; font-weight: 500; color: #979eb5;
  text-transform: uppercase; letter-spacing: .05em; }
.ib-sec-t { margin: 0; font-size: 15px; font-weight: 600; color: var(--color-base-content, #e6ecf5); }

.ib-hero-side { width: 296px; flex-shrink: 0; display: flex; flex-direction: column;
  gap: 12px; justify-content: center; }
.ib-lic-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ib-lic-t { display: flex; align-items: center; gap: 8px; }
.ib-lic-add { width: 25px; height: 25px; border-radius: 6px; display: grid;
  place-items: center; background: var(--color-base-200, #161c2f);
  color: var(--color-accent, #b5e4f8); text-decoration: none; }
.ib-lic-add svg { width: 14px; height: 14px; }
.ib-lic-left { font-family: var(--font-mono); font-size: 14px;
  font-variant-numeric: tabular-nums; color: var(--ib-ice, #f0f7fc); font-weight: 600; }
.ib-track { height: 8px; border-radius: 4px; background: rgba(255,255,255,.06); overflow: hidden; }
.ib-fill { height: 100%; border-radius: inherit; background: var(--color-primary, #6aa8e7); }
/* Under four days is the last week of a month, which is when somebody has to
 * act rather than notice. */
.ib-fill.is-low { background: #e3c07b; }

.ib-card { background: var(--color-base-100, #22283e); border-radius: 10px;
  padding: 20px; margin-bottom: 16px; box-shadow: 0 10px 30px rgba(0,0,0,.28); }
.ib-card-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }

/* The one card about a PERSON rather than about the machine. */
.ib-gov { position: relative; padding-left: 24px; }
.ib-gov::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 4px;
  border-radius: 10px 0 0 10px; background: var(--color-accent, #b5e4f8); }
.ib-gov-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ib-gov-av { width: 56px; height: 56px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; font-weight: 700; font-size: 17px; color: #080d1c;
  background: linear-gradient(180deg, var(--color-primary, #6aa8e7), #306ba6); }
.ib-gov-id { min-width: 0; flex: 1 1 190px; }
.ib-eyebrow { font-size: 11.5px; letter-spacing: .12em; text-transform: uppercase; color: #8990a8; }
.ib-gov-name { margin: 2px 0; font-size: 19.5px; font-weight: 650; color: var(--ib-ice, #f0f7fc); }
.ib-gov-sub { font-size: 13px; color: #979eb5; font-family: var(--font-mono); }
.ib-gov-stats { display: flex; gap: 28px; flex-wrap: wrap; }
.ib-gov-stat { display: flex; flex-direction: column; gap: 3px; }
.ib-gov-stat .v { font-size: 15px; font-weight: 600; color: var(--color-base-content, #e6ecf5); }
.ib-gov-stat .v.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.ib-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 18px 24px; margin-top: 16px; }
.ib-fact { display: flex; flex-direction: column; gap: 4px; }
.ib-fact .v { font-size: 19px; font-weight: 650; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; color: var(--ib-ice, #f0f7fc); }
.ib-fact .v.plain { font-family: inherit; font-weight: 600; font-size: 15px; }

.ib-tot { text-align: right; }
.ib-tot strong { display: block; font-size: 21.5px; font-weight: 700;
  font-family: var(--font-mono); color: var(--ib-ice, #f0f7fc); }
.ib-tot span { font-size: 11.5px; color: #8990a8; text-transform: uppercase; letter-spacing: .08em; }
.ib-marches { list-style: none; margin: 16px 0 0; padding: 0; }
.ib-march { display: flex; align-items: center; gap: 14px; padding: 12px 0;
  border-top: 1px solid rgba(255,255,255,.065); }
.ib-march:first-child { border-top: 0; }
.ib-m-cmd { display: flex; flex-shrink: 0; }
.ib-m-cmd span { width: 34px; height: 34px; border-radius: 8px;
  background: var(--color-base-200, #161c2f); display: grid; place-items: center;
  font-size: 11.5px; font-weight: 700; color: #979eb5; margin-left: -8px;
  border: 2px solid var(--color-base-100, #22283e); }
.ib-m-cmd span:first-child { margin-left: 0; }
.ib-m-where { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.ib-m-where strong { font-size: 14.5px; font-weight: 600; display: flex;
  align-items: center; gap: 8px; flex-wrap: wrap; color: var(--color-base-content, #e6ecf5); }
.ib-m-where small { font-size: 12.5px; color: #979eb5; font-family: var(--font-mono); }
.ib-tag { font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 4px; background: rgba(181,228,248,.14);
  color: var(--color-accent, #b5e4f8); }
.ib-m-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: #6aa8e7; }
.ib-m-dot.is-gathering { background: #7fd6a2; }
.ib-empty { margin: 16px 0 0; font-size: 14px; color: #979eb5; }

.ib-console { margin-top: 16px; max-height: 260px; overflow-y: auto; border-radius: 8px;
  background: #0c0f18; padding: 12px 14px; font-family: var(--font-mono);
  font-size: 13px; line-height: 1.75; }
.ib-cline { display: flex; gap: 12px; }
.ib-ctime { color: #5d6684; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.ib-ctext { color: #cbd0dd; }
.ib-cline.is-warn .ib-ctext { color: #e3c07b; }
.ib-cline.is-bad .ib-ctext { color: #e5919b; }

.ib-enf { list-style: none; margin: 16px 0 0; padding: 0; }
.ib-enf li { padding: 10px 0; border-top: 1px solid rgba(255,255,255,.065);
  display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; }
.ib-enf li:first-child { border-top: 0; }
.ib-enf b { color: #e5919b; text-transform: uppercase; font-size: 12px; letter-spacing: .08em; }
.ib-enf span { color: var(--color-base-content, #e6ecf5); font-size: 14px; }
.ib-enf small { margin-left: auto; color: #8990a8; font-size: 12px; }

/* THE OLD LAYOUT'S FURNITURE, which this tab no longer draws. The range picker,
 * the chart and the four stat tiles belonged to a page that asked a different
 * question; leaving their rules in would only matter if something still emitted
 * them, and nothing does. Listed rather than deleted because the trunk template
 * still has them and a future merge may bring them back. */
@media (max-width: 1100px) {
  .ib-hero { flex-direction: column; gap: 22px; }
  .ib-hero-side { width: auto; }
}

/* ONE CARD, NOT TWO. The hero and the governor block are separate elements --
 * the governor is conditional, and nesting it inside the header would have
 * meant the header carrying an {% if %} around its own closing tag. Joined
 * here instead: the hero drops its bottom corners and its gap ONLY when a
 * governor block actually follows it, so a bot with nothing attached still
 * renders one complete card rather than one with a flat bottom edge.
 */
.ib-hero:has(+ .ib-hero-gov) { margin-bottom: 0; border-radius: 10px 10px 0 0; }
.ib-hero-gov {
  background: var(--color-base-100, #22283e);
  border-top: 1px solid rgba(255, 255, 255, .075);
  border-radius: 0 0 10px 10px;
  padding: 18px 20px; margin-bottom: 16px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .28);
}
/* The avatar shrinks now that it sits under a 66px tile rather than beside
 * nothing: two circles of the same weight, stacked, read as two subjects. */
.ib-hero-gov .ib-gov-av { width: 46px; height: 46px; font-size: 15px; }
.ib-hero-gov .ib-gov-name { font-size: 17.5px; }

/* ================================== the Configuration workspace, arcane's shape
 * Header with a search box, a sidebar of categories with counts, one category
 * on screen at a time. The markup is built by the script at the foot of
 * gem.html from the sections this console already renders; this is its
 * geometry, in the brand's own colours rather than arcane's purple.
 */
.ib-cfgws { margin-top: 4px; }

.ib-cfg-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  background-color: var(--color-base-100, #22283e);
  background-image:
    linear-gradient(135deg, rgba(181,228,248,.08), transparent 55%),
    radial-gradient(circle at top right, rgba(181,228,248,.10), transparent 52%);
  border-radius: 10px; padding: 18px 20px; margin-bottom: 16px;
  box-shadow: 0 10px 30px rgba(0,0,0,.28);
}
.ib-cfg-titlerow { display: flex; align-items: center; gap: 10px; }

/* HOW MUCH IS PENDING, not whether anything is. The save bar already says
 * there are changes; a reader about to leave the page wants to know if it is
 * one toggle or fifteen. */
.ib-cfg-unsaved {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  padding: 3px 9px; border-radius: 999px;
  color: #e3c07b; background: rgba(227,192,123,.14);
}

/* THE SIDEBAR IS FIXED WIDTH AND THE BODY TAKES THE REST. A category list that
 * resizes with its longest label makes the settings column jump every time you
 * switch, which is the one thing a navigation column must never do. */
.ib-cfg-layout { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 16px; align-items: start; }
.ib-cfg-side {
  background: var(--color-base-100, #22283e); border-radius: 10px;
  padding: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.28);
  position: sticky; top: 16px;
}
.ib-cfg-side nav { display: flex; flex-direction: column; gap: 3px; }

.ib-cfg-cat {
  appearance: none; cursor: pointer; font: inherit; text-align: left;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; min-height: 44px; padding: 8px 10px 8px 12px;
  border: 1px solid transparent; border-radius: 6px; background: transparent;
  color: #979eb5; transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.ib-cfg-cat:hover { background: rgba(255,255,255,.035); color: var(--color-base-content, #e6ecf5); }
.ib-cfg-cat .lb { font-size: 14px; font-weight: 600; min-width: 0; }
/* The count, so a category says how much is behind it before you open it. */
.ib-cfg-cat .n {
  font-family: var(--font-mono); font-size: 12px; font-variant-numeric: tabular-nums;
  color: #7c849e; flex-shrink: 0;
}
/* Arcane marks the current category with a tinted wash and a border rather
 * than a bar, which is right here too: the rail already owns the 2px accent
 * edge, and reusing it two levels down would make them read as the same kind
 * of choice. */
.ib-cfg-cat.is-on {
  border-color: rgba(181,228,248,.30);
  background: linear-gradient(90deg, rgba(181,228,248,.14), rgba(106,168,231,.05));
  color: var(--ib-ice, #f0f7fc);
}
.ib-cfg-cat.is-on .n { color: var(--color-accent, #b5e4f8); }

/* One section, already styled by the Config rules above. It only needs to stop
 * being one cell of a grid. */
.ib-cfg-body > .cfg-sect { margin: 0 0 16px; }
.ib-cfg-body > .cfg-sect[hidden] { display: none; }
.ib-cfg-body .cfg-row[hidden] { display: none; }

@media (max-width: 1100px) {
  /* The sidebar becomes a scrolling row of chips. A 232px column on a phone
     leaves the settings themselves about 120px, which is not a layout. */
  .ib-cfg-layout { grid-template-columns: minmax(0, 1fr); }
  .ib-cfg-side { position: static; padding: 8px; overflow-x: auto; }
  .ib-cfg-side nav { flex-direction: row; gap: 6px; }
  .ib-cfg-cat { width: auto; white-space: nowrap; min-height: 38px; }
  .ib-cfg-head { gap: 14px; }
}

/* ============================== the gem and fort panels, without Zenon's art
 * Three images and a red accent, all of them the game's or Zenon's:
 *   gems-heap.png      the gem panel's art column
 *   icon-gem-node.png  the sprite in each level tile
 *   fort-barbarian.png the fort panel's art column
 * plus a checked state in rgb(239 74 65), which is Zenon's gem red on a brand
 * whose whole palette is cold.
 *
 * WHAT REPLACES THEM IS DRAWN, NOT ANOTHER PICTURE. The trunk's own comment
 * says it best and then draws the opposite conclusion: a drawn gem was
 * replaced by the game's sprite because "it was our idea of a gem next to a
 * page full of the game's own art". On this brand there is no game art left on
 * the page at all, so that reason is gone and the drawn one is now the
 * consistent choice.
 *
 * The tier is carried by SIZE and by SATURATION rather than by a caption: a
 * level 3 node is worth three times a level 1 and the tile says so before the
 * numbers are read.
 */

/* Both art columns go and the panels take the full width, which is the actual
   compensation: the controls stop sharing the card with a picture. */
.gg-panel, .ft-panel { grid-template-columns: 1fr !important; }
.gg-art, .ft-art { display: none !important; }

/* The sprite goes; the tile draws its own. */
.gg-gem { display: none !important; }

/* THE GEM, IN CSS. A six sided stone with a lit facet across its top left,
   which is what makes it read as cut rather than as a coloured hexagon.
   order:-1 puts it back above the text: as a pseudo element it would otherwise
   render after both flex children. */
.gg-node::after {
  content: ""; order: -1; position: relative; z-index: 1; flex: none;
  width: var(--g, 30px); height: calc(var(--g, 30px) * 1.06);
  clip-path: polygon(50% 0%, 93% 26%, 93% 74%, 50% 100%, 7% 74%, 7% 26%);
  background:
    linear-gradient(145deg,
      rgba(255, 255, 255, .55) 0%, rgba(255, 255, 255, 0) 38%),
    linear-gradient(160deg, var(--color-accent, #b5e4f8), var(--color-primary, #6aa8e7));
  transition: filter .16s ease, opacity .16s ease, transform .16s ease;
}
.gg-node[data-lv="1"] { --g: 26px; }
.gg-node[data-lv="2"] { --g: 33px; }
.gg-node[data-lv="3"] { --g: 40px; }

/* Off is not absent. A grey stone still reads as a gem you are not collecting,
   where hiding it would make the row look like it had fewer choices. */
.gg-node::after { filter: grayscale(1) brightness(.75); opacity: .5; }
.gg-node:has(.gg-in:checked)::after { filter: none; opacity: 1; }
.gg-node:hover::after { transform: translateY(-1px); }

/* THE CHECKED WASH, RECOLOURED. ::before is the trunk's, so it is overridden
   rather than replaced: same geometry, the brand's blue instead of Zenon red. */
.gg-node.is-on::before,
.gg-node:has(.gg-in:checked)::before {
  background: linear-gradient(180deg,
    rgba(106, 168, 231, .30), rgba(106, 168, 231, .12)) !important;
}
.gg-node:has(.gg-in:checked) { background: rgba(106, 168, 231, .08) !important; }
.gg-node:has(.gg-in:checked) .gg-worth { color: var(--color-accent, #b5e4f8) !important; }
.gg-node:has(.gg-in:checked) .gg-txt b { color: var(--ib-ice, #f0f7fc) !important; }
.gg-node:hover { background: rgba(255, 255, 255, .05) !important; }

/* The tiles get room now that nothing shares the row with them. */
.gg-ladder { gap: 8px !important; }
.gg-node { border-radius: 8px; padding: 14px 8px 12px !important; }
.gg-txt b { font-size: 16px !important; }

/* The fort panel loses its barbarian and its opacity trick. is-dead dims the
   whole panel to 42%, which was readable over artwork and is not over text:
   the level numbers a customer is trying to read go to about 2.4:1. A quieter
   dim plus the brand's own disabled treatment keeps it legible while still
   plainly off. */
.ft-panel.is-dead { opacity: .62 !important; }
.ft-panel { border-top: 1px solid rgba(255, 255, 255, .065) !important; }

/* ===================================== troop tiers and materials, kept smaller
 * BOTH SETS OF IMAGES STAY. They are the game's own troop and material sprites
 * and they are doing real work: a tier is recognised by its unit far faster
 * than by the numeral 4, and a material by its icon faster than by the word
 * Leather. Neither is decoration, which is what separates them from the gem
 * heap and the barbarian that went.
 *
 * The troop tiers were the largest thing on the page at 52px, 60 in a wide
 * card: five of them per row across four troop types is twenty sprites, which
 * read as a gallery rather than as a row of choices. Smaller, they are a
 * control again. Materials are already modest at 34px and stay as they are.
 */
.tier-opt img { max-width: 40px !important; }
.care-card.is-wide .tier-opt img { max-width: 46px !important; }
@media (max-width: 720px) { .tier-opt img { max-width: 34px !important; } }

/* THE CHECKED STATE WAS ZENON GOLD, rgb(200 160 74), on both pickers. The
 * border already moves to the primary on selection, so the fill was the only
 * thing still saying this belongs to another brand. */
.tier-opt:has(input:checked),
.tier-off:has(input:checked),
.mat-opt:has(input:checked),
.ft-chip:has(input:checked) {
  border-color: var(--color-primary, #6aa8e7) !important;
  background: rgba(106, 168, 231, .14) !important;
}

/* (14) AND EVERY OTHER PLACE THE GOLD WAS STILL SHOWING. rgb(200 160 74) is
 * written as a literal eleven times in the trunk's config sheet, so it cannot
 * follow --color-primary and each one has to be named. The pick is here
 * because removing the card-shaped .cfg-pick.has-why block handed its checked
 * state straight back to `.cfg-pick input:checked+span`, which is gold -- a
 * fix in one place uncovering a default in another. The fort cells and chips
 * and the "Changes restart the bot" pill were never overridden at all. */
body.bg-base-300 .cfg-pick input:checked + span {
  background: rgba(106, 168, 231, .16);
  color: var(--ib-ice, #f0f7fc);
}
body.bg-base-300 .cfg-pick label:focus-within span {
  outline-color: var(--color-accent, #b5e4f8);
}
body.bg-base-300 .ft-cell.is-on { background: rgba(106, 168, 231, .16) !important; }
body.bg-base-300 .cfg-note {
  border-color: rgba(106, 168, 231, .34) !important;
  background: rgba(106, 168, 231, .09) !important;
  color: var(--ib-ice, #f0f7fc) !important;
}
.tier-off:has(input:checked) span,
.mat-opt:has(input:checked) span { color: var(--ib-ice, #f0f7fc) !important; }

/* ------------------------------------------------ day one warm-up, in the dock
 * It reads as a STATE, not as an error. The dock's stop button is red and its
 * start button is the brand blue; a rest is neither, so it takes the ice tone
 * the rest of this sheet uses for "the system is doing something deliberate".
 * The disabled button keeps its shape so the bar does not reflow when the bot
 * wakes up and the real Start returns. */
.ib-rest {
  display: inline-flex; align-items: baseline; gap: 8px;
  padding: 6px 12px; border-radius: 999px;
  background: rgba(181, 228, 248, .10);
  border: 1px solid rgba(181, 228, 248, .34);
}
.ib-rest b {
  font: 600 12px var(--font-mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--color-accent, #b5e4f8);
}
.ib-rest i {
  font-style: normal; font-size: 12.5px; color: #9aa8c6;
}
.ib-dock.is-rest .ib-dbtn.is-start[disabled] {
  cursor: default; opacity: .5;
  background: rgba(255, 255, 255, .06) !important;
  color: #9aa8c6 !important; border: 1px solid rgba(255, 255, 255, .12);
}
/* The sentence under the bar, at the width of the content it belongs to. */
.ib-rest-why {
  margin: 8px auto 0; max-width: 62ch; text-align: center;
  font-size: 12.5px; line-height: 1.5; color: #8990a8;
}
@media (max-width: 560px) {
  .ib-rest { padding: 5px 10px; gap: 6px; }
  .ib-rest-why { margin-top: 6px; padding: 0 14px; font-size: 12px; }
}

/* =========================================== the customer bots page
 * BIG, PLAIN, AND ONE ROW PER BOT. The reseller reads English as a second
 * language and answers his customers on a phone, so this is sized for a thumb
 * and worded for a glance: what the bot is called, who owns it, what it is
 * doing, how long it has left, and the three buttons.
 *
 * The state word carries the colour, not the row, so a page of twenty bots is
 * not a wall of green. */
.ab-head { padding: 2px 0 18px; }
.ab-head h1 {
  margin: 0; font: 700 26px/1.15 var(--font-sans); letter-spacing: -.02em;
  color: var(--ib-ice, #f0f7fc);
}
.ab-head p { margin: 7px 0 0; font-size: 14px; color: #9aa8c6; max-width: 62ch; }
.ab-flash { margin-bottom: 14px; }
.ab-none { color: #8990a8; font-size: 14px; }

.ab-list { display: flex; flex-direction: column; gap: 10px; }
.ab-bot {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 14px 16px; border-radius: 11px;
  background: rgba(255, 255, 255, .03);
  border: 1px solid rgba(255, 255, 255, .08);
}
.ab-who { flex: 1 1 240px; min-width: 0; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 4px 8px; }
.ab-who b {
  font: 600 16px var(--font-sans); color: var(--ib-ice, #f0f7fc);
}
.ab-id { font: 500 12px var(--font-mono); color: #7c849e; }
.ab-test {
  font: 600 10.5px var(--font-mono); letter-spacing: .1em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 4px; color: #9aa8c6;
  background: rgba(255, 255, 255, .06);
}
.ab-sub { flex: 1 1 100%; font-size: 13px; color: #9aa8c6; }

.ab-facts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ab-state {
  font: 600 12px var(--font-mono); letter-spacing: .08em; text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px;
  background: rgba(255, 255, 255, .06); color: #9aa8c6;
}
.ab-state.is-running { background: rgba(61, 214, 140, .14); color: #3dd68c; }
.ab-state.is-resting { background: rgba(181, 228, 248, .14); color: var(--color-accent, #b5e4f8); }
.ab-state.is-no-signal { background: rgba(232, 199, 102, .16); color: #e8c766; }
.ab-days { font-size: 13px; color: #9aa8c6; white-space: nowrap; }
.ab-days.is-soon { color: #e8c766; font-weight: 600; }
.ab-conduct { font-size: 12.5px; color: #7c849e; white-space: nowrap; }

.ab-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ab-acts form { display: flex; align-items: center; gap: 6px; margin: 0; }
.ab-btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 40px; padding: 0 16px; border-radius: 9px; cursor: pointer;
  font: 600 14px var(--font-sans); border: 1px solid transparent;
  text-decoration: none; white-space: nowrap;
}
.ab-btn.is-open { background: var(--color-primary, #6aa8e7); color: #08101f; }
.ab-btn.is-open:hover { filter: brightness(1.08); }
.ab-btn.is-start { background: rgba(61, 214, 140, .16); color: #3dd68c;
  border-color: rgba(61, 214, 140, .4); }
.ab-btn.is-stop { background: rgba(229, 104, 109, .12); color: #e5686d;
  border-color: rgba(229, 104, 109, .4); }
.ab-btn.is-days { background: rgba(255, 255, 255, .06); color: #c2c9db;
  border-color: rgba(255, 255, 255, .14); }
.ab-btn:hover { filter: brightness(1.12); }
.ab-num {
  width: 68px; height: 40px; padding: 0 10px; border-radius: 9px;
  border: 1px solid rgba(255, 255, 255, .14);
  background: rgba(0, 0, 0, .22); color: var(--ib-ice, #f0f7fc);
  font: 600 14px var(--font-sans); text-align: center;
}
.ab-num:focus { outline: 2px solid var(--color-accent, #b5e4f8); outline-offset: 1px; }

/* ON A PHONE the buttons get the full width rather than wrapping to one each
   on its own ragged line. He answers customers from his phone. */
@media (max-width: 620px) {
  .ab-bot { gap: 10px; }
  .ab-acts { width: 100%; }
  .ab-acts > .ab-btn, .ab-acts form { flex: 1 1 auto; }
  .ab-acts form .ab-btn { flex: 1 1 auto; }
  .ab-head h1 { font-size: 22px; }
}

/* ---------------------------------------------- commander portraits and the log
 * A tile holds either a portrait or two initials, and both have to be the same
 * 34px circle-in-a-square or the overlap that makes a pair read as a pair
 * comes apart. `has-art` carries no padding so the image fills it; the initials
 * keep their centred text.
 */
.ib-m-cmd span.has-art { padding: 0; overflow: hidden; }
.ib-m-cmd span.has-art img {
  width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 6px;
}
/* The empty-state line, so "Nothing reported yet." does not read as output. */
.ib-ctext.ib-dim { color: #7c849e; font-style: italic; }

/* --------------------------------------------- the gathering bar, and bigger faces
 * COMMANDERS AT 44px. They were 34, which is a favicon: a portrait that small
 * is a coloured smudge and the pair it belongs to cannot be told from the pair
 * on the row below. The overlap grows with them so two still read as one pair.
 */
.ib-m-cmd span { width: 44px !important; height: 44px !important;
                 margin-left: -12px !important; font-size: 13px !important; }
.ib-m-cmd span:first-child { margin-left: 0 !important; }
.ib-m-cmd span.has-art img { border-radius: 7px; }

/* THE BAR. Written empty by the template and filled by the ticker, so it is
 * right to the second rather than right at render time. The transition is
 * short and linear: the ticker moves it once a second in even steps, and an
 * eased transition on an even step reads as a stutter. */
.ib-mtrack {
  display: block; height: 5px; border-radius: 3px; margin-top: 5px;
  background: rgba(255, 255, 255, .07); overflow: hidden; max-width: 340px;
}
.ib-mfill {
  display: block; height: 100%; width: 0; border-radius: inherit;
  background: var(--color-primary, #6aa8e7);
  transition: width .9s linear;
}
/* Gathering is the one that earns; it gets the green the running badge uses so
 * the two say the same thing in the same colour. */
.ib-mfill.is-gathering { background: #7fd6a2; }
.ib-mfill.is-returning { background: #979eb5; }
.ib-m-eta { font-variant-numeric: tabular-nums; }

/* The conduct mark, sitting in the pill before the score. Baseline-aligned
 * rather than centred: the pill is text and an icon that floats above the
 * type's baseline reads as pasted on. */
.ib-pill.is-cond { display: inline-flex; align-items: center; gap: 6px;
                   padding-left: 6px; }
.ib-cond-ic { width: 18px; height: 18px; object-fit: contain; display: block;
              flex-shrink: 0; }

/* ------------------------------------------------- picture icons in the rail
 * ONE CELL, ONE SIZE, FOR EVERY ROW. There used to be three: 30px for a
 * picture, 38px for terms because it was the quiet row at the bottom, 33px for
 * a bot because its row is taller. Each was defensible on its own, and together
 * they made a rail whose icons were all slightly different sizes, which is the
 * one thing a column of icons must not be -- the eye reads the variation as
 * meaning before it reads any of the labels.
 *
 * So the size is set on .ic itself, every row gets it, and `is-art` now says
 * only WHAT KIND of icon it is rather than how big. The art fills the cell; the
 * one line drawing left sits at 70% of it, because a 1.6px stroke scaled to the
 * whole cell is a wireframe standing next to painted objects.
 */
.ib-nav .ic img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* WHY THERE IS NO PER-ICON RULE HERE. One box each did not make the column
 * even, because the files did not agree about how much of themselves was
 * picture. Counting pixels above 180 alpha, so a soft halo does not get
 * mistaken for the object, and drawing each into the same cell:
 *
 *     shop        97px of object in a 150px canvas   ->  26px drawn
 *     support     97px in 144px                      ->  27px
 *     terms      140px in 160px                      ->  35px
 *     gem        148px in 170px                      ->  35px
 *     admin       78px in  80px                      ->  39px
 *
 * Shop and Support were a third smaller than everything beside them and no
 * cell size could have fixed it: their cells were already full, of a halo so
 * faint it vanishes by 30 alpha and so wide it eats a fifth of every edge.
 *
 * Scaling those two up in CSS worked and was the wrong repair -- it left the
 * cause in the files, so the next icon dropped in here would need its own
 * measurement and its own class. The files were re-canvassed instead, each to
 * the frame that puts its object at 88% of the side. They all draw at 40px in
 * a 46px cell now and there is nothing per-icon left to keep in step.
 */
/* The collapsed rail is a 74px column of icons and nothing else, so they keep
   their size and lose their margin. */
@media (max-width: 1100px) {
  .ib-nav .ic { margin: 0; }
}

/* THE TWO MARKS, AND "THE SAME SIZE" MEANS THE SAME GREEN DISC.
 *
 * These two files are not the same kind of picture. The deposit pin is a disc
 * with a pointer hanging under it: its green circle is 42px inside a 50x72
 * canvas, so 58% of the height is the part you see. The footprints are a disc
 * that fills their frame: 98px of green inside 128x128, 77% of the height.
 *
 * So matching the BOXES is what made them look wrong. At 36px and 34px the pin
 * drew a 21px circle beside a 26px one, which reads as two different marks
 * rather than as one column saying two things. Each is sized instead so the
 * circle comes out at 26px:
 *
 *     pin          26 / 0.583 = 45 tall, x 50/72 = 31 wide
 *     footprints   26 / 0.766 = 34 square   (unchanged, it was the right one)
 *
 * The negative margin is what keeps the row the height it already was. The pin
 * is 45px against the 34px portraits beside it and would have grown every
 * gathering row by nine pixels; -5px top and bottom hands the row a 35px box
 * and lets the tail hang into the 12px of row padding, which is empty.
 */
.ib-m-pin { width: 31px; height: 45px; margin: -5px 0; flex-shrink: 0; display: block; }
.ib-m-pin.is-walk { width: 34px; height: 34px; margin: 0; }

/* ======================== arcane's own controls, and the Statistics tab
 * The workspace shell was arcane's; the controls inside it were still Zenon's,
 * which is why the page still read as Zenon with a new frame around it. These
 * are the primitives from their globals.css, in this brand's colours:
 *
 *   toggle   44x24 track, 18px knob, 2px inset, fills on when active
 *   input    a card-coloured field with a hairline, not a lit box
 *
 * Zenon's switch is .z-sw and is already close in shape, so this mostly
 * changes what it is made of: a filled track and a plain round knob rather
 * than a bordered pill with a shadowed one.
 */
/* THE SWITCHES. `.z-sw` IS THE INPUT, not a wrapper, and that one fact is why
 * the on state was green until now.
 *
 * The rules below used to test `.z-sw:has(input:checked)`, `input:checked +
 * .z-sw` and `.z-sw.is-on` -- a wrapper containing an input, a sibling of an
 * input, and a class nothing sets. `<input class="z-sw" type="checkbox">` is
 * none of those, so not one of the three ever matched, while the plain
 * `body.bg-base-300 .z-sw` rule above them matched fine. The off state took
 * this brand's navy and the on state fell through to Zenon's
 * `.z-sw:checked { background-color: var(--color-success) }`. A toggle that
 * only looks wrong in ONE of its two states is the kind that survives review,
 * because half the screenshots are correct.
 *
 * `input.z-sw:checked` now, which is what it always should have been.
 *
 * ICE, AND THE KNOB GOES DARK. On is the pale accent -- the same #b5e4f8 the
 * rail uses for the open row and the console for a focused field -- so the
 * track reads as lit rather than as coloured-in. A white knob on a pale track
 * would be one shape on another shape of the same value, so the knob takes the
 * page's deepest navy: 13.9:1 against the track, and the position is legible
 * from across the room rather than by comparing two whites.
 */
body.bg-base-300 input.z-sw {
  --sw-w: 44px; --sw-h: 24px; --sw-knob: 18px; --sw-pad: 2px;
  background-color: rgba(181, 228, 248, .10) !important;
  border: 1px solid rgba(181, 228, 248, .26) !important;
  border-radius: 999px !important;
  box-shadow: none !important;
}
body.bg-base-300 input.z-sw::after {
  background-color: #9fb0cc !important;
  box-shadow: none !important;
}
body.bg-base-300 input.z-sw:hover:not(:disabled) {
  border-color: rgba(181, 228, 248, .5) !important;
}
body.bg-base-300 input.z-sw:checked {
  background-color: var(--color-accent, #b5e4f8) !important;
  border-color: var(--color-accent, #b5e4f8) !important;
  box-shadow: 0 0 0 3px rgba(181, 228, 248, .13) !important;
}
body.bg-base-300 input.z-sw:checked::after {
  background-color: #0b1224 !important;
}
body.bg-base-300 input.z-sw:checked:hover:not(:disabled) {
  border-color: var(--color-accent, #b5e4f8) !important;
  filter: brightness(1.06);
}

/* Fields: the card colour with a hairline, which is arcane's .input. Zenon
 * lights its inputs a step brighter than the surface; on a navy card that
 * reads as a box cut into the page rather than a field on it. */
body.bg-base-300 .cfg-sel,
body.bg-base-300 .z-num,
body.bg-base-300 select.z-num {
  background: var(--color-base-100, #22283e) !important;
  border: 1px solid rgba(255, 255, 255, .085) !important;
  border-radius: 8px !important;
  transition: border-color .14s ease;
}
body.bg-base-300 .cfg-sel:focus-within,
body.bg-base-300 .z-num:focus { border-color: var(--color-accent, #b5e4f8) !important; }

/* ------------------------------------------------------------ Statistics
 * By level is the only table here that changes a decision: levels 1, 2 and 3
 * are worth 10, 20 and 30 gems and each is a switch on the Settings tab, so
 * "almost everything came from level 1" is the argument for turning the others
 * on. A bar per level says that at a glance; four numbers in a row do not.
 */
.ib-lvls { list-style: none; margin: 18px 0 0; padding: 0; }
.ib-lvl {
  display: grid; grid-template-columns: 34px minmax(0, 1fr) 76px 92px;
  align-items: center; gap: 14px; padding: 11px 0;
  border-top: 1px solid rgba(255, 255, 255, .065);
}
.ib-lvl:first-child { border-top: 0; }
.ib-lvl-n {
  font-family: var(--font-mono); font-size: 14px; font-weight: 700;
  color: var(--color-accent, #b5e4f8);
}
.ib-lvl-bar { height: 8px; border-radius: 4px; background: rgba(255, 255, 255, .06); overflow: hidden; }
.ib-lvl-fill { display: block; height: 100%; border-radius: inherit;
               background: var(--color-primary, #6aa8e7); }
.ib-lvl-v {
  text-align: right; font-family: var(--font-mono); font-size: 15px;
  font-weight: 650; font-variant-numeric: tabular-nums;
  color: var(--ib-ice, #f0f7fc);
}
.ib-lvl-s { text-align: right; font-size: 12.5px; color: #8990a8; }

.ib-runs { list-style: none; margin: 18px 0 0; padding: 0; }
.ib-run {
  display: flex; align-items: baseline; gap: 16px; padding: 10px 0;
  border-top: 1px solid rgba(255, 255, 255, .065);
}
.ib-run:first-child { border-top: 0; }
.ib-run-when { flex: 1; font-size: 14px; color: var(--color-base-content, #e6ecf5); }
.ib-run-for { font-family: var(--font-mono); font-size: 13px; color: #979eb5; }
.ib-run-g {
  font-family: var(--font-mono); font-size: 14px; font-weight: 650;
  color: var(--ib-ice, #f0f7fc); min-width: 70px; text-align: right;
}

@media (max-width: 720px) {
  .ib-lvl { grid-template-columns: 30px minmax(0, 1fr) 70px; }
  .ib-lvl-s { display: none; }
}

/* ------------------------------------------------- the two live surfaces
 * Marches and the log are the same moment described two ways: what is out, and
 * what the bot says about it. Stacked they were two screens apart -- by the
 * time the log scrolled into view the marches were off the top.
 *
 * NOT 1fr 1fr. The march list carries a 44px portrait pair, a title, a
 * countdown and a bar; the log is one column of monospace. Equal halves gave
 * the log more room than it can use and squeezed the march rows into wrapping.
 */
.ib-live2 { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 16px; }

/* THE TWO CARDS SHARE ONE SET OF ROWS, which is the only way their headings
 * and their bodies can be guaranteed to start on the same line.
 *
 * `align-items: start` used to be here and it is what made them uneven: each
 * card took its own natural height, so their bottoms missed each other by a
 * few pixels. Removing it squares the bottoms but not the insides -- the
 * marches head carries the big gems-today figure and is taller than the log's
 * head, so the first march row still began below the first log line, and the
 * word Marches sat lower on the page than the words Live log.
 *
 * Subgrid fixes both at once and with no measured constant to go stale: each
 * card spans both rows and adopts them, so row 1 is as tall as the TALLER head
 * and row 2 is as tall as the taller body, for both cards. `align-self: start`
 * on the heads keeps their contents at the top of that shared row rather than
 * centred in it, which is what puts the two titles on one line.
 *
 * Wrapped in @supports because without it `grid-row: span 2` would apply on
 * its own and stack the head on top of the body in the same cell.
 */
.ib-live2 > .ib-card { margin-bottom: 0; }
@supports (grid-template-rows: subgrid) {
  .ib-live2 > .ib-card { display: grid; grid-row: span 2; grid-template-rows: subgrid; }
  .ib-live2 .ib-card-head { align-self: start; }
}

/* The console grows to the height of the marches beside it rather than sitting
   at a fixed 260px with a gap under it. It still scrolls; it just uses the
   room the row already occupies. */
.ib-live2 .ib-console { max-height: 420px; }

@media (max-width: 1250px) {
  /* Below this the march row starts wrapping inside its own half, which is
     worse than the two being stacked. */
  .ib-live2 { grid-template-columns: minmax(0, 1fr); }
  .ib-live2 > .ib-card { margin-bottom: 16px; }
  .ib-live2 .ib-console { max-height: 260px; }
  /* Stacked there is no second card to line up with, so the shared rows go and
     each card is its own box again. Without this the two would still span a
     pair of rows in a single column and leave the first card padded out to the
     height of the second one's head. */
  @supports (grid-template-rows: subgrid) {
    .ib-live2 > .ib-card { display: block; grid-row: auto; }
  }
}

/* ========================================================== the bots page
 * /dash, and the reason it is an override is in the template. The short of
 * it: the trunk page is Zenon's -- a fixed full-viewport photograph of
 * gold-trimmed felt, a centred cream mono title, charcoal tiles divided by
 * hairlines with each product's sprite washed out to 24% behind the figures,
 * and the footer hidden. All of it deliberate and all of it recognisably
 * theirs, which makes two sites look like one site with two logos.
 *
 * These are the same parts this brand already uses everywhere else: a navy
 * card on the page ground, .ib-badge for state, .ib-fact for a figure, and
 * the operator's own gem -- the same FILE the rail and the console hero draw,
 * so a customer clicking a row, reading a card and opening a console meets one
 * object three times instead of three pictures of a gem.
 */
.ibd-grid { margin-top: 22px; }

.ibd-bot {
  position: relative; display: flex; flex-direction: column;
  background: var(--color-base-100, #22283e); border-radius: 10px;
  padding: 18px 20px 16px; text-decoration: none; color: inherit;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .28);
  border: 1px solid transparent;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
/* THE WHOLE CARD IS THE TARGET, so it has to say so. The trunk tile changes
   its background by four percent on hover, which on a dark surface is not a
   state change, it is a rendering difference. */
a.ibd-bot:hover {
  border-color: rgba(181, 228, 248, .35); transform: translateY(-2px);
  box-shadow: 0 16px 34px rgba(0, 0, 0, .36);
}
/* An expired bot is still yours and still has to be findable, so it keeps its
   card and loses its confidence rather than its place. */
.ibd-bot.is-dead { opacity: .72; }

.ibd-head { display: flex; align-items: center; gap: 14px; }
/* The gem gets a seat rather than floating on the card, which is the same
   treatment the console hero gives it. */
.ibd-mark {
  flex-shrink: 0; width: 56px; height: 56px; border-radius: 12px;
  display: grid; place-items: center; background: var(--color-base-200, #161c2f);
}
.ibd-mark img { width: 46px; height: 46px; object-fit: contain; display: block; }
.ibd-id { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
.ibd-id b {
  font-size: 17px; font-weight: 650; letter-spacing: -.01em;
  color: var(--ib-ice, #f0f7fc);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* THE GOVERNOR AND THE KINGDOM, in sans rather than mono. The trunk tile sets
   this line in the mono face because Zenon's whole identity is mono; here mono
   is reserved for figures and log lines, so a name in it reads as data. */
.ibd-id span { font-size: 13.5px; font-weight: 500; color: #a2abc4; }

/* A trial says so once, under the head, not as a corner ribbon cutting the
   card's own border. */
.ibd-trial {
  align-self: flex-start; margin-top: 12px; padding: 2px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: #e3c07b; background: rgba(227, 192, 123, .14);
}

/* THE FIGURES, ON THEIR OWN GROUND. A band a shade darker than the card rather
   than two numbers floating in the padding: it groups them, and it is what the
   washed-out sprite was there to do on the trunk tile without the smudge. */
.ibd-figs {
  display: flex; gap: 32px; margin-top: 16px; padding: 13px 15px;
  border-radius: 8px; background: var(--color-base-200, #161c2f);
}
.ibd-figs .ib-fact { gap: 5px; }
.ibd-figs .ib-lbl { font-size: 11.5px; letter-spacing: .08em; }
.ibd-figs .ib-fact .v { font-size: 22px; }

/* One step from working, said as an instruction rather than as a zero. */
.ibd-todo {
  margin: 16px 0 0; padding: 13px 15px; border-radius: 8px;
  background: var(--color-base-200, #161c2f);
  font-size: 13.5px; line-height: 1.5; color: #a2abc4;
}

.ibd-go {
  display: flex; align-items: center; gap: 8px; margin-top: 16px;
  font-size: 13.5px; font-weight: 600; color: var(--color-accent, #b5e4f8);
}
.ibd-go i { font-style: normal; transition: transform .15s ease; }
a.ibd-bot:hover .ibd-go i { transform: translateX(4px); }

/* The two products this brand does not sell. Plain on purpose; the template
   says why they are rendered at all. */
.ibd-bot.is-plain .ibd-go { margin-top: 18px; }

/* Free trial, which trial_pool 0 means never renders today. */
.ibd-bot.is-offer .ib-btn { margin-top: 18px; align-self: flex-start; }

/* ------------------------------------------------------------- add a bot
 * AN OUTLINE, NOT A SURFACE. That is what says "not one of yours" faster than
 * any label, and it is why this can sit in the grid here when the trunk page
 * had to pull its version out into a strip: there the tile was a third equal
 * cell in an auto-fit row with no ceiling and took a full third of a wide
 * screen. .ib-cards caps a track at 450px, so this is a card beside cards.
 */
.ibd-bot.is-add {
  flex-direction: row; align-items: center; gap: 14px;
  background: none; box-shadow: none;
  border: 1px dashed rgba(255, 255, 255, .14);
  min-height: 92px;
}
a.ibd-bot.is-add:hover {
  background: var(--color-base-100, #22283e);
  border-color: rgba(181, 228, 248, .4); border-style: solid;
}
.ibd-plus {
  flex-shrink: 0; width: 46px; height: 46px; border-radius: 12px;
  display: grid; place-items: center; font-size: 24px; font-weight: 300;
  color: var(--color-accent, #b5e4f8); background: rgba(181, 228, 248, .09);
}
.ibd-bot.is-add .ibd-id b { font-size: 15.5px; }

/* State colours the console does not have yet, because its header only ever
 * had to say running or stopped. A licence is the one thing on this page a
 * customer can still act on, so it gets a colour rather than a word. */
.ib-badge.is-warn { color: #e3c07b; background: rgba(227, 192, 123, .14); }
.ib-badge.is-bad  { color: #e5919b; background: rgba(229, 145, 155, .14); }

@media (max-width: 560px) {
  .ibd-figs { gap: 22px; }
  .ibd-mark { width: 48px; height: 48px; }
  .ibd-mark img { width: 40px; height: 40px; }
}

/* ==================================================================== phones
 * This site has been built and checked on a desktop all day, and the console
 * is a page customers read on a handset at three in the morning. Three things
 * were plainly broken at 380px, and none of them is a matter of taste.
 *
 * 1. THE LANGUAGE SWITCHER SAT ON TOP OF THE TABS. It is position:fixed at the
 *    top right with z-index 70, which on a wide screen lands in empty chrome
 *    and on a phone lands exactly where the tab row is. STATISTICS was behind
 *    an "English" button and could not be pressed at all -- and Vietnamese is
 *    this brand's default locale, so the control doing the covering is the one
 *    most of these customers need least.
 *
 * 2. THE TABS WRAPPED INTO TWO RAGGED LINES, half of one hidden under the
 *    switcher, because .z-nav is a centred flex row sized for four words at
 *    26px of padding each.
 *
 * 3. THE RAIL TOOK A FIFTH OF THE SCREEN. 74px of 380 is 19% of a phone spent
 *    on five icons, permanently, on every page.
 *
 * --ib-rail-sm IS REDEFINED RATHER THAN JOINED BY A THIRD WIDTH. Every offset
 * that has to agree with the rail -- the dock's centring and max-width, the
 * hovercard, the stacked live row -- is already written as calc() over that
 * one variable, so narrowing the variable moves all of them at once. A new
 * --ib-rail-xs would have meant finding and updating each calc by hand, and
 * the one that got missed would be a control sitting half off the screen.
 */
@media (max-width: 560px) {
  :root { --ib-rail-sm: 62px; }

  /* The icons come down with it. 46px in a 62px column touches both edges. */
  .ib-nav .ic { width: 38px; height: 38px; }
  .ib-nav .ic svg { width: 28px; height: 28px; }
  .ib-nav a, .ib-nav a:has(.bot) { height: 50px; }
  .ib-brand { height: 62px; }
  .ib-brand img { width: 42px; height: 42px; }
  .ib-av { width: 30px; height: 30px; }
  .ib-who { height: 54px; }

  /* FLAG AND CHEVRON, NO WORD. The label is what makes this wide enough to
     cover a tab; the flag already says which language is on. */
  .ib-lang { top: 8px; right: 8px; }
  .ib-lang > summary { height: 34px; padding: 0 9px; gap: 5px; }
  .ib-lang > summary .sh { display: none; }
  .ib-lang .fl { font-size: 17px; }
  .ib-lang-menu { top: 40px; min-width: 172px; }

  /* ONE LINE THAT SCROLLS, not two that wrap. A row of four tabs on a phone is
     a horizontal strip in every app that has ever shipped one; wrapping puts
     the second line under a fixed control and hides it. The top margin is what
     clears the switcher above -- pushing the strip down is honest, where
     letting it slide underneath would leave a tab that cannot be tapped. */
  .ib-contabs { margin-top: 46px; }
  .ib-contabs .z-nav {
    justify-content: flex-start; flex-wrap: nowrap;
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .ib-contabs .z-nav::-webkit-scrollbar { display: none; }
  .ib-contabs .z-nav > a {
    padding: 12px 15px; font-size: 12.5px; letter-spacing: .1em; flex: none;
  }

  /* The dock is the only way to stop a bot, so on a phone it gets the width. */
  .ib-dock {
    left: var(--ib-rail-sm); transform: none; right: 8px;
    bottom: 8px; max-width: none; width: auto;
    justify-content: space-between; padding: 8px;
  }
  .ib-dbtn { height: 42px; padding: 0 16px; font-size: 13.5px; }
  body.bg-base-300 .gem-page { padding-bottom: 120px; }

  /* A 16px gutter, and nothing allowed to push the page sideways. A single
     overflowing element turns every vertical scroll into a diagonal one. */
  body.bg-base-300 > .flex-1 { overflow-x: hidden; }
  .z-wrap, .z-wrap-page, .gem-page { padding-left: 14px; padding-right: 14px; }

  /* The hero stacks: a 56px tile, a name and a state chip do not fit beside a
     licence bar in 300px of content. */
  .ib-hero, .ib-hero-main { flex-direction: column; align-items: flex-start; gap: 14px; }
  .ib-hero-side { width: 100%; }
  .ib-hero h1 { font-size: 22px; }
  .ib-facts { grid-template-columns: repeat(auto-fit, minmax(122px, 1fr)); gap: 14px 16px; }
  .ib-h1 { font-size: 22px; }
}

/* ------------------------------------------------------------ the footer, small
 * .z-footer-in is a wrapping flex row: mono, uppercase, 11.5px, .14em of
 * tracking, gap-x-6, with the disclaimer pushed to the far end by ml-auto. On a
 * desktop that is one tidy line. In 300px of content it is four fragments
 * breaking wherever they run out of room -- the wordmark and Terms sharing a
 * line, REFUNDS & CANCELLATION alone on the next, the disclaimer wrapping twice
 * -- and .14em on capitals is most of why: the tracking that makes a single
 * line read as a plate is what stops three words fitting on a phone.
 *
 * So it stacks deliberately instead of wrapping accidentally: identity, then
 * the two links as a row of their own, then the legal line. ml-auto is undone,
 * because "push to the far end" in a column means "leave a hole above me".
 *
 * mt-20 is 80px of nothing above it. That is a reasonable pause at the end of a
 * long desktop page and a wasted screenful on a handset.
 *
 * OVERFLOW IS GUARDED HERE TOO. The clip added for the console is on
 * body > .flex-1, and the footer is a SIBLING of that element rather than a
 * child -- base.html puts it after the div -- so it was never covered by it.
 * A long unbroken legal sentence at .14em is exactly the kind of thing that
 * pushes a page sideways.
 */
@media (max-width: 560px) {
  body.bg-base-300 > .z-footer { margin-top: 36px; overflow-x: hidden; }
  /* STILL WRAPPING, with the two spans forced to their own rows. A flex COLUMN
     would have been simpler and would have put Terms alone on a line with
     Refunds & cancellation under it, which is four stacked fragments rather
     than three tidy ones. Pinning the first and last children to 100% leaves
     the two links to fall side by side on the row between them, because that
     is what they are: a pair. */
  .z-footer-in {
    flex-wrap: wrap; align-items: flex-start;
    gap: 9px 18px; padding: 16px 14px 20px; letter-spacing: .08em; line-height: 1.5;
  }
  .z-footer-in > span:first-child { flex: 0 0 100%; gap: 8px; }
  /* Undone: in a column it only opens a gap above the last item. */
  .z-footer-in > span.ml-auto { margin-left: 0 !important; }
  .z-footer-in > span:last-child {
    flex: 0 0 100%; letter-spacing: .01em; line-height: 1.45;
  }
}

/* =========================================== troop tiers: a row, not a matrix
 * Zenon draws four troop types down and Off plus five tier pictures across:
 * twenty-four pieces of artwork in one grid. On a desktop that reads as a
 * chart; on a phone it is five 34px thumbnails crushed into 300px, four times.
 *
 * It is also the wrong shape for the question being asked. Each row is ONE
 * choice out of six, and the five pictures are five versions of the same
 * soldier at different ranks -- the art is identity, not options. Drawing all
 * five spends the row on the part that does not vary.
 *
 * So the chosen tier is drawn once and large, and the six choices become a
 * strip of numbers beside it. The script that moves the elements is in
 * gem.html; it never creates or removes an input, so the form still posts what
 * it always posted.
 */
.tier-grid.ib-troops { display: block; }
.ib-troop {
  display: grid; grid-template-columns: 54px minmax(0, 1fr) auto;
  align-items: center; gap: 14px; padding: 12px 2px;
  border-top: 1px solid rgba(255, 255, 255, .065);
}
.ib-troop:first-child { border-top: 0; }
.ib-troop-art { width: 54px; height: 54px; display: grid; place-items: center; }
.ib-troop-art img {
  max-width: 100%; max-height: 100%; display: block;
  transition: opacity .15s ease, filter .15s ease;
}
/* OFF still shows the soldier, at a weight that says he is not being trained.
   A blank slot would make the row stop saying which troop it is. */
.ib-troop.is-off .ib-troop-art img { opacity: .3; filter: grayscale(1); }
.ib-troop-name {
  font-size: 15px; font-weight: 600; color: var(--color-base-content, #e6ecf5);
}
.ib-troop.is-off .ib-troop-name { color: #8990a8; font-weight: 550; }

.ib-troop-tiers { display: flex; align-items: center; gap: 6px; }
/* THE CHIPS ARE THE ORIGINAL LABELS, so these rules have to beat the console's
   own .tier-opt / .tier-off, which are inline in gem.html's <head> and would
   otherwise win on source order. Two classes each, which they are not. */
.ib-troop-tiers .tier-opt,
.ib-troop-tiers .tier-off {
  min-width: 38px; height: 38px; padding: 0; border-radius: 8px;
  display: grid; place-items: center; cursor: pointer;
  background: var(--color-base-200, #161c2f);
  border: 1px solid rgba(255, 255, 255, .09);
  font: 600 13.5px var(--font-mono); color: #a2abc4;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
.ib-troop-tiers .tier-off { min-width: 46px; font-size: 11.5px; letter-spacing: .1em; }
.ib-troop-tiers .tier-opt img { display: none; }
.ib-troop-tiers .tier-opt > span,
.ib-troop-tiers .tier-off > span { line-height: 1; }
.ib-troop-tiers .tier-opt:hover,
.ib-troop-tiers .tier-off:hover {
  border-color: rgba(181, 228, 248, .4); color: var(--ib-ice, #f0f7fc);
}
.ib-troop-tiers .tier-opt:has(input:checked) {
  background: var(--color-primary, #6aa8e7); border-color: var(--color-primary, #6aa8e7);
  color: #08101f;
}
.ib-troop-tiers .tier-off:has(input:checked) {
  background: rgba(255, 255, 255, .06); border-color: rgba(255, 255, 255, .2);
  color: var(--ib-ice, #f0f7fc);
}
.ib-troop-tiers .tier-opt:focus-within,
.ib-troop-tiers .tier-off:focus-within { outline: 2px solid var(--color-accent, #b5e4f8); outline-offset: 2px; }

@media (max-width: 560px) {
  /* The strip drops under the name rather than shrinking the chips below the
     44px a thumb needs. */
  .ib-troop { grid-template-columns: 46px minmax(0, 1fr); gap: 10px 12px; }
  .ib-troop-art { width: 46px; height: 46px; }
  .ib-troop-tiers { grid-column: 1 / -1; gap: 5px; }
  .ib-troop-tiers .tier-opt { min-width: 0; flex: 1; height: 40px; }
  .ib-troop-tiers .tier-off { min-width: 0; flex: 1.2; height: 40px; }
}

/* ================================================================ right to left
 * ARABIC IS THE FIRST LOCALE THAT IS NOT LEFT TO RIGHT, and this whole design
 * is built around a rail pinned to the left edge with everything else pushed
 * off it by a margin. Translating the strings and stopping there would have
 * produced an Arabic page with its navigation on the wrong side, its arrows
 * pointing away from the thing they lead to, and the active-row marker on the
 * outside edge of the rail instead of against the content.
 *
 * `dir` comes from <html>, which base.html now sets from the request. Before
 * this it was a hardcoded lang="en" with no dir at all, so every Vietnamese
 * page has been declaring itself English to browsers and screen readers.
 *
 * THESE ARE MIRRORS, NOT A SECOND DESIGN. Every rule here flips one physical
 * property that the LTR rules set. Nothing new is introduced, and a language
 * added later inherits it by direction alone.
 */
[dir="rtl"] .ib-rail { inset: 0 0 0 auto; }
[dir="rtl"] body.bg-base-300 > .flex-1,
[dir="rtl"] body.bg-base-300 > .z-footer {
  margin-left: 0; margin-right: var(--ib-rail);
}
@media (max-width: 1100px) {
  [dir="rtl"] body.bg-base-300 > .flex-1,
  [dir="rtl"] body.bg-base-300 > .z-footer {
    margin-left: 0; margin-right: var(--ib-rail-sm);
  }
}

/* The open row's marker belongs against the content, whichever side that is. */
[dir="rtl"] .ib-nav a.on { border-right: 0; border-left: 2px solid var(--color-accent, #b5e4f8); }
[dir="rtl"] .ib-nav .ic { margin-right: 0; margin-left: 13px; }
[dir="rtl"] .ib-brand { padding: 0 16px; }
[dir="rtl"] .ib-who { padding: 0 15px 0 13px; }
[dir="rtl"] .ib-out { margin-left: 0; margin-right: auto; }

/* The switcher moves to the other corner with everything else. */
[dir="rtl"] .ib-lang { right: auto; left: 20px; }
[dir="rtl"] .ib-lang-menu { right: auto; left: 0; }
@media (max-width: 1100px) { [dir="rtl"] .ib-lang { right: auto; left: 12px; } }
@media (max-width: 560px)  { [dir="rtl"] .ib-lang { right: auto; left: 8px; } }

/* The dock is centred on the CONTENT, so its offset is on the rail's side. */
[dir="rtl"] .ib-dock {
  left: auto; right: calc(var(--ib-rail) + ((100vw - var(--ib-rail)) / 2));
  transform: translateX(50%);
}
@media (max-width: 1100px) {
  [dir="rtl"] .ib-dock {
    left: auto; right: calc(var(--ib-rail-sm) + ((100vw - var(--ib-rail-sm)) / 2));
  }
}
@media (max-width: 560px) {
  [dir="rtl"] .ib-dock { right: var(--ib-rail-sm); left: 8px; transform: none; }
}

/* AN ARROW IS A DIRECTION, not a glyph. "Open console ->" points at the thing
 * it opens; in Arabic that thing is to the left, so the arrow is mirrored
 * rather than swapped for a different character. */
[dir="rtl"] .ibd-go i { transform: scaleX(-1); }
[dir="rtl"] a.ibd-bot:hover .ibd-go i { transform: scaleX(-1) translateX(4px); }

/* The accent edge on the governor card, and the level bars, fill from the
   reading edge. */
[dir="rtl"] .ib-gov { padding-left: 0; padding-right: 24px; }
[dir="rtl"] .ib-gov::before { inset: 0 0 0 auto; border-radius: 0 10px 10px 0; }
[dir="rtl"] .ib-card.is-lead::before { border-radius: 8px 8px 0 0; }

/* Numbers and the mono figures stay left to right inside an RTL sentence: a
   gem count is a number, not Arabic text, and bidi would otherwise move its
   punctuation to the wrong end. */
[dir="rtl"] .ib-fact .v,
[dir="rtl"] .ib-lvl-v,
[dir="rtl"] .ib-run-for,
[dir="rtl"] .ib-ctime,
[dir="rtl"] .ib-m-eta { direction: ltr; unicode-bidi: embed; }

/* The console log is engine output in English; forcing it LTR keeps timestamps
   and message text in the order the daemon wrote them. */
[dir="rtl"] .ib-console { direction: ltr; text-align: left; }

/* ========================================== the operator console, arcane's shape
 * Zenon's admin is a .z-topbar plus a second row of .z-nav tabs, each carrying
 * a two-digit index: 01 Overview, 02 Fleet, 03 Trials. Those numerals are the
 * signature of that design and the first thing that says whose console this is.
 *
 * arcane-web's admin is one sticky blurred bar in a three column grid -- brand,
 * workspace links, actions -- with the links as plain pills that fill faintly
 * when current, a search carrying its own Ctrl K, and the account as avatar over
 * role. No indices, no second tier, no rules between anything. That is what
 * this reproduces, in this brand's navy rather than arcane's black.
 *
 * TONAL LAYERING, NO STROKES is their phrase and it is the whole card language:
 * a panel is a few percent of white over the page rather than a box with a
 * border, and the only line on it is an inset highlight along the top edge,
 * which reads as light catching a surface rather than as a drawn frame. Their
 * globals.css calls the borders ghost borders, felt and not seen.
 *
 * THE EIGHT ADMIN PAGES ARE NOT FORKED. They go on rendering z-kpi, z-card,
 * z-table and the rest; everything below re-skins those under .iba-main, so the
 * surface changes and not one line of any admin page does.
 */
.iba {
  --iba-panel: rgba(255, 255, 255, .04);
  --iba-edge: inset 0 1px 0 rgba(181, 228, 248, .13);
  min-height: 100dvh;
  background:
    radial-gradient(1100px 480px at 88% -140px, rgba(106, 168, 231, .10), transparent 65%),
    radial-gradient(900px 420px at -10% 110%, rgba(181, 228, 248, .05), transparent 60%),
    var(--color-base-300, #0d1120);
}

/* ------------------------------------------------------------------ the bar */
.iba-head {
  position: sticky; top: 0; z-index: 180;
  background: rgba(13, 17, 32, .9);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  box-shadow: inset 0 -1px 0 rgba(181, 228, 248, .12), 0 14px 36px rgba(0, 0, 0, .3);
}
.iba-head-main {
  min-height: 66px; padding: 12px 26px; gap: 18px; width: 100%;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
}

.iba-brand { display: flex; align-items: center; gap: 10px; text-decoration: none; }
.iba-logo {
  width: 38px; height: 38px; border-radius: 9px; overflow: hidden; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, .05);
}
.iba-logo img { width: 100%; height: 100%; object-fit: contain; }
.iba-brand-copy { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.iba-brand-copy strong {
  font-size: 15px; font-weight: 600; letter-spacing: .01em;
  color: var(--ib-ice, #f0f7fc); white-space: nowrap;
}
.iba-brand-copy small {
  font-size: 11px; font-weight: 600; line-height: 1; color: #8990a8;
  text-transform: uppercase; letter-spacing: .07em;
}

/* Pills, and no numerals. A link that is current is filled, not underlined and
   not prefixed: the fill is the whole indicator. */
.iba-ws {
  min-width: 0; display: flex; align-items: center; gap: 4px;
  overflow-x: auto; scrollbar-width: none;
}
.iba-ws::-webkit-scrollbar { display: none; }
.iba-ws-link {
  flex: 0 0 auto; display: inline-flex; align-items: center; min-height: 38px;
  padding: 0 13px; border-radius: 8px; text-decoration: none;
  font-size: 13px; font-weight: 600; letter-spacing: .01em; color: #a2abc4;
  transition: background .14s ease, color .14s ease;
}
.iba-ws-link:hover { color: var(--ib-ice, #f0f7fc); background: rgba(255, 255, 255, .05); }
.iba-ws-link.active { color: var(--ib-ice, #f0f7fc); background: rgba(255, 255, 255, .085); }

.iba-actions { min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.iba-search {
  width: clamp(200px, 22vw, 380px); height: 38px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px; padding: 0 10px;
  border-radius: 8px; border: 1px solid transparent; font: inherit;
  font-size: 13px; color: #8990a8; background: rgba(255, 255, 255, .045);
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.iba-search:hover {
  background: rgba(255, 255, 255, .07); color: var(--ib-ice, #f0f7fc);
  border-color: rgba(181, 228, 248, .22);
}
.iba-search svg { width: 15px; height: 15px; flex: none; }
.iba-search span { flex: 1; text-align: left; }
.iba-search kbd {
  font: 600 10.5px var(--font-mono); letter-spacing: .04em; color: #8990a8;
  padding: 3px 6px; border-radius: 5px; background: rgba(255, 255, 255, .06);
}

.iba-acct {
  display: flex; align-items: center; gap: 9px; padding: 4px 8px 4px 4px;
  border-radius: 9px; text-decoration: none; transition: background .14s ease;
}
.iba-acct:hover { background: rgba(255, 255, 255, .05); }
.iba-av {
  width: 30px; height: 30px; border-radius: 50%; overflow: hidden; flex: none;
  display: grid; place-items: center; font-weight: 700; font-size: 12px; color: #08101f;
  background: linear-gradient(180deg, var(--color-primary, #6aa8e7), #306ba6);
}
.iba-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.iba-acct-copy { display: flex; flex-direction: column; gap: 1px; line-height: 1.2; }
.iba-acct-copy span { font-size: 13px; font-weight: 600; color: var(--ib-ice, #f0f7fc); }
.iba-acct-copy small {
  font-size: 10.5px; color: #8990a8; text-transform: uppercase; letter-spacing: .06em;
}
.iba-out {
  width: 36px; height: 36px; border-radius: 8px; flex: none; display: grid;
  place-items: center; color: #a2abc4; transition: background .14s ease, color .14s ease;
}
.iba-out svg { width: 17px; height: 17px; }
.iba-out:hover { background: rgba(255, 255, 255, .05); color: #e5919b; }

.iba-main { max-width: 1440px; margin: 0 auto; padding: 26px; }

/* ------------------------------------------------- the pages, re-skinned
 * Everything from here down changes what the SHARED admin templates render
 * without touching them. Panels lose their borders and gain a tonal fill and
 * one inset highlight; tables lose their rules except the row hairline, which
 * is what a table actually needs in order to be read.
 */
.iba-main .z-kpi,
.iba-main .z-card,
.iba-main .z-sec,
.iba-main .z-panel {
  background: var(--iba-panel) !important;
  border: 0 !important;
  border-radius: 12px !important;
  box-shadow: var(--iba-edge) !important;
}
.iba-main .z-kpi { padding: 16px 18px !important; display: flex; flex-direction: column; gap: 6px; }
.iba-main .z-kpi-k {
  font-size: 11.5px !important; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: #8990a8 !important;
}
.iba-main .z-kpi-v {
  font-size: 27px !important; font-weight: 650; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--ib-ice, #f0f7fc) !important;
}
.iba-main .z-kpi-of { font-size: 16px; color: #8990a8; font-weight: 550; }
.iba-main .z-kpi-s { font-size: 12px; color: #8990a8 !important; }
.iba-main .z-kpi-v.is-warn { color: #e3c07b !important; }
.iba-main .z-kpi-v.is-gold { color: var(--color-accent, #b5e4f8) !important; }

/* THE INDEX NUMERALS GO. 01 / 02 / 03 beside a page title is Zenon's device and
   the loudest one left on these pages. The eyebrow stays, because it names the
   group and a heading alone does not. */
.iba-main .z-idx { display: none !important; }
.iba-main .z-eyebrow {
  font-size: 11.5px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: #8990a8;
}
.iba-main .z-page-head h1 {
  font-size: 25px; font-weight: 650; letter-spacing: -.01em;
  color: var(--ib-ice, #f0f7fc);
}

.iba-main .z-table { border: 0 !important; }
.iba-main .z-table th {
  font-size: 11.5px !important; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: #8990a8 !important;
  border-bottom: 1px solid rgba(255, 255, 255, .08) !important;
  background: none !important;
}
.iba-main .z-table td { border-bottom: 1px solid rgba(255, 255, 255, .05) !important; }
.iba-main .z-table tr:hover td { background: rgba(255, 255, 255, .025) !important; }

.iba-main .z-pill {
  border: 0 !important; border-radius: 999px !important;
  background: rgba(255, 255, 255, .07) !important; color: #a2abc4 !important;
  font-size: 11.5px; font-weight: 600; padding: 3px 10px !important;
}
.iba-main .z-pill-good { background: rgba(127, 214, 162, .14) !important; color: #7fd6a2 !important; }
.iba-main .z-pill-bad { background: rgba(229, 145, 155, .14) !important; color: #e5919b !important; }
.iba-main .z-pill-mute { background: rgba(255, 255, 255, .05) !important; color: #8990a8 !important; }

.iba-main .z-note, .iba-main .z-empty { color: #8990a8 !important; }
.iba-main .z-hair { border-color: rgba(255, 255, 255, .06) !important; }
.iba-main .z-num, .iba-main .z-input, .iba-main .z-field select {
  background: rgba(255, 255, 255, .05) !important;
  border: 1px solid rgba(255, 255, 255, .09) !important;
  border-radius: 8px !important; color: var(--ib-ice, #f0f7fc) !important;
}
.iba-main .z-num:focus, .iba-main .z-input:focus {
  border-color: var(--color-accent, #b5e4f8) !important; outline: none;
}
.iba-main .z-btn-ghost {
  border: 0 !important; border-radius: 8px !important;
  background: rgba(255, 255, 255, .06) !important; color: #c2c9db !important;
  font-weight: 600;
}
.iba-main .z-btn-ghost:hover {
  background: rgba(255, 255, 255, .1) !important; color: var(--ib-ice, #f0f7fc) !important;
}

/* ---------------------------------------------------------------- the palette */
.iba-pal {
  position: fixed; inset: 0; z-index: 300; display: grid; place-items: start center;
  padding-top: 14vh; background: rgba(6, 9, 18, .62);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.iba-pal[hidden] { display: none; }
.iba-pal-box {
  width: min(520px, calc(100vw - 32px)); border-radius: 12px; overflow: hidden;
  background: var(--color-base-100, #22283e);
  box-shadow: 0 30px 70px rgba(0, 0, 0, .6), inset 0 1px 0 rgba(181, 228, 248, .14);
}
.iba-pal-box input {
  width: 100%; height: 50px; padding: 0 16px; border: 0; outline: none;
  font: inherit; font-size: 15px; color: var(--ib-ice, #f0f7fc);
  background: rgba(255, 255, 255, .04);
}
.iba-pal-box ul { list-style: none; margin: 0; padding: 6px; max-height: 46vh; overflow-y: auto; }
.iba-pal-box li[hidden] { display: none; }
.iba-pal-box a {
  display: block; padding: 10px 12px; border-radius: 7px; text-decoration: none;
  font-size: 14px; color: #c2c9db;
}
.iba-pal-box li.on a, .iba-pal-box a:hover {
  background: rgba(181, 228, 248, .12); color: var(--ib-ice, #f0f7fc);
}

@media (max-width: 900px) {
  .iba-head-main { grid-template-columns: auto minmax(0, 1fr); row-gap: 10px; padding: 10px 16px; }
  .iba-ws { grid-column: 1 / -1; order: 3; }
  .iba-search { width: 38px; padding: 0; justify-content: center; }
  .iba-search span, .iba-search kbd { display: none; }
  .iba-acct-copy { display: none; }
  .iba-main { padding: 18px 14px; }
}

/* The rail belongs to the customer side; the operator console has its own bar,
   so the margin that makes room for the rail must not apply here, and neither
   should the public footer. */
body.bg-base-300:has(.iba) > .flex-1 { margin-left: 0; }

/* AND THE GOVERNOR PICKER, for the same reason and one nobody had hit yet.
   gem_pick.html draws its own z-topbar and never calls page_header(), so it
   has no rail -- but the gutter that makes room for one is unconditional, and
   the whole screen sat 296px to the right of where it should be. It is the
   first page a new customer sees after paying. */
body.bg-base-300:has(.pick-wrap) > .flex-1,
body.bg-base-300:has(.pick-wrap) > .z-footer { margin-left: 0; }
body.bg-base-300:has(.iba) > .z-footer { display: none; }

/* ------------------------------------------------- the dock's two controls
 * THEY WERE ALREADY CENTRED, and that was the problem. .ib-dock is a flex row
 * with align-items:center, so the chip and the button share a centre line
 * exactly -- but the chip is .z-state at 7px of padding and a 1px border, which
 * comes to about 33px, against the button's 46. Two things on one axis at
 * different heights read as misaligned even when their centres agree, and on a
 * phone justify-content:space-between throws them to opposite ends of the bar,
 * which is what makes the mismatch obvious.
 *
 * AND THE CHIP HAS SQUARE CORNERS. state_style() in _chrome.html sets no
 * border-radius at all, so a hard-cornered rectangle was sitting beside a 10px
 * rounded button. Matching the height fixes the alignment; matching the radius
 * is what makes them read as a pair rather than as two controls that happen to
 * be near each other.
 *
 * Scoped to .ib-dock. The same chip is drawn in the console hero and in the bot
 * cards, where it is a label in running text and its own size is right.
 */
.ib-dock-state { padding-left: 0; }
.ib-dock .z-state {
  height: 46px; padding: 0 16px; border-radius: 10px; box-sizing: border-box;
}
@media (max-width: 560px) {
  .ib-dock .z-state { height: 42px; padding: 0 14px; font-size: 12px; }
}

/* --------------------------------------------------- air at the top, everywhere
 * THE MARK SAT ON THE EDGE OF THE SCREEN. .ib-brand is 86px tall with its
 * contents centred, so the 60px mark had 13px above it and the rail begins at
 * y=0 -- which on a phone, where the block is 62px and the mark 42px, left ten
 * pixels between a browser chrome and a brand. A logo touching the top of the
 * viewport reads as a page that has been cut off rather than one that starts
 * there.
 *
 * The block grows rather than the mark moving inside it: the scene behind it is
 * a background-image on this same element, so padding the CONTENTS down would
 * have left the picture where it was and only moved the bear, which is the half
 * of the problem nobody complained about.
 *
 * AND THE PAGES GET THE SAME. py-8 is 32px above the first card on every
 * console page, which is correct beneath a top bar and thin beneath nothing at
 * all -- this brand removed the bar and never gave the space back. One rule on
 * the two wrappers every page uses, so it lands on all of them rather than on
 * whichever one gets remembered.
 */
.ib-brand { height: 108px; }
body.bg-base-300 .gem-page,
body.bg-base-300 .ib-dash,
body.bg-base-300 .ib-shop { padding-top: 26px; }

@media (max-width: 1100px) {
  /* The collapsed rail centres the mark with no wordmark beside it, so it needs
     less of the extra than the full one does. */
  .ib-brand { height: 92px; }
}
@media (max-width: 560px) {
  .ib-brand { height: 74px; }
  /* The tab strip already clears the language switcher by 46px; adding the page
     padding on top of that would push the first card off the fold. */
  body.bg-base-300 .gem-page { padding-top: 10px; }
  body.bg-base-300 .ib-dash,
  body.bg-base-300 .ib-shop { padding-top: 16px; }
}

/* ------------------------------------------- the dock is a pair, not a bar
 * The phone rules stretched it edge to edge with justify-content:space-between,
 * on the reasoning that Stop is the only way to halt a bot and should get the
 * width. That was wrong in an obvious way once it was on a screen: two small
 * controls pinned to opposite ends of a 370px bar with a void between them do
 * not read as a pair of related buttons, they read as two things that failed to
 * line up. The desktop dock hugs its contents and sits centred over the
 * content column, and there was never a reason for the phone to differ.
 *
 * Centred on the CONTENT, not the viewport: centring on the viewport pushes it
 * right by half the rail, which is the same mistake the 1100px rule above
 * already avoids.
 */
@media (max-width: 560px) {
  .ib-dock {
    left: calc(var(--ib-rail-sm) + ((100vw - var(--ib-rail-sm)) / 2));
    right: auto; transform: translateX(-50%);
    width: auto; max-width: calc(100vw - var(--ib-rail-sm) - 16px);
    justify-content: center; gap: 10px; padding: 8px;
  }
  [dir="rtl"] .ib-dock {
    left: auto; right: calc(var(--ib-rail-sm) + ((100vw - var(--ib-rail-sm)) / 2));
    transform: translateX(50%);
  }
}

/* ================================================= the governor's own portrait
 * The hero drew ib-icon-gemstone.png beside the instance name: the same picture
 * on every bot, next to a label the customer typed. Neither identified
 * anything, and the rail an inch away already says which product this is.
 *
 * THE FRAME IS A SEPARATE IMAGE THAT OVERLAPS THE PICTURE, which is how the
 * game ships it: avatar and avatarFrame are two urls, the frame is a ring with
 * a transparent middle, and it is drawn slightly LARGER than the portrait
 * because its art includes the gap it is meant to leave. Stacking them in a
 * grid cell with both at the same place, the frame scaled up and the portrait
 * clipped to a circle, is what reproduces what a player sees in game.
 *
 * pointer-events:none on the frame. It sits on top of the portrait, and without
 * it the frame swallows any hover or click meant for what is underneath.
 */
.ib-gov-face {
  position: relative; flex-shrink: 0;
  width: 78px; height: 78px; display: grid; place-items: center;
}
.ib-gov-pic {
  grid-area: 1 / 1; width: 62px; height: 62px; border-radius: 50%;
  object-fit: cover; display: block;
  background: var(--color-base-200, #161c2f);
}
.ib-gov-frame {
  grid-area: 1 / 1; width: 78px; height: 78px; object-fit: contain;
  display: block; pointer-events: none;
}
/* Without a frame the portrait carries its own ring, so it does not float. */
.ib-gov-face:not(.has-frame) .ib-gov-pic {
  width: 66px; height: 66px;
  box-shadow: 0 0 0 2px rgba(181, 228, 248, .3);
}

/* --------------------------------------------------------------- swap account
 * Quiet on purpose. It is the control that moves a paid bot onto a different
 * game account and starts a 48 hour lock, so it belongs beside the account it
 * replaces and not in the dock with Start and Stop, and it should never be the
 * brightest thing on the card.
 */
.ib-swap {
  margin-left: auto; flex-shrink: 0;
  display: inline-flex; align-items: center; gap: 8px; height: 38px;
  padding: 0 14px; border-radius: 9px; text-decoration: none;
  font-size: 13.5px; font-weight: 600; color: #a2abc4;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .09);
  transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.ib-swap svg { width: 16px; height: 16px; flex: none; }
.ib-swap:hover {
  background: rgba(181, 228, 248, .12); color: var(--ib-ice, #f0f7fc);
  border-color: rgba(181, 228, 248, .34);
}

@media (max-width: 720px) {
  .ib-gov-face { width: 66px; height: 66px; }
  .ib-gov-pic { width: 52px; height: 52px; }
  .ib-gov-frame { width: 66px; height: 66px; }
  .ib-gov-face:not(.has-frame) .ib-gov-pic { width: 56px; height: 56px; }
  /* The row wraps on a phone, so the button takes its own line rather than
     being squeezed against the stats. */
  .ib-swap { margin-left: 0; width: 100%; justify-content: center; }
}

/* --------------------------------------------- the identifiers row, after the
 * The governor card lost its avatar, its eyebrow and its name to the hero, and
 * what is left is two identifiers and the control that changes them. Without
 * the 56px circle the row has nothing to align against, so the stats take the
 * space and the button sits at the far end.
 */
.ib-gov-row.is-ids { gap: 14px 28px; }
.ib-gov-row.is-ids .ib-gov-stats { flex: 1 1 auto; gap: 32px; }

/* ------------------------------------------ the identifiers, on a narrow screen
 * .ib-gov-stat stacks its label over its value, which is right for a figure
 * somebody reads -- gems per hour, uptime -- and wrong for these two. A governor
 * id and an email are REFERENCE, looked up once when support asks for them, and
 * stacking them spent five lines on two facts: label, value, label, value, and a
 * button across the full width underneath.
 *
 * One line each instead, label left and value right, which is what a definition
 * list looks like and reads the same way. Three lines instead of six, and the
 * button goes back to its own size at the end of the row.
 *
 * ONLY IN THIS ROW. Everywhere else the stacked pair is correct and this must
 * not reach it: .ib-facts on the session card is the same shape and those ARE
 * figures somebody reads.
 */
@media (max-width: 720px) {
  .ib-hero-gov { padding: 14px 16px; }
  .ib-gov-row.is-ids { gap: 10px; }
  .ib-gov-row.is-ids .ib-gov-stats {
    flex-direction: column; gap: 9px; width: 100%; flex: 1 1 100%;
  }
  .ib-gov-row.is-ids .ib-gov-stat {
    flex-direction: row; align-items: baseline; justify-content: space-between;
    gap: 14px;
  }
  .ib-gov-row.is-ids .ib-gov-stat .ib-lbl { flex: none; }
  /* The value keeps the room and truncates plainly if it is long.
     NOT `direction: rtl` to clip from the other end, which is the trick this
     first reached for: an email is full of neutral characters and bidi is
     entitled to move a trailing .com to the front of the line. A clipped
     address is legible; a reordered one is a support ticket. It would also
     collide with the real dir=rtl this site now serves for Arabic. */
  .ib-gov-row.is-ids .ib-gov-stat .v {
    min-width: 0; text-align: right; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; font-size: 14px;
  }
  /* Back to its own width, at the end of the row rather than across it. */
  .ib-gov-row.is-ids .ib-swap {
    margin-left: auto; width: auto; height: 34px; padding: 0 12px; font-size: 13px;
  }
}

/* The locked swap. A span, not a disabled link -- <a> has no disabled state and
   one styled dead but still navigable is worse than either. The hours are the
   point, so they are the only bright thing in it. */
.ib-swap.is-locked {
  cursor: default; color: #7c849e;
  background: rgba(255, 255, 255, .03);
  border-color: rgba(255, 255, 255, .06);
}
.ib-swap.is-locked svg { opacity: .55; }
.ib-swap.is-locked b {
  font-weight: 700; color: #e3c07b; font-family: var(--font-mono); font-size: 12.5px;
  padding: 2px 7px; border-radius: 999px; background: rgba(227, 192, 123, .13);
}

/* ------------------------------------ the collapsed rail says what it means
 * Below 1100px the rail is icons and nothing else: .ib-nav .lb is display:none,
 * which is what makes the narrow column possible and also what makes a customer
 * guess. Five pictures in a strip, and the only way to learn which is which is
 * to press one and see where you land.
 *
 * ONE FIXED ELEMENT THE SCRIPT MOVES, not a pseudo-element on each row. The
 * first version floated each row's own .lb with position:absolute and needed
 * `overflow: visible` on .ib-nav to escape the rail -- and THAT rule is what
 * lets the rail scroll when somebody owns more bots than fit the screen.
 * Trading a scrollbar for a tooltip is a bad trade on the one surface where a
 * customer might have ten bots.
 *
 * position:fixed is not clipped by an ancestor's overflow, so the rail keeps
 * its scroll and the tip still escapes. The text is READ FROM the row's own
 * label, so it is already translated and a bot row names the bot.
 */
.ib-tip {
  position: fixed; z-index: 200; pointer-events: none;
  padding: 8px 12px; border-radius: 8px; white-space: pre; max-width: 60vw;
  background: var(--color-base-100, #22283e);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(181, 228, 248, .14);
  color: var(--ib-ice, #f0f7fc); font-size: 14px; font-weight: 600; line-height: 1.35;
  opacity: 0; transform: translateY(-50%);
  transition: opacity .12s ease;
}
.ib-tip.on { opacity: 1; }
@media (min-width: 1101px) { .ib-tip { display: none; } }

/* ------------------------------------------ a pick whose answers explain
 * NOTHING HERE ANY MORE, ON PURPOSE. This was seventy lines that undid the
 * segmented control -- border, divider, nowrap, the checked chip -- so each
 * answer could be a card with a title over its own sentence. It looked right
 * on a desktop and was wrong on a phone: the base sheet centres a pick's
 * label under 900px, so a card wider than the screen put its title off the
 * right edge while the sentence wrapped at 64ch underneath.
 *
 * The sentence now prints under the control instead of inside the button, so
 * the pill is a pill again and needs no help from this sheet. .cfg-why below
 * is the whole of it. Do not reintroduce prose inside .cfg-pick.
 */

/* The sentence for whichever choice is selected. Under the control, one line
   of it, because a native <select> draws its own list and four picture tiles
   each carrying prose is a row four sentences tall. */
/* SCOPED TO THE BODY CLASS ON PURPOSE. The trunk now carries .cfg-why too,
 * and gem.html's <style> is page-local markup that renders AFTER this linked
 * sheet -- so a bare .cfg-why here loses on source order and the sentence
 * would take Zenon's --color-dim. One class more and it does not. */
body.bg-base-300 .cfg-why {
  display: block; margin-top: 7px; max-width: 64ch;
  font-size: 12.5px; line-height: 1.5; color: #8990a8;
}
body.bg-base-300 .cfg-why:empty { display: none; }
