/* Split out of the old monolithic styles.css -- this file is loaded
   only by its own sub-module page, after the shared ../styles.css. */
/** CONTENT USER PLANETS **/
#content-UP {
    position: relative;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    flex: auto;
    overflow: hidden;
    /**/
    animation: show 0.5s ease-out;
}

#content-UP > svg {
    z-index: 1;
}

#ante-content-UP {
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    justify-content: center;
    gap: 32px;
    text-align: center;
}

/* Shown in #content-UP (in place of its previous contents) while the
   server-side template render is in flight -- same "not authenticated"
   layout as #ante-content-UP above (full-height, centered column), just
   with a spinner instead of the SSO button. */
.up_loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    gap: 16px;
    color: #999999;
    font-family: 'Shentox Light';
    font-size: 14px;
    text-align: center;
}

/* Loading-screen animation: a fedo pulls a shopping cart under the planet,
   bumps it, the planet shakes loose a growing pile of resources into the
   cart, then fedo hauls the loaded cart off-screen. One infinite 9s CSS
   timeline (percentage keyframe stops on every piece, no JS orchestration)
   -- getUserPlanets() in script.js holds the real content swap back until
   at least one full 9s loop has played, even if the AJAX response is
   already back, so this never gets cut off mid-loop. */
.up_la_stage {
    position: relative;
    width: 380px; height: 200px;
    overflow: hidden;
    border-radius: 4px;
}

.up_la_planet {
    position: absolute; left: 145px; top: 10px; width: 90px; height: 90px;
    border-radius: 50%;
    background: #262626;
    border: 2px solid #3a3a3a;
    box-sizing: border-box;
    animation: up_la_planetShake 9s ease-in-out infinite;
    transform-origin: 50% 100%;
}
@keyframes up_la_planetShake {
    0%, 24% { transform: translate(0,0) rotate(0deg); }
    25%     { transform: translate(-4px,0) rotate(-3deg); }
    26.5%   { transform: translate(4px,0) rotate(3deg); }
    28%     { transform: translate(-3px,0) rotate(-2deg); }
    29.5%   { transform: translate(3px,0) rotate(2deg); }
    31.5%   { transform: translate(-1px,0) rotate(-1deg); }
    33%, 100% { transform: translate(0,0) rotate(0deg); }
}

.up_la_rig {
    position: absolute; left: 0; top: 0; width: 380px; height: 200px;
    animation: up_la_rigMove 9s ease-in-out infinite;
}
@keyframes up_la_rigMove {
    0%        { transform: translateX(-280px); }
    24%, 58%  { transform: translateX(0px); }
    90%, 100% { transform: translateX(300px); }
}

/* shopping cart -- basket center sits at stage x=190, directly under the
   planet's own center. */
.up_la_cart { position: absolute; left: 155px; top: 118px; width: 70px; height: 52px; z-index: 2; }
/* hand-drawn cart: bold line-art, gridded lattice basket, rounded handle
   hook, flat undercarriage bar (grey palette matching the previous
   CSS-box cart: #8a8a8a line-art, #232323/#666666 wheels). Positioned/
   scaled so the basket interior lines up with where the falling
   .up_la_chunk resources land. Sits above the resource chunks (z-index)
   so the lattice/frame reads on top of the pile instead of being buried
   under it. Handle faces left, toward .up_la_fedo. */
.up_la_cart-svg { position: absolute; left: -14px; top: -12px; width: 92px; height: 64px; z-index: 2; }

/* resource chunks -- real PI material icons (img/icons/planetary/materials),
   children of .up_la_cart so they ride along once .up_la_rig moves again.
   14 of them: the cart fills past the rim into a visible pile. Stacked
   below the cart's own lattice (z-index) and landing spots tuned to the
   cart-svg basket's floor/center so nothing crosses the floor rim or
   undercarriage bar and the pile stays centered under the opening. */
.up_la_chunk {
    position: absolute; width: 20px; height: 20px;
    opacity: 0;
    z-index: 1;
}
@keyframes up_la_cfall1  { 0%,26%   {top:-18px;left:34px;opacity:0;} 29%   {top:2px; left:0px; opacity:1;} 100% {top:2px; left:0px; opacity:1;} }
@keyframes up_la_cfall2  { 0%,27.5% {top:-20px;left:38px;opacity:0;} 30.5% {top:0px;left:10px;opacity:1;} 100% {top:0px;left:10px;opacity:1;} }
@keyframes up_la_cfall3  { 0%,29%   {top:-16px;left:32px;opacity:0;} 32%   {top:3px; left:20px;opacity:1;} 100% {top:3px; left:20px;opacity:1;} }
@keyframes up_la_cfall4  { 0%,30.5% {top:-22px;left:40px;opacity:0;} 33.5% {top:1px; left:30px;opacity:1;} 100% {top:1px; left:30px;opacity:1;} }
@keyframes up_la_cfall5  { 0%,32%   {top:-18px;left:36px;opacity:0;} 35%   {top:2px; left:38px;opacity:1;} 100% {top:2px; left:38px;opacity:1;} }
@keyframes up_la_cfall6  { 0%,33.5% {top:-20px;left:30px;opacity:0;} 36.5% {top:5px; left:6px; opacity:1;} 100% {top:5px; left:6px; opacity:1;} }
@keyframes up_la_cfall7  { 0%,35%   {top:-16px;left:42px;opacity:0;} 38%   {top:6px; left:24px;opacity:1;} 100% {top:6px; left:24px;opacity:1;} }
@keyframes up_la_cfall8  { 0%,36.5% {top:-22px;left:34px;opacity:0;} 39.5% {top:4px; left:36px;opacity:1;} 100% {top:4px; left:36px;opacity:1;} }
@keyframes up_la_cfall9  { 0%,38%   {top:-18px;left:36px;opacity:0;} 41%   {top:-1px; left:2px; opacity:1;} 100% {top:-1px; left:2px; opacity:1;} }
@keyframes up_la_cfall10 { 0%,39.5% {top:-20px;left:40px;opacity:0;} 42.5% {top:-2px; left:16px;opacity:1;} 100% {top:-2px; left:16px;opacity:1;} }
@keyframes up_la_cfall11 { 0%,41%   {top:-16px;left:34px;opacity:0;} 44%   {top:-4px; left:32px;opacity:1;} 100% {top:-4px; left:32px;opacity:1;} }
@keyframes up_la_cfall12 { 0%,42.5% {top:-22px;left:38px;opacity:0;} 45.5% {top:-5px; left:10px;opacity:1;} 100% {top:-5px; left:10px;opacity:1;} }
@keyframes up_la_cfall13 { 0%,44%   {top:-18px;left:32px;opacity:0;} 47%   {top:-7px; left:26px;opacity:1;} 100% {top:-7px; left:26px;opacity:1;} }
@keyframes up_la_cfall14 { 0%,45.5% {top:-20px;left:44px;opacity:0;} 48.5% {top:-9px;left:18px;opacity:1;} 100% {top:-9px;left:18px;opacity:1;} }
.up_la_c1  { background: url(../img/icons/planetary/materials/2073_32.png) center/contain no-repeat; animation: up_la_cfall1  9s ease-in infinite; }
.up_la_c2  { background: url(../img/icons/planetary/materials/2267_32.png) center/contain no-repeat; animation: up_la_cfall2  9s ease-in infinite; }
.up_la_c3  { background: url(../img/icons/planetary/materials/2268_32.png) center/contain no-repeat; animation: up_la_cfall3  9s ease-in infinite; }
.up_la_c4  { background: url(../img/icons/planetary/materials/2270_32.png) center/contain no-repeat; animation: up_la_cfall4  9s ease-in infinite; }
.up_la_c5  { background: url(../img/icons/planetary/materials/2286_32.png) center/contain no-repeat; animation: up_la_cfall5  9s ease-in infinite; }
.up_la_c6  { background: url(../img/icons/planetary/materials/3683_32.png) center/contain no-repeat; animation: up_la_cfall6  9s ease-in infinite; }
.up_la_c7  { background: url(../img/icons/planetary/materials/2073_32.png) center/contain no-repeat; animation: up_la_cfall7  9s ease-in infinite; }
.up_la_c8  { background: url(../img/icons/planetary/materials/2267_32.png) center/contain no-repeat; animation: up_la_cfall8  9s ease-in infinite; }
.up_la_c9  { background: url(../img/icons/planetary/materials/2268_32.png) center/contain no-repeat; animation: up_la_cfall9  9s ease-in infinite; }
.up_la_c10 { background: url(../img/icons/planetary/materials/2270_32.png) center/contain no-repeat; animation: up_la_cfall10 9s ease-in infinite; }
.up_la_c11 { background: url(../img/icons/planetary/materials/2286_32.png) center/contain no-repeat; animation: up_la_cfall11 9s ease-in infinite; }
.up_la_c12 { background: url(../img/icons/planetary/materials/3683_32.png) center/contain no-repeat; animation: up_la_cfall12 9s ease-in infinite; }
.up_la_c13 { background: url(../img/icons/planetary/materials/2073_32.png) center/contain no-repeat; animation: up_la_cfall13 9s ease-in infinite; }
.up_la_c14 { background: url(../img/icons/planetary/materials/2267_32.png) center/contain no-repeat; animation: up_la_cfall14 9s ease-in infinite; }

/* fedo -- real reference sprite (img/icons/planetary/ui/fedo.png). Sits
   left of the cart, ground-aligned with it, on top of everything
   (z-index) since it's the character leading the scene. */
.up_la_fedo {
    position: absolute; left: 87.5px; top: 96px; width: 74px; height: 74px;
    animation: up_la_fedoBump 9s ease-in-out infinite;
    z-index: 3;
}
@keyframes up_la_fedoBump {
    0%, 23.5%   { transform: translate(0,0) rotate(0deg); }
    24.5%       { transform: translate(-6px,-10px) rotate(6deg); }
    26%         { transform: translate(2px,2px) rotate(-3deg); }
    27.5%, 100% { transform: translate(0,0) rotate(0deg); }
}
.up_la_fedo-sprite {
    position: absolute; left: 0; top: 0; width: 74px; height: 74px;
    animation: up_la_fedoWaddle 0.5s ease-in-out infinite;
    transform-origin: 50% 100%;
}
@keyframes up_la_fedoWaddle {
    0%, 100% { transform: rotate(-3deg) translateY(0px); }
    50%      { transform: rotate(3deg) translateY(-1px); }
}

/* Narrow panels (small windows / mobile): scale the whole stage down
   rather than letting it get clipped or force horizontal scroll. */
@media (max-width: 420px) {
    .up_la_stage { transform: scale(0.82); transform-origin: top center; margin-bottom: -36px; }
}

#ante-content-UP-SSOButton-IMG {
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;;
}

/* position:relative so .up_char_menu (the manage-characters overlay) can
   anchor to this bar's own box rather than the whole page. padding:16px
   keeps the avatar and the icon buttons off the panel's edge -- previously
   this bar had no padding of its own and sat flush against it.

   Centered-divider layout: the "|" (.toolLogos, same divider used in
   #mainTop-Nav's #right) is the actual reference point, not the midpoint
   of the whole content -- the outer 1fr columns are equal, so the divider
   always lands on the bar's true horizontal center regardless of the
   portrait+name block and the two icon buttons being different widths.
   .up_character is right-aligned within its column (hugs the divider from
   the left) and .up_character_options is left-aligned within its column
   (hugs the divider from the right). The middle "auto" column also still
   works if .up_character is absent (no linked character yet) -- an empty
   1fr track takes the same share of space as a populated one, so the
   divider stays centered either way. */
.up_characters {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    /* Same dark surface + thin border + rounded corners as the colony
       filter bar (.up_filters below) -- #212121 here was already the
       exact same color as --tileBg, just not written as the shared
       token, and had no border/radius of its own yet. */
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
    color: #999999 !important;
    /* .up_filters gets its inset from the edge via its parent
       (.up_planets)'s own 16px padding -- this bar has no such parent
       padding to lean on (#content-UP has none), so now that it's a
       visibly bordered box, it needs that same 16px kept as its OWN
       outer margin instead, or it just sits flush against the panel's
       edges. width is reduced by the same amount on each side so the
       box still doesn't overflow its container. */
    width: calc(100% - 32px);
    gap: 8px;
    margin: 16px 16px 8px;
    padding: 16px;
    box-sizing: border-box;
    position: relative;
}

.up_characters > .up_character {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    grid-column: 1;
    /* Anchor point for .up_char_switcher (the click-to-switch-character
       dropdown) below -- it stretches to fill this whole grid column
       (default grid item behavior), so its own right edge is always
       exactly at the column boundary regardless of this character's
       actual content width, which is what lets the dropdown's fixed
       -8px/+24px offsets (styles.css, .up_char_switcher) reliably land on
       the divider and 8px under the bar no matter whose name is showing. */
    position: relative;
}

.up_character_ {
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    /* Was a one-sided margin-left on .up_character_body when the portrait
       always came first -- now that portrait/name order can be either way
       (see template_planets.php, the name now renders before the
       portrait), the gap lives on the container instead so it holds
       regardless of which child comes first. */
    gap: 8px;
}

/* Mini planet-icon list added to the left of the name/portrait duo (see
   template_planets.php) -- one icon per colony the CURRENT character
   owns, each half the size of the portrait circle (.up_character_img is
   64px) and showing only its own attn status, not the full pill set the
   colony tiles themselves carry (sync time, pin count). row-reverse
   keeps the underlying list in its natural (PHP array) order while
   displaying it right-to-left, so the first planet found stays closest
   to the name/portrait it sits beside. */
.up_char_planets {
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    /* +16px over the original 6px, per direct request. */
    gap: 22px;
    margin-right: 10px;
    /* .up_characters itself (above) now carries the filter-bar look
       (background/border/radius) -- this inner list only needs its own
       background to match so its padding reads as breathing room rather
       than a second, visibly separate box; no border of its own. */
    background-color: var(--tileBg);
    border-radius: 8px;
    padding: 0.5em 0.75em;
}

.up_char_planet_mini {
    position: relative;
    display: block;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    background: none;
    flex-shrink: 0;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}

.up_char_planet_mini img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    box-shadow: 0 0 0 1px #313131;
    transition: box-shadow 0.15s ease;
}

.up_char_planet_mini:hover img,
.up_char_planet_mini:focus-visible img {
    box-shadow: 0 0 0 2px #999999;
}

/* Same good/warn/bad coloring as the full-size .attn (colony tile
   thumbnails) -- only the size/position are overridden here, scaled down
   to suit this 32px icon instead of the 128px .thumb-wrap. */
.up_char_planet_mini .attn {
    position: absolute;
    bottom: -0.2em;
    right: -0.2em;
    width: 0.95em;
    height: 0.95em;
    border-width: 1px;
    font-size: 0.7em;
}

/* ---- Character switcher dropdown -- opens under the top bar when the
   current character's own portrait (#up_char_switcher_toggle) is clicked,
   listing every OTHER character linked to this account (the current one
   is already shown up top, see template_planets.php). Right-anchored to
   .up_character (position:relative, above) rather than .up_characters
   itself, because .up_character always stretches to fill its whole grid
   column regardless of THIS character's own content width -- so its
   right edge is a stable, predictable reference point no matter whose
   name/portrait/planet-count is currently showing, which a fixed offset
   from .up_characters' own edge couldn't be (the two 1fr columns don't
   split evenly when their content needs differ). */
.up_char_switcher {
    position: absolute;
    /* .up_character's own bottom coincides with the grid row's bottom
       (it's the row's tallest item -- the portrait beats the divider
       text and the icon buttons), which sits .up_characters' own
       padding-bottom (16px) above the bar's true outer edge. +16 to
       reach that edge, +8 more for the requested gap past it = 24. */
    top: calc(100% + 24px);
    /* .up_character fills its whole column, so its own right edge is
       short of the divider by exactly .up_characters' own grid-gap
       (8px) -- this pushes 8px further right to land on the divider
       itself, not this block's edge. */
    right: -8px;
    min-width: max-content;
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
    z-index: 50;
}

.up_char_switcher_row {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--borderColor31);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.up_char_switcher_row:last-child { border-bottom: none; }
.up_char_switcher_row:hover,
.up_char_switcher_row:focus-visible { background-color: var(--color42); }

/* The row's own copy of .up_char_planets (same markup/helper as the top
   bar's, see $_renderCharPlanets) drops its standalone dark-box look --
   it already sits inside the dropdown row's own surface, so a second
   nested box would just look like a box within a box -- and shrinks its
   icons a notch to fit a compact row instead of the roomier top bar. */
.up_char_switcher_row .up_char_planets {
    background: none;
    padding: 0;
    margin-right: 0;
    gap: 16px;
}
.up_char_switcher_row .up_char_planet_mini { width: 26px; height: 26px; }

.up_char_switcher_name {
    font-size: 15px;
    font-family: 'Shentox Light';
    color: var(--textColor3);
    white-space: nowrap;
}

.up_char_switcher_img {
    display: block;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
}
.up_char_switcher_img img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.up_characters > .toolLogos {
    grid-column: 2;
}

.up_characters > .up_character_options {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 16px;
    grid-column: 3;
}

.up_character_img {
    display: flex;
    height: 64px;
    width: 64px;
    justify-content: center;
    align-items: center;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}

.up_character_img > img {
    height: 60px;
    width: 60px;
    border-radius: 50%;
    box-shadow: 0 0 0 0 transparent;
    transition: box-shadow 0.15s ease;
}

/* .open is toggled by script.js alongside #up_char_switcher's own hidden
   attribute, so the ring stays lit while the dropdown is open, not just
   on a transient :hover. */
.up_character_img:hover > img,
.up_character_img.open > img {
    box-shadow: 0 0 0 2px var(--borderColor62);
}

.up_character_body {
    font-size: 20px;
    font-family: 'Shentox Medium';
}

/* Flat icon buttons -- no circle background, just the glyph, colored like
   any other secondary text/control on the page and brightening on hover.
   Used for both header triggers (manage characters, logout) and the modal
   close button. */
.up_icon_btn {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 32px;
    width: 32px;
    background: none;
    border: none;
    padding: 0;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.up_icon_btn svg { height: 32px; width: 32px; fill: #999999; transition: fill 0.15s ease; }
.up_icon_btn:hover svg { fill: #eeeeee; }

/* Modal close button shares .up_icon_btn but keeps its original, smaller
   glyph size (pre-dating the header triggers' 64px/32px sizing changes) --
   the button box itself stays the same 32px as the header triggers. */
#up_char_modal_close svg { height: 20px; width: 20px; }

/* The 3 character-toolbar buttons (refresh, manage characters, logout)
   get the same boxed look as .up_filter_reset -- background/border/radius/
   hover-brightens-border -- all harmonized to the refresh button's 44px
   scale (was 32px, then refresh alone was bumped to 44px; now all three
   match it). Scoped to .up_character_options specifically so
   #up_char_modal_close (also .up_icon_btn, but in the modal header rather
   than this toolbar) stays exactly as it was. */
.up_character_options .up_icon_btn {
    height: 44px;
    width: 44px;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 0 0.5em;
    transition: border-color 0.15s ease;
}
.up_character_options .up_icon_btn:hover { border-color: var(--borderColor62); }

/* Manage-characters modal -- a real centered dialog over a dimmed,
   window-wide backdrop (position:fixed + inset:0), rather than a dropdown
   anchored to the trigger icon. */
.up_char_modal_backdrop {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background-color: rgba(0, 0, 0, 0.6);
    display: flex;
    justify-content: center;
    align-items: center;
}
.up_char_modal_backdrop[hidden] { display: none; }

/* Overall box scaled up 50% from the original (width/padding/gap) --
   text size is deliberately untouched here, only the box itself grows. */
.up_char_modal {
    display: flex;
    flex-direction: column;
    gap: 18px;
    width: min(630px, 90vw);
    max-height: 80vh;
    background-color: #161616;
    border: 1px solid #474747;
    border-radius: 10px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
    padding: 24px;
    box-sizing: border-box;
}

.up_char_modal_header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.up_char_modal_title {
    font-size: 1.2em;
    font-family: 'Shentox Medium';
    color: #eeeeee;
}

.up_char_menu_list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    /* Exactly 4 rows tall before the scrollbar kicks in: each row is 66px
       (48px avatar + 9px top/bottom padding) and rows are separated by the
       6px gap above -- 4*66 + 3*6 = 282px. A 5th character is the first to
       need scrolling. */
    max-height: 282px;
    overflow-y: auto;
    /* overflow-x must be set explicitly alongside overflow-y -- leaving it
       at its default 'visible' doesn't actually stay visible: per the CSS
       overflow spec, when one axis is non-'visible' the other computes to
       'auto' too, which was silently turning this into a horizontal
       scrollport (the stray scrollbar at the bottom of the list) and
       clipping any tooltip from a row that tried to render past its
       edge. Explicit 'hidden' here stops the phantom scrollbar; it still
       clips tooltips that would overflow left/right, but centering them
       properly (see lib/custom_elements.css) keeps that from happening in
       practice. */
    overflow-x: hidden;
}

.up_char_menu_row {
    display: flex;
    align-items: center;
    gap: 15px;
    padding: 9px 12px;
    border-radius: 6px;
}
.up_char_menu_row.current { background-color: #212121; }

.up_char_menu_img { display: flex; height: 48px; width: 48px; flex-shrink: 0; }
.up_char_menu_img > img { height: 48px; width: 48px; border-radius: 50%; }

.up_char_menu_name {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 1em;
    font-family: 'Shentox Medium';
    color: #eeeeee;
}

.up_char_menu_tag {
    font-size: 12px;
    font-family: 'Shentox Light';
    color: #6fcc3d;
    border: 1px solid #6fcc3d;
    border-radius: 4px;
    padding: 2px 8px;
    text-transform: uppercase;
}

.up_char_menu_actions {
    display: flex;
    align-items: center;
    gap: 2px;
}

.up_char_menu_action {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 36px;
    width: 36px;
    background: none;
    border: none;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.up_char_menu_action svg { height: 20px; width: 20px; fill: #999999; transition: fill 0.15s ease; }
.up_char_menu_action:hover svg { fill: #eeeeee; }

/* The refresh/remove icons sit at the far-right edge of each row, inside
   .up_char_menu_list which is overflow-x:hidden -- a centered tooltip would
   still overflow that boundary and get clipped, so anchor it to the icon's
   right edge and let it grow leftward instead. */
.up_char_menu_actions [data-tooltip]:before {
    left: auto;
    right: 0;
    transform: none;
}
.up_char_menu_actions [data-tooltip]:after {
    left: auto;
    right: 13px;
    margin-left: 0;
}

/* The first row sits flush against the top of the scrollable list, so a
   tooltip opening upward has no room and gets clipped by overflow-y:auto.
   Flip it to open downward, over the row(s) below, for that row only. */
.up_char_menu_row:first-child .up_char_menu_actions [data-tooltip]:before {
    bottom: auto;
    top: 100%;
    margin-bottom: 0;
    margin-top: 5px;
}
.up_char_menu_row:first-child .up_char_menu_actions [data-tooltip]:after {
    bottom: auto;
    top: 150%;
}

.up_char_menu_add {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: none;
    border: 1px solid #474747;
    border-radius: 6px;
    color: #999999;
    font-family: 'Shentox Light';
    font-size: 1em;
    padding: 8px 12px;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
/* Sized in em rather than a fixed px so it tracks font-size (1em above)
   automatically -- if that ever changes again this doesn't need touching. */
.up_char_menu_add svg { height: 1em; width: 1em; fill: #999999; }
.up_char_menu_add:hover { color: #eeeeee; border-color: #999999; }
.up_char_menu_add:hover svg { fill: #eeeeee; }

/* Explicit 1em (=16px, matching the site's html/body base) anchor -- every
   size below the .up_planets subtree is expressed as an em multiple of this
   one value, so the whole component scales together from one place. */
.up_planets {
    display: flex;
    flex-direction: column;
    gap: 1.375em;
    font-size: 1em;
    width: 100%; /* #content-UP uses align-items:center (flex column), so
                    without an explicit width this shrink-wrapped to its
                    content instead of filling the panel -- that was the
                    "too narrow" bug. Scoped to this class only, doesn't
                    touch #content-UP or any sibling .pTools-Content panel
                    (CR/SC/SB) that other tools rely on. */
    box-sizing: border-box; /* so the padding below doesn't push width past 100% */
    padding: 16px;
    flex: 1;
    min-height: 0; /* required alongside overflow-y below for a flex child
                      to actually scroll instead of just growing its
                      flex-column parent past the viewport. */
    overflow-y: auto;
}

/* Mirrors the existing #content-CR scrollbar treatment so this panel's
   internal scrollbar matches the one other tool panels already use. */
.up_planets::-webkit-scrollbar { display: block; width: 14px; height: 14px; }
.up_planets::-webkit-scrollbar-track { background: var(--scrollbarBG); }
.up_planets::-webkit-scrollbar-thumb { background-color: var(--thumbBG); border-radius: 0; border: 1px solid var(--scrollbarBG); }
.up_planets { scrollbar-color: var(--thumbBG) var(--scrollbarBG); }

.up_planets_legend {
    display: flex;
    align-self: center;
    justify-content: flex-end;
    /* Pushes itself (now a flex item inside .up_filters, not its own
       standalone row) all the way to the bar's own far right, past
       whatever the dropdown groups/reset/attn chip leave unclaimed. */
    margin-left: auto;
    gap: 1em;
    flex-wrap: wrap;
    font-size: 1em;
    color: var(--textColor);
}

.up_planets_legend span {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
}

.up_attn_dot {
    display: inline-block;
    width: 0.5em;
    height: 0.5em;
    border-radius: 50%;
}

.up_attn_dot.good { background-color: #6fcc3d; }
.up_attn_dot.warn { background-color: #cc793d; }
.up_attn_dot.bad { background-color: #f30202; }

/* ---- Colony list filter bar -- character / planet type / set-up type
   (extractor, P1-P4) / needs-attention, all applied client-side
   (script.js) against data-owner-character-id / data-planet-type /
   data-setup-tiers / data-attn on each .colony-row, no re-fetch needed.
   Chip styling deliberately echoes .pill (dark, rounded, muted text that
   brightens on the active state) rather than introducing a new visual
   language for "a small labeled toggle" on this page.

   Also holds the reset button (far left) and .up_planets_legend (far
   right) as regular flex children -- see template_planets.php.

   No longer sticky, and no longer even inside .up_planets -- reverted
   per direct feedback: pinning it while the list scrolled kept finding
   new things (tooltip-elevated pills, tile content) that could still
   render over it, so instead of chasing those, it's now a sibling of
   .up_planets rather than its first child. That takes it completely out
   of .up_planets' own internal scroll, so it just sits permanently
   visible above the list -- same as .up_characters above IT -- with no
   sticky/z-index trickery involved at all.

   .up_planets' own 16px padding used to provide this bar's inset for
   free; as a direct child of #content-UP now (zero padding of its own,
   same as .up_characters), it needs that inset itself: width trimmed by
   32px total and #content-UP's own align-items:center centers the
   narrower box, so the 16px lands evenly on both sides without needing
   an explicit left/right margin -- only top/bottom margin is set. */
.up_filters {
    display: flex;
    flex-wrap: wrap;
    /* center, not flex-start -- the reset button, the three dropdown
       triggers (now 36px tall), the shorter attn checkbox chip, and the
       legend text all have different natural heights, so top-aligning
       them left the shorter ones looking like they'd drifted upward
       relative to the row; centering keeps every item's middle on one
       shared line regardless of its own height. */
    align-items: center;
    gap: 1.25em;
    margin: 16px 0px 8px;
    width: calc(100% - 32px);
    box-sizing: border-box;
    padding: 0.875em 1em;
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
}

/* Colony list layout switcher -- far left of the filter bar, ahead of the
   "|" divider and the reset/dropdown controls. Same 36px height as the
   dropdown triggers/reset button next to it (see .up_filter_dd_trigger,
   .up_filter_reset) so it lines up on the same row, just built as one
   dark color16/borderColor31 box (matching those same tokens) holding two
   icon buttons instead of one. The active layout's button gets its own
   slightly lighter background + brighter icon, same brighten-on-select
   language as everywhere else on this bar; the inactive one sits flat
   until hovered, echoing .up_icon_btn's own #999999 -> #eeeeee hover. */
.up_layout_toggle {
    display: flex;
    align-items: stretch;
    gap: 0.125em;
    height: 36px;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 0.1875em;
}
.up_layout_toggle_btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    background: none;
    border: none;
    border-radius: 4px;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.up_layout_toggle_btn svg { width: 16px; height: 16px; fill: #999999; transition: fill 0.15s ease; }
.up_layout_toggle_btn:hover svg { fill: var(--textColor3); }
.up_layout_toggle_btn.active { background-color: var(--color42); }
.up_layout_toggle_btn.active svg { fill: var(--textColor3); }

/* Plain 1px rule, not a literal "|" glyph -- reads the same at any zoom
   level and never fights the row's own line-height the way a text
   character would. Sized to the row's shared 36px control height via
   align-self:stretch (overriding .up_filters' own align-items:center just
   for this one child) rather than a fixed height that could drift out of
   sync with it. */
.up_filter_divider {
    align-self: stretch;
    width: 1px;
    background-color: var(--borderColor31);
}

/* ---- Fleet summary bar -- same outer card treatment as .up_filters right
   above it (dark tileBg, 1px border, 8px radius, same calc(100% - 32px)
   inset logic against #content-UP's zero padding), holding five read-only
   stat chips instead of controls. Values come from script.js's
   updateFleetSummary(), summed only across whichever .colony-row elements
   the filter bar currently leaves visible -- so toggling a filter updates
   this bar the same tick it updates the list below. Each chip reuses the
   dropdown-trigger's own color16/borderColor31 box language rather than a
   new component, and the two "needs attention" chips are colored with the
   exact same warn/bad tokens as .up_attn_dot and the storage bars, so a
   red number here means the same thing a red dot means everywhere else. */
.up_summary {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 0.75em;
    margin: 0 0 8px;
    width: calc(100% - 32px);
    box-sizing: border-box;
    padding: 0.875em 1em;
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
}
.up_summary_stat {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.2em;
    min-width: 6.5em;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 0.5em 0.9em;
}
.up_summary_val {
    font-family: 'Shentox Medium';
    font-size: 1.375em;
    line-height: 1.1;
    color: var(--textColor3);
}
.up_summary_stat.is-warn .up_summary_val { color: #cc793d; }
.up_summary_stat.is-bad .up_summary_val { color: #f30202; }
.up_summary_cap { font-size: 0.8125em; color: var(--textColor); white-space: nowrap; }

/* Four of the five stat chips double as click-to-filter toggles (script.js
   wires this up at render time -- "colonies" is left alone, there's
   nothing for it to filter by). Border/background brighten to the same
   var(--color42)/var(--borderColor62) pair .panel-row.selected and
   .up_layout_toggle_btn.active already use for "this is the active thing"
   elsewhere, so a clicked chip reads consistently with the rest of the
   page instead of inventing a new "selected" language. */
.up_summary_stat-filterable {
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.up_summary_stat-filterable:hover { border-color: var(--borderColor62); }
.up_summary_stat-filterable.active { background-color: var(--color42); border-color: var(--borderColor62); }

.up_filter_group { display: flex; flex-direction: column; gap: 0.4375em; }

/* .up_filter_group_attn has just the one chip and no dropdown trigger
   above it -- align it to the row's baseline instead of sitting a
   trigger-height too high next to the three dropdown groups. */
.up_filter_group_attn { justify-content: flex-end; padding-top: 0; }

.up_filter_chip {
    display: inline-flex;
    align-items: center;
    gap: 0.375em;
    height: 36px;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 100px;
    padding: 0 0.9em 0 0.6em;
    font-size: 1em;
    color: var(--textColor);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
    user-select: none;
}
.up_filter_chip:has(input:checked) { border-color: var(--borderColor62); color: var(--textColor3); }

/* ---- Filter dropdowns (character / planet type / set-up) -- a custom
   trigger+panel pair replaces the old always-visible chip row, built from
   scratch (no <select>, no native checkbox rendering) so it can share the
   filter bar's own dark/rounded visual language exactly, rather than
   inheriting whatever the browser/OS draws a <select> or a checkbox as.
   Each panel's first row is a synthetic "All" checkbox -- not a real
   filter value (see data-role="all"), purely a mass check/uncheck for
   the real options below it, kept in sync both ways by script.js. */
.up_filter_dd { position: relative; }

.up_filter_dd_trigger {
    display: flex;
    align-items: center;
    gap: 0.5em;
    /* Explicit 36px box (border-box so the border/padding below sit
       inside it, not on top of it) rather than letting padding + line-
       height add up to a height -- guarantees exactly 36px regardless of
       font metrics. Font-size raised from 0.8125em to 1em to match --
       roughly the same proportion the box height grew by -- and the
       caret (an em-sized child) scales right along with it, no separate
       change needed there. Horizontal padding stays in em too, so it
       grows a little with the larger text instead of suddenly looking
       cramped. */
    height: 36px;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 0 0.75em;
    font-size: 1em;
    font-family: 'Shentox Light';
    color: var(--textColor);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.up_filter_dd_trigger:hover,
.up_filter_dd.open .up_filter_dd_trigger { border-color: var(--borderColor62); color: var(--textColor3); }

.up_filter_dd_caret {
    width: 0.7em;
    height: 0.7em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    transition: transform 0.15s ease;
}
.up_filter_dd.open .up_filter_dd_caret { transform: rotate(180deg); }

.up_filter_dd_panel {
    position: absolute;
    top: calc(100% + 0.375em);
    left: 0;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 0.125em;
    min-width: 11em;
    /* Raised from 16em -- with the option rows' font-size bumped to 0.9em
       the "All" row + separator + all 8 planet-type options (the longest
       fixed-length list among the three dropdowns; Character can run
       longer still depending on how many are linked) no longer fit under
       16em, which was clipping the panel and forcing an inner scrollbar --
       the separator sitting right at that clipped edge is what read as
       "not showing". 19em comfortably fits that full 9-row list with a
       little headroom; overflow-y:auto stays as the safety valve for
       whenever the Character list genuinely runs longer than that. */
    max-height: 19em;
    overflow-y: auto;
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
    padding: 0.4375em;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.up_filter_dd_panel[hidden] { display: none; }

.up_filter_dd_option {
    display: flex;
    align-items: center;
    gap: 0.5625em;
    padding: 0.375em 0.5em;
    border-radius: 4px;
    font-size: 0.9em;
    color: var(--textColor);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    user-select: none;
    white-space: nowrap;
}
.up_filter_dd_option:hover { background-color: var(--color42); color: var(--textColor3); }
.up_filter_dd_all { font-weight: 600; }
.up_filter_dd_sep { height: 1px; background-color: var(--borderColor31); margin: 0.25em 0.5em; }

/* Fully custom checkbox -- appearance:none strips the browser/OS's own
   checkbox rendering entirely (the "avoid system defaults" ask). Reverted
   per direct feedback to a plain black box with a thin gray border in
   BOTH states (never fills white on check) and a light gray/white tick,
   rather than the earlier white-fill version. Shared with .up_filter_chip
   input (the standalone "Needs attention only" toggle) so every checkbox
   on this bar reads the same way, not just the three dropdown panels. */
.up_filter_dd_option input[type="checkbox"],
.up_filter_chip input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    position: relative;
    width: 14px;
    height: 14px;
    margin: 0;
    flex-shrink: 0;
    border: 1px solid var(--borderColor62);
    border-radius: 3px;
    background-color: #000000;
    cursor: inherit;
    transition: border-color 0.15s ease;
}
.up_filter_dd_option input[type="checkbox"]:checked::after,
.up_filter_chip input[type="checkbox"]:checked::after {
    content: "";
    position: absolute;
    left: 4px;
    top: 1px;
    width: 3px;
    height: 7px;
    border: solid #d2d2d2;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* Icon-only now (first child of .up_filters, far left) -- box dimensions
   deliberately echo .up_filter_dd_trigger's own background/border/radius/
   height/font-size so it lines up exactly with the (now 36px) dropdown
   triggers beside it, even though it carries an icon instead of text.
   The svg is raised from 16px to 20px to match, roughly the same
   proportion the box itself grew by. Hover now matches
   .up_filter_dd_trigger's own (border brightens to --borderColor62, text/
   icon brightens to --textColor3) -- this used to deliberately stay flat
   with no hover state at all per an earlier round of feedback, reversed by
   a later one asking for the dropdown's own hover treatment here instead. */
.up_filter_reset {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 36px;
    width: 36px;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 0 0.5em;
    font-size: 1em;
    color: #d2d2d2;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.up_filter_reset svg { width: 20px; height: 20px; fill: currentColor; }
.up_filter_reset:hover { border-color: var(--borderColor62); color: var(--textColor3); text-decoration: none; }

.up_filters_empty {
    color: var(--textColor);
    font-size: 0.9375em;
    padding: 1em;
    text-align: center;
}
.up_filters_empty[hidden] { display: none; }

/* ---- Per-colony row: a shared band (var(--color42), lighter) wraps the 3
   tiles (--tileBg, darker) so the planet/details/owner tiles visibly read
   as one row even when the middle tile grows very tall -- without this,
   tracing which owner tile belongs to which planet tile across a tall row
   got difficult. ---- */
.colony-row {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    gap: 16px;
    /* No row background plate any more -- the tiles inside carry the card
       look themselves (below), matching how the Panel layout's cards sit
       directly on the page background with no wrapper behind them. */
    border-radius: 12px;
    padding: 10px;
}

/* Same card treatment as the Panel layout's .panel-row/.detail-panel
   (tileBg fill, borderColor31 hairline, 12px radius), so the two views
   read as one design language. */
.tile {
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 12px;
}

.tile-planet {
    padding: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* Opens/closes this row's in-game layout tile (see .tile-map below and
       the click wiring in script.js) -- every colony row is clickable here
       now, even colonies with no facility position data yet, since the
       opened tile itself explains that case rather than the thumbnail
       silently doing nothing. */
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: background-color 0.2s ease;
    /* A user clicking this repeatedly to flip the layout tile open/closed
       was highlighting the thumbnail/pills as selectable text on a fast
       double-click -- this is a button, not text, so it should never be
       selectable at all (belt-and-braces with the mousedown preventDefault
       in script.js, which also covers browsers that ignore user-select on
       drag-selection). */
    user-select: none;
    -webkit-user-select: none;
}

.tile-planet:hover { background-color: #272727; }

.thumb-wrap {
    position: relative;
    width: 128px;
    height: 128px;
    flex-shrink: 0;
}

.thumb-wrap img.pico {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.pill {
    position: absolute;
    display: flex;
    align-items: center;
    gap: 0.3em;
    background-color: var(--color16);
    border: 1px solid #313131;
    border-radius: 100px;
    font-size: 0.875em;
    padding: 0.25em 0.65em 0.25em 0.45em;
    color: var(--textColor);
    white-space: nowrap;
}

.pill svg {
    width: 1.2em;
    height: 1.2em;
    flex-shrink: 0;
    fill: currentColor;
}

/* .pill-sync carries data-tooltip (the "last synced" hover detail) --
   the sitewide [data-tooltip] component (../lib/custom_elements.css) puts
   z-index:50 on any element that attribute lands on, which lets this pill
   (and anything else with a tooltip) float above later UI like the filter
   dropdown panels (.up_filter_dd_panel, z-index:30) as they open over the
   colony list. .pill-count has no data-tooltip, so it never picked up
   that elevation and always stacked normally.

   A plain ".pill-sync { z-index: auto }" isn't enough to reset it: index.html
   loads styles.css BEFORE ../lib/custom_elements.css, so on an ordinary
   cascade tie (both selectors are equally specific -- one class, one
   attribute) the later sitewide rule always wins regardless of what's
   written here. Adding [data-tooltip] to this selector bumps it to two
   selectors' worth of specificity, which beats the sitewide rule outright
   instead of relying on source order -- the hover tooltip itself still
   pops out fine, it's just no longer artificially elevated above
   everything else on the page. */
.pill-sync[data-tooltip] { top: -0.75em; left: -0.875em; z-index: auto; }
.pill-count { bottom: -0.75em; left: -0.875em; }
/* Upgrade level (ESI's own upgrade_level, 0-5) on the panel layout's own
   thumbnail -- top-right corner, so it never collides with .pill-sync
   (top-left) or .pill-count (bottom-left). Only rendered on .panel-row's
   thumb-wrap (see template_colony_tile.php), not the row list's
   .tile-planet thumb-wrap. */
.pill-cc { top: -0.75em; right: -0.875em; }

.attn {
    position: absolute;
    bottom: -0.5em;
    right: -0.5em;
    width: 1.8em;
    height: 1.8em;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px solid var(--tileBg);
    color: #fff;
    font-size: 0.75em;
    font-weight: 800;
    font-family: Georgia, serif;
}

.attn.good { background-color: #6fcc3d; }
.attn.warn { background-color: #cc793d; }
.attn.bad { background-color: #f30202; }

.tile-details {
    padding: 1.125em 1.375em;
    flex: 1;
    min-width: 0;
}

.colony-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5em;
    font-size: 1.25em;
    font-weight: 600;
    font-family: 'Shentox Medium';
    color: var(--textColor3);
    margin-bottom: 0.5625em;
}

/* Holds the name/type/security text -- its own flex+wrap, separate from
   .colony-title's space-between, so a long planet name can still wrap
   onto a second line without dragging the refresh button down with it. */
.colony-title-text {
    display: flex;
    align-items: center;
    gap: 0.4375em;
    flex-wrap: wrap;
}

.colony-title .sep { color: var(--textColor); font-weight: 400; }
.colony-title .sec { font-family: 'Shentox Bold'; }

/* Per-colony "Refresh" button -- same icon/animation as the "Refresh
   token" action in the manage-characters menu (.up_char_menu_action),
   sized to sit inline with .colony-title's 1.25em text (bumped up from an
   earlier, noticeably smaller pass so the icon reads at the same visual
   weight as the title next to it) instead of a 36px menu row. Shrinks to
   nothing rather than wrapping under long text, so it stays pinned to the
   far right of the title row. */
.colony-refresh-btn {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-shrink: 0;
    height: 1.9em;
    width: 1.9em;
    background: none;
    border: none;
    padding: 0;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.colony-refresh-btn svg { height: 1.3em; width: 1.3em; fill: #999999; transition: fill 0.15s ease; }
.colony-refresh-btn:hover svg { fill: #eeeeee; }
.colony-refresh-btn.refreshing svg { animation: colony-refresh-spin 0.8s linear infinite; fill: #eeeeee; }
.colony-refresh-btn:disabled { cursor: default; }

/* Detail panel's own "Refresh" button (template_colony_tile.php) -- same
   icon/animation/behavior as .colony-refresh-btn above (both are wired to
   the same refreshOneColony() in script.js), just scaled to sit inline
   with .detail-title's larger 1.75em text and placed to the LEFT of the
   planet name (it's the title's first child, ahead of the name text)
   instead of pinned to the row's far right. Same height/width-to-svg
   ratio as .colony-refresh-btn so the two read as the same control at two
   different sizes rather than two different designs. */
.detail-refresh-btn {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-shrink: 0;
    height: 1.9em;
    width: 1.9em;
    background: none;
    border: none;
    padding: 0;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.detail-refresh-btn svg { height: 1.3em; width: 1.3em; fill: #999999; transition: fill 0.15s ease; }
.detail-refresh-btn:hover svg { fill: #eeeeee; }
.detail-refresh-btn.refreshing svg { animation: colony-refresh-spin 0.8s linear infinite; fill: #eeeeee; }
.detail-refresh-btn:disabled { cursor: default; }

/* Top bar's "Refresh all colonies" button (#up_char_refresh_btn,
   template_planets.php's .up_character_options) -- same .refreshing spin
   convention as the per-colony and detail-panel refresh buttons above
   (script.js adds/removes the class around one full manual
   runColonyAutoRefreshPass()), it just inherits its base look from
   .up_icon_btn instead of needing its own sizing rules. */
#up_char_refresh_btn.refreshing svg { animation: colony-refresh-spin 0.8s linear infinite; }
#up_char_refresh_btn:disabled { cursor: default; }

@keyframes colony-refresh-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .colony-refresh-btn.refreshing svg { animation: none; }
    .detail-refresh-btn.refreshing svg { animation: none; }
    #up_char_refresh_btn.refreshing svg { animation: none; }
}

.buildings {
    display: flex;
    flex-direction: column;
    gap: 0.0625em;
}

.brow {
    display: flex;
    align-items: center;
    gap: 0.75em;
    font-size: 1em;
    color: var(--textColor);
    /* Horizontal padding + matching negative margin cancel out visually --
       every row's content still lines up exactly where it did with the old
       padding:0.375em 0 -- but the row's own box now extends a little past
       its content on both sides, which is what gives .brow-highlight (the
       click-to-locate flash below) room to round its corners and inset
       from the tile's edges instead of touching them flush. */
    padding: 0.375em 0.5em;
    margin: 0 -0.5em;
    border-radius: 6px;
    position: relative;
}

/* position:relative + overflow:hidden turn this into a fixed-size anchor
   for the tier/heads badge below -- the badge is absolutely positioned so
   it can never push .bicon-wrap's own 2em/2em box to grow, and overflow:
   hidden is a hard backstop so the badge itself can never visually spill
   past the icon's own edges either. */
.brow .bicon-wrap { width: 2em; height: 2em; flex-shrink: 0; position: relative; overflow: hidden; }
.brow .bicon-wrap img { width: 100%; height: 100%; display: block; }
.brow .res-icon { width: 2em; height: 2em; border-radius: 3px; flex-shrink: 0; }
/* Factory tier (P1-P4) and extractor head-count badges: a standalone pill
   right after the icon, back to how the tier pill originally looked before
   it was tried as a corner badge overlaid on the icon -- restored per
   request, at its original (pre-shrink) size. Not scoped to .brow (any
   ancestor will do). .buildings-toggle's own hidden-row count now reuses
   .pill instead (see .buildings-toggle-count below), to match the other
   detail-tile pills' true capsule shape rather than this rectangular one. */
.res-tier {
    flex-shrink: 0;
    font-size: 0.8125em;
    line-height: 1;
    color: var(--textColor);
    background-color: var(--color16);
    border: 1px solid #313131;
    border-radius: 4px;
    padding: 0.3em 0.4em;
}
.brow .res-name { color: var(--textColor3); min-width: 8em; flex-shrink: 0; }
.brow .time { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Dot-leader connecting the building name (left) to its value (right) --
   with the middle tile this wide, a plain gap made it hard to tell which
   value belongs to which row when several rows look similar (e.g. two 0%
   storage pins in a row). Classic "leader line" trick: a flex-growing
   element with only a dotted bottom border. It fully replaces the old
   flex:1 on .sc-list / margin-left:auto on .time as the thing that pushes
   the value to the far right, so that invariant still holds either way. */
.brow-leader {
    flex: 1;
    min-width: 12px;
    align-self: center;
    height: 0;
    margin: 0 0.625em;
    border-bottom: 2px dotted rgba(153, 153, 153, 0.15);
}

.brow .loadbar-wrap { flex-shrink: 0; width: 12.5em; display: flex; align-items: center; gap: 0.5625em; margin-left: 0.75em; }
/* Thickened from the old 0.375em (~6px) sliver -- tall enough to hold
   .loadbar-label's centered text (see below) directly on the bar itself,
   like a game HUD meter, instead of the number needing its own separate
   line. position:relative so .loadbar-sim/.loadbar-real/.loadbar-label
   can stack on top of each other instead of side by side. */
.brow .loadbar { position: relative; flex: 1; height: 1.375em; border-radius: 4px; background-color: #101010; overflow: hidden; } /* deliberately darker than --tileBg (#212121) so the empty track stays visible on the new tile background */
/* Fill colors deliberately darker/less saturated than the good/warn/bad
   tokens used elsewhere (attention dots, badges, status text) -- those
   never have text sitting on top of them, but .loadbar-label's white text
   does, and the original #6fcc3d green in particular was far too light
   (~2:1 contrast against white) for that text to read over. All three
   were darkened by roughly the same amount so the set still reads as one
   consistent traffic-light -- not just green fixed in isolation next to
   two untouched, much brighter warn/full colors. Each now clears ~4.5:1
   contrast against white on its own. */
.brow .loadbar > div { position: absolute; top: 0; left: 0; height: 100%; border-radius: 4px; background-color: #4a7c2f; }
.brow .loadbar > div.warn { background-color: #8a5a2e; }
.brow .loadbar > div.full { background-color: #8c1c1c; }
/* .loadbar-sim is the projected/simulated fill level (see
   lib/simulation.php's simulateColonyRouteFlow) -- drawn first (so
   .loadbar-real's higher z-index paints over it) and at half opacity, so
   it reads as a translucent preview behind the real synced bar. Same
   color scale (good/warn/full) as the real bar, just softer, rather than
   a separate color that would need its own legend. */
.brow .loadbar > div.loadbar-sim { opacity: 0.5; z-index: 1; }
.brow .loadbar > div.loadbar-real { z-index: 2; }
/* Flat "used / capacity" (e.g. "4.4k / 12k m³"), centered on the bar
   above both fill layers -- z-index above .loadbar-real (2) so it's
   always readable regardless of how full the bar is. White + a dark
   text-shadow rather than a themed text color: this sits on top of
   whichever fill color (green/orange/red) or empty track happens to be
   underneath it at that point, and needs to stay legible against all of
   them rather than being tuned for just one. Precise real numbers are
   still in the .loadbar-sim title tooltip for anyone hovering for exact
   figures; this is deliberately the abbreviated, at-a-glance version. */
.brow .loadbar-label {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.6875em;
    font-variant-numeric: tabular-nums;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), 0 0 3px rgba(0, 0, 0, 0.6);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 0 0.25em;
    pointer-events: none;
}
.brow .pct { min-width: 2.125em; text-align: right; color: var(--textColor3); }

/* No longer flex:1 -- .brow-leader now does the job of pushing this (and
   the bar/time after it) to the far right, so this just sits at its own
   natural width immediately before .loadbar-wrap. */
.sc-list { flex-shrink: 0; display: flex; align-items: center; gap: 0.625em; }
.sc-item { display: flex; align-items: center; gap: 0.25em; font-size: 0.8125em; color: var(--textColor); }
.sc-item img { width: 24px; height: 24px; border-radius: 3px; } /* literal px, not em -- .sc-item's own font-size (0.8125em) was compounding with the icon's em-based size and landing at ~19.5px instead of the intended value */
.sc-more { font-size: 0.8125em; color: var(--textColor); }

/* Collapsed-by-default tail of a long buildings list, plus its toggle.
   The toggle is laid out as one more .brow-shaped row -- same font-size
   (1em, so its 2em .bicon-wrap and 0.8125em .res-tier pill resolve to the
   exact same pixel sizes .brow's own icon/pills do), same padding and
   negative horizontal margin trick, so its icon column lines up exactly
   with every real building icon above it instead of starting flush at
   the tile's edge. script.js (setBuildingsToggleState) swaps the chevron
   direction and shows/hides the count pill on toggle; only margin-top
   (the gap from the last visible row) carries over from the old
   text-link version. */
.buildings-extra[hidden] { display: none; }
.buildings-toggle {
    display: flex;
    align-items: center;
    gap: 0.75em;
    align-self: flex-start;
    margin: 0.375em -0.5em 0;
    background: none;
    border: none;
    color: #d2d2d2;
    font-size: 1em;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    padding: 0.375em 0.5em;
    border-radius: 6px;
}
.buildings-toggle:hover { color: #d2d2d2; text-decoration: none; }
/* This button itself carries data-tooltip ("Show X more" / "Show less"),
   so the sitewide [data-tooltip] component (../lib/custom_elements.css)
   puts z-index:50 on it -- same root cause as .pill-sync above, and it
   was letting the button (chevron + count pill together, since they're
   its own children) float above the filter dropdown panels
   (.up_filter_dd_panel, z-index:30) as they open over the colony list.
   [data-tooltip] added to the selector bumps specificity so this wins
   outright rather than depending on stylesheet order (styles.css loads
   before custom_elements.css, so a same-specificity override here would
   otherwise lose the cascade tie). */
.buildings-toggle[data-tooltip] { z-index: auto; }
.buildings-toggle .bicon-wrap {
    width: 2em;
    height: 2em;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
.buildings-toggle-icon { width: 16px; height: 10px; flex-shrink: 0; }
/* Matches the other detail-tile pills (.pill-sync/.pill-count) now instead
   of .res-tier's rectangular badge shape -- .pill is normally absolutely
   positioned over a thumbnail corner, which doesn't apply here since this
   pill sits inline in the toggle button's own flow, so that's cancelled. */
.buildings-toggle-count { position: static; }

/* Mirrors .tile-planet's padding (24px) and content size (128px) exactly,
   with no explicit width of its own, so both tiles auto-size to the same
   total width -- verified this holds with the avatar raised to 128px to
   match the planet thumbnail; no need for the 64px fallback. */
.tile-owner {
    padding: 24px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4375em;
    flex-shrink: 0;
}

/* Circular, matching the planet thumbnail's own shape/size (.thumb-wrap
   img.pico, also 128px, also border-radius:50%) instead of the old
   rounded square -- both are "a picture representing this row's subject",
   so they read as a matched pair now rather than two different shapes. */
.tile-owner .avatar { width: 128px; height: 128px; border-radius: 50%; overflow: hidden; }
.tile-owner .avatar img { width: 100%; height: 100%; display: block; }
.tile-owner .name { font-size: 1.125em; color: var(--textColor); text-align: center; line-height: 1.3; }

/* Responsive: shrink the planet thumbnail/badges back down on narrow
   windows instead of letting a fixed-huge circle crowd out the row. */
@media (max-width: 720px) {
    .colony-row { flex-wrap: wrap; }
    .tile-planet { padding: 14px; }
    .thumb-wrap { width: 88px; height: 88px; }
    .pill-sync { top: -0.6em; left: -0.7em; }
    .pill-count { bottom: -0.6em; left: -0.7em; }
    .pill-cc { top: -0.6em; right: -0.7em; }
    .attn { width: 1.6em; height: 1.6em; }
    .tile-owner { flex-direction: row; padding: 14px; }
    .tile-owner .avatar { width: 44px; height: 44px; }
    .brow .loadbar-wrap { width: 8em; }
    .brow .res-name { min-width: 6em; }
    .tile-map { width: 100%; }
}

/* ---- In-game layout: 4th tile, server-rendered per row (see
   template_planets.php) from that colony's own pin lat/long, but hidden
   by default -- opened/closed by clicking .tile-planet, one row at a
   time (script.js). Square corners, not circular: matches the
   rectangular tile language of the other 3 tiles instead of introducing
   a one-off round shape. Same --tileBg/8px-radius as .tile-owner. Sits
   between .tile-details and .tile-owner in the DOM (moved there from
   after .tile-owner) so opening it reads as expanding outward from the
   details list rather than appending after the character tile.
   No header label or caption text -- align-items:stretch (inherited from
   .colony-row) plus flex:1 on .colonymap-stage let the map itself grow to
   fill the tile's full height (driven by however tall the buildings list
   next to it turns out to be), rather than sitting as a fixed-size square
   with dead space below it. The close X has nothing to anchor a header
   bar to any more, so it's a small overlay pinned to the corner instead.

   Opacity/transform here (rather than an instant [hidden] flip) is what
   gives the tile a smooth appear/disappear -- script.js still uses the
   [hidden] attribute to pull it in/out of the row's flex layout entirely
   when closed (so collapsed rows stay compact), but sequences adding/
   removing .is-open around that so the fade+slide actually gets to play
   instead of being skipped by display:none. */
.tile-map {
    padding: 14px;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    flex-shrink: 0;
    width: 343.75px;
    border: 1px solid #D2D2D2;
    position: relative;
    opacity: 0;
    transform: translateX(-8px);
    /* max-height is re-measured off .tile-details and applied inline
       (script.js, syncMapTileHeight) any time that height could have
       changed -- "Show more/less", a pin click force-expanding a
       collapsed section, or first opening. Transitioning it here is what
       makes the layout tile visibly grow/shrink to match instead of
       snapping straight to the new size. */
    transition: opacity 0.18s ease, transform 0.18s ease, max-height 0.2s ease;
}

.tile-map[hidden] { display: none; }
.tile-map.is-open { opacity: 1; transform: translateX(0); }

.tile-map .hdr {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 1;
}

.tile-map .hdr .x {
    display: block;
    color: var(--textColor);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    font-size: 1.1em;
    line-height: 1;
    background: rgba(22, 22, 22, 0.6);
    border-radius: 4px;
    padding: 2px 5px;
    transition: color 0.15s ease;
}

.tile-map .hdr .x:hover { color: var(--textColor3); }

.colonymap-stage { position: relative; flex: 1; min-height: 0; }
/* Draggable ("sliding") map (script.js): the fixed real-km scale means a
   spread-out colony can extend past this small canvas, so panning is how
   the rest of it gets reached. touch-action:none stops a touch-drag here
   from also scrolling the page while panning. Custom grab/grabbing cursors
   (site-styled, matching cursor_default.svg/cursor_pointer.svg) mark this
   area as draggable -- the keyword after the url() is the fallback if the
   svg ever fails to load, same pattern as the custom pointer cursor used
   for actually clickable things like the pins themselves. */
svg.colonymap { display: block; width: 100%; height: 100%; border-radius: 4px; cursor: url(../img/cursors/cursor_grab.svg), grab; touch-action: none; }
.colonymap-stage.panning svg.colonymap { cursor: url(../img/cursors/cursor_grabbing.svg), grabbing; }

/* In-game layout pins -- clickable svg <g> wrappers around a circle+icon
   (single building) or circle+icon+count-badge (an overlapping cluster,
   see the PHP clustering pass). pointer-events on the child <image> can be
   flaky across browsers inside an svg <g>, so the click/hover target is
   the whole group. */
.map-pin {
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    /* Never let a browser default focus ring show on this element (see the
       :focus-visible removal below for why) -- outline always traces the
       element's rectangular bounding box, never the circular ring drawn
       inside it, on any element that isn't natively rectangular. */
    outline: none;
}
/* A CSS filter (drop-shadow) on these pins was an earlier hover treatment,
   but filters on SVG elements force the browser to recomposite the whole
   <svg> on every filter change -- moving the mouse briskly across several
   adjacent/overlapping pins was re-triggering that recomposite fast enough
   to read as the whole layout "flashing". Thickening the pin's own ring
   is a plain presentation-attribute change (stroke-width), not a filter,
   so it repaints only that one pin. The command center has no ring at
   rest, so it gets one for the first time on hover -- same "this is
   interactive" cue via a different starting point.
   .pin-selected (added by selectMapPin in script.js when a pin is actually
   clicked/picked, not on hover) reuses this exact same ring treatment and
   just leaves it switched on -- so the selected pin keeps looking the way
   it does the moment you hover it, even after the mouse moves away. A
   drop-shadow "overglow" was tried here first but didn't land well, and
   dropping it also removes any temptation to put a filter back on this
   element -- keep it that way, see above. */
.map-pin-ring { transition: stroke-width 0.15s ease; }
.map-pin-cc-ring { stroke: transparent; stroke-width: var(--map-pin-cc-stroke, 2px); }
/* Hover/focus/selected indicator for EVERY colony map pin -- both the row
   list's .tile-map copy and the Detail panel's -- is the .map-pin-ring2
   outer circle each pin group now carries (template_colony_tile.php):
   invisible at rest, faint on hover (the pin's own :hover, keyboard
   :focus-visible, or .pin-hover mirrored from hovering a linked Detail
   card), full opacity + glow when selected. This REPLACED the old
   "thicken the inner status ring" treatment sitewide, per the approved
   interaction sketch: the building's own status ring color/weight never
   changes, the second circle alone carries the affordance, in the same
   --hoverColor as every other hover on the site. */
.map-pin-ring2 {
    fill: none;
    stroke: var(--hoverColor);
    stroke-width: 2.5px;
    opacity: 0;
    transition: opacity 0.15s ease, filter 0.15s ease;
    pointer-events: none;
}
.map-pin:hover .map-pin-ring2,
.map-pin:focus-visible .map-pin-ring2,
.map-pin.pin-hover .map-pin-ring2 { opacity: 0.4; }
.map-pin.pin-selected .map-pin-ring2 {
    opacity: 1;
    filter: drop-shadow(0 0 5px rgba(210, 210, 210, 0.75));
}
/* No outline-based focus ring here on purpose (see .map-pin's outline:none
   above) -- a real mouse click focuses this element the instant you press
   down (it's tabindex="0" for keyboard reachability), well before any of
   script.js's click handling runs, so an outline rule here would flash the
   browser's own square default focus ring for the whole press-and-hold
   window with nothing we can do about the timing. The ring2 rules just
   above respond to :focus-visible exactly like :hover, so keyboard
   Tab-navigation still gets a clear, circular "focus is here" indicator
   -- it's just never a rectangle, for keyboard OR mouse. */
.map-headdot, .map-headline { pointer-events: none; }

/* Off-canvas edge indicators (script.js, .colonymap-edge-indicators group
   in template_planets.php) -- a smaller, dimmed echo of a real .map-pin
   pinned to the canvas edge in the rough direction of whatever's currently
   panned out of view. Dimmed at rest so it never reads as an actual
   building on the map, brightening on hover/focus like any other clickable
   marker; the chevron and any count badge stay at full opacity throughout
   since they're the "there's more this way" cue, not the building itself. */
.map-edge-indicator {
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    outline: none;
    opacity: 0.62;
    transition: opacity 0.15s ease;
}
.map-edge-indicator:hover, .map-edge-indicator:focus-visible { opacity: 1; }
.map-edge-ring { transition: stroke-width 0.15s ease; }
.map-edge-indicator:hover .map-edge-ring, .map-edge-indicator:focus-visible .map-edge-ring { stroke-width: 2.4; }

/* Overlap picker: a small floating list anchored at a cluster's centroid
   (percent-positioned by PHP, relative to .colonymap-stage), listing each
   overlapping building so a crowded click resolves to one specific pin
   instead of guessing. Offset right of its anchor point rather than
   centered on it, so it never sits directly on top of the marker it
   belongs to. */
.pin-overlap-list {
    position: absolute;
    z-index: 2;
    min-width: 148px;
    background-color: var(--color16);
    border: 1px solid #313131;
    border-radius: 6px;
    padding: 4px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
    transform: translate(10px, -50%);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
}
.pin-overlap-list[hidden] { display: none; }
.pin-overlap-list.is-open { opacity: 1; pointer-events: auto; }
.pin-overlap-lbl {
    color: var(--textColor2);
    font-size: 10px;
    padding: 4px 8px 3px;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}
.pin-overlap-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 6px 8px;
    border-radius: 5px;
    border: none;
    background: none;
    color: var(--textColor3);
    font-family: 'Shentox Regular';
    font-size: 0.8125em;
    text-align: left;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: background-color 0.15s ease;
}
.pin-overlap-item:hover, .pin-overlap-item:focus-visible { background-color: var(--color42); }
.pin-overlap-item img { width: 18px; height: 18px; flex-shrink: 0; }
.pin-overlap-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }

/* Click-to-locate: a pin (or an overlap-list item) marks the matching row
   in the buildings list as selected, so the user can find it without
   hunting. A 1px outline rather than a background/border -- outline never
   participates in layout (unlike border, which would add its width to the
   row's box and nudge every row after it), so turning this on and off
   never bumps the list. Stays on the row until a different pin is picked
   or the map tile closes (script.js), rather than fading on a timer --
   it's a selection, not a toast. .brow's own padding/negative-margin pair
   (above) is what keeps this inset from the tile's edges instead of
   touching them flush; .brow's border-radius carries through automatically
   since outline follows it. */
.brow.brow-selected {
    outline: 1px solid #D2D2D2;
    outline-offset: -1px;
    background-color: var(--backgroundColor);
    transition: outline-color 0.15s ease;
}
/* A row with a real pin_id is clickable (script.js) -- selects and, if the
   map's currently panned away, re-centers on its matching building on the
   layout map. A row with no pin_id (no real ESI lat/long for that
   building) has nothing on the map to select, so it's left looking
   non-interactive. */
.brow[data-pin-id]:not([data-pin-id=""]) {
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: background-color 0.15s ease, outline-color 0.15s ease;
}
.brow[data-pin-id]:not([data-pin-id=""]):hover {
    background-color: rgba(210, 210, 210, 0.08);
}

@media (prefers-reduced-motion: reduce) {
    .tile-planet, .tile-map, .tile-map .hdr .x, .map-pin-ring, .map-pin-cc-ring,
    .map-pin-ring2,
    .pin-overlap-list, .pin-overlap-item, .brow.brow-selected, .up_icon_btn svg,
    .up_char_menu_action svg {
        transition: none !important;
    }
}

.up_alertsettings {
    /* Hidden for now -- moving to an overlay/options-menu at the end of the
       colony-list redesign (see roadmap). Markup/backend left intact so
       it's a one-line revert. */
    display: none;
    flex-direction: column;
    gap: 8px;
    margin: 12px 0 20px 0;
    padding: 12px;
    border-radius: 8px;
    background-color: #212121;
}

.up_alertsettings_title {
    font-family: 'Shentox Medium';
    font-size: 16px;
    color: var(--textColor3);
}

.up_alertsettings_row {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.up_alertsettings_row input[type="text"] {
    flex: 1;
    min-width: 260px;
}

.up_alertsettings_hint {
    font-size: 11px;
    color: var(--textColor);
}

#up_alert_status {
    font-size: 12px;
    color: var(--textColor);
}

.up_extraction_overdue {
    color: #f30202;
    font-family: 'Shentox Medium';
}

/* Unconfigured factory (no schematic assigned at all) -- same orange used
   everywhere else for the "warn" attention state (.up_attn_dot.warn,
   .attn.warn, storage >=70% bar), so this reads consistently with the rest
   of the attention-state language on the page. */
.up_extraction_warn {
    color: #cc793d;
    font-family: 'Shentox Medium';
}

/* Resource-icon and sync-pill tooltips now reuse the sitewide [data-tooltip]
   component from ../lib/custom_elements.css (the same one the header nav
   buttons use) instead of the bespoke JS-positioned [data-tooltip2] one
   that used to be here -- that custom one kept drifting off-target because
   it was reimplementing pixel math the header's tooltip never needed in
   the first place. [data-tooltip] sets position:relative on its trigger,
   which would otherwise stomp .pill's own position:absolute (both rules
   have equal specificity, and custom_elements.css loads after this file) --
   this restores it so the sync/count pills stay pinned to their corner of
   the thumbnail instead of falling into normal flow. */
.pill[data-tooltip] {
    position: absolute;
}

/* The header's own [data-tooltip]::before offsets (bottom:-50%; left:200%;
   margin-left:-100%, in custom_elements.css) were hand-tuned for its 48px
   square nav icons sitting in open space -- reused as-is here they land
   the bubble overlapping whatever's immediately right of the icon in a
   tightly-packed .brow row (the next resource icon, the load bar). This
   overrides the geometry to sit directly above the trigger instead --
   same component, same look, just positioned sanely for a smaller/denser
   trigger -- and caps the width, since the header's own tooltip labels
   are all short ("System Checker") so width:max-content never needed a
   cap; the sync pill's text is a full sentence and without one it just
   kept growing and ran off the edge of the viewport instead of wrapping. */
.sc-item[data-tooltip]::before,
.pill[data-tooltip]::before {
    bottom: 100%;
    margin-bottom: 8px;
    max-width: 260px;
    white-space: normal;
    /* custom_elements.css sets this pseudo-element's own font-size to 1em,
       which resolves against the TRIGGER's computed size -- fine for the
       header's normally-sized nav buttons, but .sc-item is already down at
       0.8125em of the page's 16px base, so the tooltip text was
       inheriting that same shrunk size (~13px). rem anchors it to the
       page's root size directly, sidestepping the trigger's own nested
       em cascade. */
    font-size: 1rem;
}

/* Centered above the trigger -- .sc-item rows always have room on both
   sides. */
.sc-item[data-tooltip]::before {
    left: 50%;
    margin-left: 0;
    transform: translateX(-50%);
}

/* Left-aligned instead of centered -- the sync pill sits at the top-left
   corner of the leftmost tile in the row, close enough to the page's own
   left edge that centering a 260px-wide bubble on it pushed the left half
   off-screen. Anchoring its left edge to the pill's own left edge and
   letting it grow rightward (into the tile's open space) keeps it fully
   on-screen instead. */
.pill[data-tooltip]::before {
    left: 0;
    margin-left: 0;
    transform: none;
}

/* ================= PANEL LAYOUT (alternate colony list layout) =================
   Sibling of .colony-row -- see template_colony_tile.php's .panel-row,
   rendered right after every .colony-row for the same colony. Hidden by
   default; .up_planets.layout-panel (toggled by script.js's
   setColonyLayoutMode(), driven by the #up_layout_toggle buttons in
   template_planets.php) is what shows it instead of the row list. Reuses
   .thumb-wrap/.pico/.pill/.pill-sync/.attn from the row layout above
   completely unchanged -- only what's genuinely new to this layout gets
   its own class names here, so none of this can ever bleed into the row
   list, and none of the row list's own rules had to move or change to make
   room for it.
   NOTE: this must be a CLASS selector (.up_planets), not an ID -- the
   container only ever carries class="up_planets" in
   template_planets.php, no id. The first shipped version of this file
   mistakenly used #up_planets here, which silently never matched anything
   -- the JS toggle was flipping the class correctly the whole time, this
   selector just never saw it. */
.panel-row { display: none; }
.up_planets.layout-panel .colony-row { display: none; }
.up_planets.layout-panel .panel-row { display: flex; }
.up_planets.layout-panel {
    display: grid;
    grid-template-columns: max-content;
    gap: 1.375em;
}

.panel-row {
    flex-direction: row;
    align-items: center;
    gap: 20px;
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 12px;
    padding: 20px;
}

/* Owner portrait, centered on the planet image at 50% of its size (the
   existing 128px .thumb-wrap -> 64px), same real
   images.evetech.net/characters/{id}/portrait source the row layout's own
   .tile-owner .avatar already uses. Thin tileBg ring so it reads as a
   badge sitting on top of the thumbnail rather than part of the planet
   texture. */
.owner-portrait {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 50%;
    height: 50%;
    border-radius: 50%;
    overflow: hidden;
    border: 8px solid var(--tileBg);
    box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.owner-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* PI-type badge cluster, bottom-left of the thumbnail: the extractor icon
   whenever this colony has one or more extractors, plus the single highest
   factory tier present (a P2+P3 colony only shows the P3 pill) -- both
   together when both apply. */
.pi-badges {
    position: absolute;
    bottom: -0.5em;
    left: -0.5em;
    display: flex;
    align-items: flex-end;
    gap: 0.2em;
}
.pi-badge-extractor { width: 1.9em; height: 1.9em; filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .55)); }
.pi-badge-tier {
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 4px;
    font-size: 0.8em;
    font-weight: 700;
    line-height: 1;
    padding: 0.4em 0.45em;
    color: var(--textColor3);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .4);
}

.panel-row-details { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.75em; }
.panel-title { font-size: 1.25em; font-weight: 600; color: var(--textColor3); white-space: nowrap; }
.panel-title .sep { color: var(--textColor); font-weight: 400; }

/* Building summary: extractors merge into ONE row regardless of what
   they're extracting, factories merge one row per tier regardless of
   output, storage-family rows merge multiple instances of the same
   building into one row with a count pill. Fixed priority order (same as
   the row layout): extractors, then Storage Facility / Launchpad / CC,
   then P1-P4 factories -- see template_colony_tile.php's $_panelGroups. */
.b-summary { display: flex; flex-direction: column; gap: 0.25em; }
.b-row { display: flex; align-items: center; gap: 0.6em; padding: 0.25em 0; }
.b-icon { width: 1.6em; height: 1.6em; flex-shrink: 0; }
.b-tier {
    flex-shrink: 0;
    font-size: 0.75em;
    line-height: 1;
    color: var(--textColor3);
    background-color: var(--color16);
    border: 1px solid #313131;
    border-radius: 4px;
    padding: 0.28em 0.38em;
}
.b-spacer { flex: 1; }
.b-count {
    flex-shrink: 0;
    font-size: 0.75em;
    font-weight: 700;
    line-height: 1;
    color: var(--textColor3);
    background-color: var(--color16);
    border: 1px solid var(--borderColor62);
    border-radius: 100px;
    padding: 0.3em 0.55em;
}

/* One small icon per distinct resource in the group -- the exact name(s),
   "|"-joined when there's more than one, live in the sitewide [data-tooltip]
   component (../lib/custom_elements.css) on hover, same mechanism as every
   other tooltip on the page, so no bespoke tooltip styling is needed here
   at all. */
.b-res-cluster { display: flex; align-items: center; gap: 0.3em; }
.res-icon-sm { width: 1.5em; height: 1.5em; border-radius: 3px; flex-shrink: 0; display: block; }

/* ================= RIGHT-HAND DETAIL PANEL =================
   Sibling of .up_planets inside the new .up_panel_frame wrapper (see
   template_planets.php) -- only ever visible in the panel layout, never
   the row list, and only once a .panel-row has actually been clicked
   (script.js). Class names and structure here match the two sketches
   this was designed against (detail_panel_preview.html, then
   combined_preview.html once the left list + right panel were shown
   together) verbatim, so this is a straight port of an already-approved
   look rather than a new design pass. */

/* .up_planets used to be the direct flex child of #content-UP -- this
   wrapper now takes over that role (flex:1/min-height:0/width:100%) so
   row-mode is completely unaffected (the wrapper is just a transparent
   pass-through then), and only becomes a real two-column layout once
   .layout-panel is toggled onto it (script.js's setColonyLayoutMode(),
   the same switch that already toggles .up_planets.layout-panel). */
.up_panel_frame {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    width: 100%;
}
.up_panel_frame.layout-panel {
    flex-direction: row;
    align-items: stretch;
    gap: 20px;
}
/* In panel mode, .up_planets.layout-panel is already a grid-template-
   columns:max-content grid (see the PANEL LAYOUT rules above) -- meaning
   it only NEEDS its content's own width, not the 100% it claims in row
   mode. Freeing that up is what actually hands the rest of the row's
   width to #up_detail_panel_wrap below; without this override
   .up_planets would still occupy the full row (its content just left-
   aligned within that claimed space) and the detail panel would have
   nothing left to sit in. */
.up_panel_frame.layout-panel .up_planets { width: auto; max-width: 100%; flex: 0 1 auto; }

.up_detail_panel_wrap {
    display: none;
    flex-direction: column;
    flex: 1;
    min-width: 420px;
    min-height: 0;
    overflow-y: auto;
    box-sizing: border-box; /* matches .up_planets's own box-sizing below --
                                without it this padding would add on top of
                                the flex-basis width instead of eating into
                                it. */
    padding: 16px; /* matches .up_planets's own padding so the first item in
                       each column (a .panel-row / this wrap's own first
                       child) lines up along the same top edge -- .up_planets
                       already had this padding, this wrap never did. */
}
.up_panel_frame.layout-panel .up_detail_panel_wrap { display: flex; }
.up_detail_panel_wrap::-webkit-scrollbar { display: block; width: 14px; height: 14px; }
.up_detail_panel_wrap::-webkit-scrollbar-track { background: var(--scrollbarBG); }
.up_detail_panel_wrap::-webkit-scrollbar-thumb { background-color: var(--thumbBG); border-radius: 0; border: 1px solid var(--scrollbarBG); }
.up_detail_panel_wrap { scrollbar-color: var(--thumbBG) var(--scrollbarBG); }

.detail-panel-empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--textColor);
    font-size: 0.9375em;
    border: 1px dashed var(--borderColor31);
    border-radius: 12px;
    min-height: 200px;
}
/* Hidden the same way script.js hides it once a colony's been picked --
   see the [hidden] override further below, same pattern as .tile-map's. */
.detail-panel-empty[hidden] { display: none; }

/* Selected-row highlight (script.js's click-to-select) -- reuses
   var(--color42), the same "active" tint .up_layout_toggle_btn.active
   already uses elsewhere, so a clicked row visibly ties back to what's
   now loaded in the panel beside it. */
.panel-row {
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: background-color 0.2s ease;
}
/* Same hover treatment .tile-planet already gets in the row layout
   (#272727 on hover) -- there it's scoped to just the thumbnail because
   that's the only clickable part of a .colony-row, but the entirety of a
   .panel-row is the click target here, so the whole card gets it.
   :not(.selected) so a hover over the already-selected row doesn't
   compete with (and, being a tie in specificity, unpredictably override)
   its own highlight below. */
.panel-row:not(.selected):hover { background-color: #272727; }
.panel-row.selected { background-color: var(--color42); border-color: var(--borderColor62); }

.detail-panel {
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 12px;
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 22px;
    flex: 1;
}
.detail-panel[hidden] { display: none; }

/* Title bar + layout map pinned while the sections scroll -- the wrapper
   (template_colony_tile.php) sticks to the top of the scrolling
   .up_detail_panel_wrap. It bleeds over .detail-panel's own 24px padding
   (negative margins + matching padding) and repaints the panel's tileBg
   behind itself, so cards sliding under it can never peek around its
   edges or through the panel's padding gutters; the top corners keep the
   panel's 12px radius so the bleed never pokes outside the rounded
   border. gap matches .detail-panel's own 22px so the header/map spacing
   is identical to before the wrapper existed; no bottom padding, since
   the panel's own flex gap still separates the wrapper from the first
   section (scrolled cards simply clip at the wrapper's bottom edge).
   z-index 60, not something small: the sitewide [data-tooltip] component
   (../lib/custom_elements.css) puts position:relative + z-index:50 on
   every element carrying that attribute, and the section cards are full
   of them (timers, pills, per-card refresh buttons) -- anything at or
   below 50 here would let those paint on top of the pinned block as they
   scroll under it. Still far below #up_char_modal_backdrop (200). */
.detail-pinned {
    display: flex;
    flex-direction: column;
    gap: 22px;
    position: sticky;
    top: 0;
    z-index: 60;
    background-color: var(--tileBg);
    margin: -24px -24px 0;
    padding: 24px 24px 0;
    border-radius: 12px 12px 0 0;
}

.detail-header { display: flex; }
/* Its own bordered tile now, same look as the card-grid cards further
   down (.extractor-card etc.), rather than sitting bare against the
   panel's own background. Title+status live in .detail-title-main on
   the left; the owner name/portrait duo now lives INSIDE this same tile
   (moved out of its own sibling .detail-owner block) on the right, so
   the whole header reads as one card instead of two. */
.detail-title-block {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    flex: 1;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
    padding: 14px 16px;
}
.detail-title-main { display: flex; flex-direction: column; gap: 0.4em; }
.detail-title { font-size: 1.75em; font-weight: 600; color: var(--textColor3); display: flex; align-items: center; gap: 0.45em; }
.detail-title .sep { color: var(--textColor); font-weight: 400; }
.detail-status { font-size: 0.9375em; font-weight: 700; letter-spacing: 0.02em; }
.detail-status.good { color: #6fcc3d; }
.detail-status.warn { color: #cc793d; }
.detail-status.bad { color: #f30202; }

.detail-owner { display: flex; align-items: center; gap: 0.7em; }
.detail-owner .owner-name { color: var(--textColor3); font-size: 1.0625em; text-align: right; }
.owner-portrait-lg { width: 44px; height: 44px; border-radius: 50%; overflow: hidden; flex-shrink: 0; border: 2px solid var(--borderColor31); }
.owner-portrait-lg img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Reuse of the real in-game layout map (see template_colony_tile.php) --
   pins here are clickable (wireDetailPanelMapLinks() in script.js links
   them to the building cards below), with their own hover/selection
   affordance: the .map-pin-ring2 outer circle, faint on hover and full
   opacity + glow when selected. The row list's hover/selection styling
   (inner-ring thickening) is deliberately suppressed in this map so a
   building's status ring color/weight never changes -- the second circle
   is the one and only highlight indicator here, per the approved
   interaction sketch. */
.detail-ingame-map {
    width: 100%;
    /* Without this, width:100% + the padding below adds 16px (plus 2px of
       border) on top of that 100%, so this box rendered wider than the
       title tile above it instead of lining up with it. */
    box-sizing: border-box;
    height: 320px;
    max-height: 320px;
    flex-shrink: 0;
    /* The pins/links group (svg.colonymap, template_colony_tile.php)
       uses plain "meet" -- same as .tile-map's own copy -- so the whole
       layout scales down uniformly to fit this box with nothing cropped
       or stretched out of proportion; a "slice"-style fill here blew the
       pins up (once this box grew past a thin decorative strip) or left
       them looking strung out along a thin cropped band, depending on
       which dimension ended up doing the cropping.
       The background TEXTURE is a separate concern from that group,
       handled here as a CSS background-image (set inline per-colony,
       template_colony_tile.php) with cover/center below -- unlike the
       pins, there's nothing structurally meaningful about the texture's
       own proportions, so it's fine for it to fill this box's full width
       edge-to-edge and get cropped top/bottom, independent of however
       (little or much) of the box the "meet"-fit pins/links group ends up
       needing. overflow:hidden clips that cover-fill image to this box's
       own rounded corners. */
    background-size: cover;
    background-position: center;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    border-radius: 8px;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    padding: 8px;
}
.detail-ingame-map .map-pin { cursor: url(../img/cursors/cursor_pointer.svg), pointer; }
/* This map's pins' hover/selection visuals (the .map-pin-ring2 outer
   circle) are the SITEWIDE .map-pin rules now -- see the .map-pin-ring2
   block in the row-list map section further up. Both maps share one
   treatment since the row list adopted the same style. */

/* Building cards are the other half of the same linking: clickable, and
   .linked-hl is the highlight a selected map pin (or a click on the card
   itself) puts on them -- border + soft glow in the same --hoverColor as
   the pin's ring2, so the pairing reads as one selection. */
.extractor-card[data-pin-id],
.storage-card[data-pin-id],
.facility-card[data-pin-id] {
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.detail-panel .linked-hl {
    border-color: var(--hoverColor);
    box-shadow: 0 0 0 1px var(--hoverColor), 0 0 14px rgba(210, 210, 210, 0.3);
}

.detail-section { display: flex; flex-direction: column; gap: 10px; }
.detail-section-label { font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.06em; color: #666; font-weight: 700; }
.card-grid { display: flex; flex-direction: column; gap: 10px; }

.extractor-card { background-color: var(--color16); border: 1px solid var(--borderColor31); border-radius: 8px; padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
.ext-head { display: flex; align-items: center; gap: 0.6em; }
.ext-head .res-name { color: var(--textColor3); font-size: 1.0625em; font-weight: 600; }
.ext-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
.ext-stat { display: flex; flex-direction: column; gap: 0.3em; }
.ext-stat .k { font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.04em; color: #666; }
.ext-stat .v { font-size: 0.9375em; color: var(--textColor3); font-variant-numeric: tabular-nums; }
.ext-stat .v .sub { display: block; font-size: 0.8125em; color: var(--textColor); font-weight: 400; margin-top: 0.15em; }
/* Flags a colony demand/hr that outruns this resource's own extraction/hr
   -- same red-circle-with-"!" language as .attn.bad elsewhere on the page,
   just inline (not position:absolute) since this sits beside the number
   rather than pinned to a tile corner. */
.ext-demand-warn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.3em;
    height: 1.3em;
    margin-left: 0.35em;
    border-radius: 50%;
    background-color: #f30202;
    color: #fff;
    font-size: 0.8125em;
    font-weight: 800;
    font-family: Georgia, serif;
    flex-shrink: 0;
    vertical-align: middle;
}

/* Storage overview + per-building-type cards reuse the .loadbar
   look established for .brow (see the BROW LOADBAR rules further up),
   but that component is scoped there as .brow .loadbar -- this detail
   panel needs the same bar OUTSIDE a .brow, so it gets its own copy here
   instead of a selector change to the existing one (which would risk the
   row list's own bars -- exactly what's not allowed to change).

   Scoped under .detail-panel (rather than left bare) so this copy can
   never collide with .brow .loadbar-wrap's own rules further up: a bare
   .loadbar-wrap { flex: 1; ... } sets flex-basis: 0% via the shorthand,
   and since .brow .loadbar-wrap never sets flex-basis or the flex
   shorthand itself (only flex-shrink and a fixed width), that flex-basis
   would win regardless of specificity -- width is ignored once
   flex-basis isn't auto, so the row list's bars silently stopped
   respecting their intended 12.5em fixed width and instead grew/shrank
   with whatever flex space happened to be left on each row (the
   "raised and inconsistent width" regression). Scoping both this block
   under .detail-panel and the row list's under .brow means the two
   never again match the same element, so this fix is purely a detail-
   panel change -- the row list's own rule above is untouched. */
.storage-overview { display: flex; align-items: center; gap: 0.75em; }
.storage-overview .ov-label { font-size: 0.9375em; color: var(--textColor3); flex-shrink: 0; }
.detail-panel .loadbar-wrap { flex: 1; display: flex; align-items: center; gap: 0.5625em; }
.detail-panel .loadbar { position: relative; flex: 1; height: 1.375em; border-radius: 4px; background-color: #101010; overflow: hidden; }
/* Same darker/less saturated fill colors as .brow .loadbar above (see the
   comment there) -- kept identical between the two so the row list and
   detail panel bars still read as the same component, just placed in
   different layouts. */
.detail-panel .loadbar > div { position: absolute; top: 0; left: 0; height: 100%; border-radius: 4px; background-color: #4a7c2f; }
.detail-panel .loadbar > div.warn { background-color: #8a5a2e; }
.detail-panel .loadbar > div.full { background-color: #8c1c1c; }
/* .loadbar-sim (the projected/simulated fill) vs .loadbar-real (the real,
   last-synced fill) -- same half-opacity-ghost-behind-the-real-bar
   treatment as .brow .loadbar above (see the comment there), missing here
   until now. Without this, both bars painted as identical fully-opaque
   fills with no z-index of their own, so the storage/Launchpad cards in
   this panel were only ever showing the real bar with no visual sign the
   simulated projection (already being computed correctly -- see
   sim_used_m3/sim_pct in template_colony_tile.php) existed at all. */
.detail-panel .loadbar > div.loadbar-sim { opacity: 0.5; z-index: 1; }
.detail-panel .loadbar > div.loadbar-real { z-index: 2; }
.detail-panel .loadbar-label {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.75em;
    font-variant-numeric: tabular-nums;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), 0 0 3px rgba(0, 0, 0, 0.6);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 0 0.25em;
    pointer-events: none;
}
.detail-panel .loadbar-wrap .pct { min-width: 2.4em; text-align: right; color: var(--textColor3); font-size: 0.875em; font-variant-numeric: tabular-nums; }

.storage-card { background-color: var(--color16); border: 1px solid var(--borderColor31); border-radius: 8px; padding: 14px 16px; display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
.storage-card .st-head { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 220px; }
.storage-card .st-name { display: flex; align-items: center; gap: 0.5em; color: var(--textColor3); font-size: 0.9375em; font-weight: 600; }
/* Row + wrap, same treatment as .facility-grid below -- cards here vary
   in width even more (a pie-chart card vs. a plain one), so equalizing to
   the widest (script.js's equalizeCardWidths(), called for both grids)
   matters just as much here. */
.storage-grid { flex-direction: row; flex-wrap: wrap; }
.storage-grid .storage-card { flex-shrink: 0; }

.pie-wrap { display: flex; align-items: center; gap: 14px; flex-shrink: 0; }
.pie { width: 84px; height: 84px; border-radius: 50%; flex-shrink: 0; }
.pie-legend { display: flex; flex-direction: column; gap: 0.35em; }
.pie-legend-item { display: flex; align-items: center; gap: 0.5em; font-size: 0.8125em; }
/* Small solid dot, same color as this resource's own pie slice
   (conic-gradient stop -- see $_pieColorFor() in
   template_colony_tile.php) -- ties each legend line back to its slice
   without needing to trace color-matching by eye. */
.legend-dot { width: 0.625em; height: 0.625em; border-radius: 50%; flex-shrink: 0; }
.pie-legend-item .name { color: var(--textColor3); }
.pie-legend-item .amt { color: var(--textColor); margin-left: auto; padding-left: 1em; font-variant-numeric: tabular-nums; }

.facility-card { background-color: var(--color16); border: 1px solid var(--borderColor31); border-radius: 8px; padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
/* Facilities lay out as a wrapping row instead of the other sections'
   single column -- cards are short, and there are usually several, so
   they read better side by side. flex-shrink:0 keeps each card at
   whatever width script.js's equalizeFacilityCardWidths() sets (the
   widest card's own natural width) instead of the row squeezing them
   back down to fit. */
.facility-grid { flex-direction: row; flex-wrap: wrap; }
.facility-grid .facility-card { flex-shrink: 0; }
.fac-name { color: var(--textColor3); font-size: 0.9375em; font-weight: 600; }
.fac-schematic { display: flex; align-items: center; gap: 0.5em; }
.fac-schematic .sname { color: var(--textColor3); font-size: 0.9375em; }
.fac-row { display: flex; align-items: center; gap: 0.6em; font-size: 0.875em; }
.fac-row .k { color: #666; text-transform: uppercase; font-size: 0.75em; letter-spacing: 0.04em; min-width: 6.5em; flex-shrink: 0; }
.fac-row .v { color: var(--textColor3); font-variant-numeric: tabular-nums; }
.fac-inputs { display: flex; gap: 0.5em; flex-wrap: wrap; }
.fac-input-chip { display: flex; align-items: center; gap: 0.4em; background-color: var(--color21); border: 1px solid var(--borderColor31); border-radius: 100px; padding: 0.25em 0.65em 0.25em 0.45em; font-size: 0.8125em; }
.fac-input-chip span { color: var(--textColor3); }

/* Below this width the left list can't afford both its own content-
   driven size AND a usable right panel -- same tradeoff as
   combined_preview.html's original sketch, ported over: drop the list
   down to icon-only rows (just the planet thumbnail + its pills) and
   hand the reclaimed width straight to the detail panel. Scoped to
   .up_panel_frame.layout-panel specifically so this can never affect
   .colony-row/.tile-details, which already has its own, separate
   @media (max-width: 720px) rules above that must stay exactly as they
   are. */
@media (max-width: 1150px) {
    .up_panel_frame.layout-panel .panel-row-details { display: none; }
    .up_panel_frame.layout-panel .panel-row { padding: 14px; }
    .up_detail_panel_wrap { min-width: 280px; }
}
/* ==========================================================================
   COLONY PLANNER
   ========================================================================== */

/* Mode flip -- .planner-active on #content-UP (script.js
   toggleColonyPlanner) swaps the colonies view for the planner while the
   characters bar stays put. Everything is hidden by explicit selector,
   NEVER a blanket *:not(...) -- new colonies-view top-level blocks added
   later will simply need a line here, which beats accidentally hiding a
   future shared element. */
#content-UP.planner-active > .up_planets_span,
#content-UP.planner-active > .up_alertsettings,
#content-UP.planner-active > .up_filters,
#content-UP.planner-active > .up_summary,
#content-UP.planner-active > .up_panel_frame { display: none; }

/* The planner container only exists once opened (script.js injects it
   after the characters bar) and only shows in planner mode.
   width:100% + box-sizing matter: #content-UP is a CENTERING flex column
   (align-items:center), so without an explicit width this container
   shrinks to fit its content -- which both squeezed the whole planner
   into the middle of the window AND made every tile resize whenever its
   content changed (selecting a planet or template altered text, text
   altered widths). Full width pins the layout; flex/min-height/overflow
   give the planner its own scroll, since #content-UP itself clips
   (overflow:hidden). */
#up_planner { display: none; }
#content-UP.planner-active > #up_planner {
    display: block;
    width: 100%;
    box-sizing: border-box;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* In planner mode the refresh + manage-characters icons hide (the module
   changed, per the approved top-bar sketch); the planner button itself
   stays, lit via .planner-on, as the way back to the colonies. */
#content-UP.planner-active #up_char_refresh_btn,
#content-UP.planner-active #up_managechar_btn { display: none; }

/* With refresh + manage-characters gone in planner mode, the divider
   ahead of the planner button would lead nothing -- hide it too, so the
   group collapses to [planner] | [logout]. :first-of-type is safe here:
   the two .up_opt_divider spans are the only <span>s in the options
   row (everything else is a <button>). */
#content-UP.planner-active .up_character_options .up_opt_divider:first-of-type { display: none; }

/* The mini planets row left of the character name is colonies-view
   context -- it hides in planner mode too and comes back on leaving
   (same .planner-active class flip, nothing JS-side). Scoped to
   .up_character's DIRECT child so the copies inside the character
   switcher dropdown rows (.up_char_switcher_row) are untouched. */
#content-UP.planner-active .up_character > .up_char_planets { display: none; }
#up_planner_btn.planner-on { border-color: var(--borderColor62); }
#up_planner_btn.planner-on svg { fill: var(--hoverColor); }

/* "|" dividers inside the icon group -- [refresh][chars] | [planner] |
   [logout]. Same glyph treatment as .toolLogos > span (2em Shentox
   UltraLight) so they read as one family with the big divider after the
   portrait, and flex-centered in a box exactly as tall as the 44px icon
   buttons so the bar's vertical centering holds by construction, not by
   baseline luck. */
.up_character_options .up_opt_divider {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 44px;
    font-size: 2em;
    font-family: "Shentox UltraLight";
    line-height: 1;
    color: var(--textColor);
    user-select: none;
}

/* ---- Step 1: template chooser -------------------------------------- */

/* Same 16px side inset as the characters bar above (.up_characters
   margin: 16px 16px 8px) so the planner's tiles line up with the bar's
   own edges. */
.plnr_wrap { padding: 12px 16px; box-sizing: border-box; }

/* Two panels: templates list left (fixed comfortable width), planet
   finder / fit card right (takes the rest). Same card language as the
   harmonized colony tiles: tileBg + borderColor31 + 12px radius. */
.plnr_step1 {
    display: flex;
    gap: 16px;
    align-items: stretch;
}

.plnr_panel {
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-radius: 12px;
    padding: 14px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.plnr_panel_templates { flex: 0 0 28em; }
.plnr_panel_planet { flex: 1 1 auto; }

.plnr_panel_head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.plnr_panel_title {
    font-family: 'Shentox Medium';
    font-size: 1.33em;
    color: var(--textColor3);
}

/* Boxed buttons, same look as the character-toolbar icons / filter
   reset: color16 ground, 31 border, border brightens on hover. */
.plnr_btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 8px 12px;
    color: var(--textColor);
    font-family: 'Shentox Medium';
    font-size: 1.04em;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.plnr_btn svg { height: 1.19em; width: 1.19em; fill: currentColor; }
.plnr_btn:hover:not(:disabled) { border-color: var(--borderColor62); color: var(--textColor3); }
.plnr_btn:disabled {
    opacity: 0.45;
    cursor: url(../img/cursors/cursor.svg), default;
}

.plnr_tpl_list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    overflow-y: auto;
}

.plnr_tpl_row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
    background-color: var(--backgroundColor21);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.plnr_tpl_row:hover { border-color: var(--borderColor62); }
/* Selection uses the same #D2D2D2 accent as the map pins' ring2 --
   one accent language across the whole tool. */
.plnr_tpl_row.selected {
    border-color: var(--hoverColor);
    background-color: var(--backgroundColor);
}
.plnr_tpl_row:focus-visible {
    outline: 1px solid var(--hoverColor);
    outline-offset: 1px;
}

.plnr_tpl_name {
    flex: 1 1 auto;
    color: var(--textColor3);
    font-size: 1.11em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.plnr_tpl_badge {
    font-size: 0.81em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--textColor);
    border: 1px solid var(--borderColor31);
    border-radius: 999px;
    padding: 2px 8px;
    background-color: var(--color16);
}

.plnr_tpl_empty {
    color: var(--textColor);
    font-size: 1.04em;
    padding: 12px;
    text-align: center;
}

/* Right panel placeholder (Slice 1 replaces this with the finder + fit
   card) -- centered, quiet, clearly intentional. */
.plnr_placeholder {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    color: var(--textColor);
    font-size: 1.04em;
    min-height: 220px;
}

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

@media (max-width: 900px) {
    .plnr_step1 { flex-direction: column; }
    .plnr_panel_templates { flex: 1 1 auto; }
}

/* ---- Step 1 additions: previews, planet finder, fit card, step 2 ---
   Text sizes are em, calibrated so .plnr_fit_row (the fit-card labels)
   sits at exactly 1em and everything else keeps its original proportion
   to it (old px / 13.5). One inherited font-size now scales the whole
   module together. */

/* The .plnr_step wrappers get display from their own rules, which beats
   the UA sheet's [hidden] -- this override keeps the hidden attribute
   meaning "gone" for the step1 <-> step2 swap (same convention as
   .detail-panel[hidden] etc.). */
.plnr_step[hidden] { display: none !important; }
.plnr_step2 { display: flex; flex-direction: column; gap: 12px; }

/* Server-drawn layout preview -- pins + links from the template's own
   coordinates. Neutral by default; picks up the selection accent with
   the row so the chosen layout reads chosen. */
.plnr_tpl_preview {
    flex: 0 0 3.26em;
    height: 3.26em;
    display: flex;
    align-items: center;
    justify-content: center;
}
.plnr_tpl_preview svg { width: 100%; height: 100%; }
.plnr_tpl_preview svg line { stroke: var(--borderColor62); stroke-width: 1; }
.plnr_tpl_preview svg circle { fill: var(--textColor); }
.plnr_tpl_row.selected .plnr_tpl_preview svg circle { fill: var(--hoverColor); }

.plnr_import_msg {
    font-size: 0.96em;
    color: var(--textColor);
    padding: 4px 2px;
}
.plnr_import_msg.error { color: #cc793d; }

/* ---- Planet finder ---- */
.plnr_finder { display: flex; flex-direction: column; gap: 10px; }

.plnr_sys_search { position: relative; }
#plnr_sys_input {
    width: 100%;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 10px 12px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 1.04em;
    outline: none;
    transition: border-color 0.15s ease;
}
#plnr_sys_input:focus { border-color: var(--borderColor62); }
#plnr_sys_input::placeholder { color: var(--textColor2); }

.plnr_sys_results {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 30; /* same layer the colony filter dropdown panels use */
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    overflow: hidden;
}
.plnr_sys_option {
    padding: 9px 12px;
    font-size: 1.04em;
    color: var(--textColor);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.plnr_sys_option:hover { background-color: #272727; color: var(--textColor3); }

.plnr_sys_title {
    font-size: 1.04em;
    color: var(--textColor3);
    padding: 2px 2px 0;
}

.plnr_planet_list { display: flex; flex-direction: column; gap: 6px; }

.plnr_planet_row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
    background-color: var(--backgroundColor21);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease, opacity 0.15s ease;
}
.plnr_planet_row img { height: 1.93em; width: 1.93em; }
.plnr_planet_row:not(.locked):hover { border-color: var(--borderColor62); }
.plnr_planet_row.selected {
    border-color: var(--hoverColor);
    background-color: var(--backgroundColor);
}
/* The allowed-planet lock: disallowed types stay VISIBLE (the player
   should see the planet exists) but read and behave as unselectable. */
.plnr_planet_row.locked {
    opacity: 0.35;
    cursor: url(../img/cursors/cursor.svg), default;
}
.plnr_planet_name { flex: 1 1 auto; color: var(--textColor3); font-size: 1.04em; }
.plnr_planet_kind { color: var(--textColor); font-size: 0.93em; }

/* ---- Fit card ---- */
.plnr_fit {
    display: flex;
    flex-direction: column;
    gap: 6px;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
    padding: 12px 14px;
}
.plnr_fit_row { display: flex; justify-content: space-between; gap: 12px; font-size: 1em; }
.plnr_fit_label { color: var(--textColor); }
.plnr_fit_value { color: var(--textColor3); font-variant-numeric: tabular-nums; }
.plnr_fit_pending .plnr_fit_value { color: var(--textColor2); font-style: italic; }

/* ---- Step 2 stub ---- */
.plnr_step2_bar { display: flex; align-items: center; gap: 16px; }
.plnr_step2_summary { color: var(--textColor3); font-size: 1.11em; }

/* Fit-card over-budget: a sum past the command center's capacity at the
   template's own skill level -- same red family as the attention marks. */
.plnr_fit_value.over { color: #dc3201; }

/* ---- Step 2: top tile, accordion, extractor lane ------------------- */
/* Same em basis as the rest of the module (1em = the fit-card label). */

/* Planner-wide [hidden] override: several planner elements get display
   from their own class rules, which outrank the UA sheet's [hidden] --
   this keeps the attribute meaning "gone" everywhere in the module. */
.plnr_wrap [hidden] { display: none !important; }

.plnr_s2_top {
    flex-direction: row;
    align-items: stretch;
    gap: 20px;
}
.plnr_s2_left { flex: 1.2 1 0; display: flex; flex-direction: column; gap: 10px; }
.plnr_s2_name { font-size: 1.26em; font-weight: 700; color: var(--textColor3); }
.plnr_s2_on { color: var(--textColor); font-size: 0.78em; font-weight: 400; }
.plnr_s2_chips { display: flex; gap: 6px; flex-wrap: wrap; }
.plnr_specchip {
    background-color: var(--backgroundColor21);
    border: 1px solid var(--borderColor31);
    border-radius: 5px;
    padding: 2px 8px;
    font-size: 0.93em;
    color: var(--textColor);
}
.plnr_specchip b { color: var(--textColor3); font-weight: 600; }
.plnr_specchip.over, .plnr_specchip.over b { color: #dc3201; }
.plnr_s2_actions { margin-top: auto; }

.plnr_s2_map {
    flex: 1 1 0;
    min-width: 20em;
    /* Centered at its own (halved) height instead of stretching to the
       tile's -- the left column sets the tile height, the map hugs its
       svg. */
    align-self: center;
    background-color: #101010;
    border: 1px solid var(--borderColor31);
    border-radius: 8px;
}
.plnr_s2_map svg { width: 100%; height: 20em; display: block; }
/* Selectable pins -- the same double-circle language as the colony
   maps: faint outer ring on hover, full ring + glow when selected. */
.plnr_map_pin { cursor: url(../img/cursors/cursor_pointer.svg), pointer; }
/* No browser focus box on the pins -- the white rounded square the
   default outline draws around a clicked <g tabindex> read as a second,
   foreign selector on top of the ring language. Selection feedback is
   the ring2 alone; keyboard focus still shows via the faint ring
   (:focus-visible rule below). */
.plnr_map_pin:focus,
.plnr_map_pin:focus-visible { outline: none; }
.plnr_ring2 {
    fill: none;
    stroke: var(--hoverColor);
    stroke-width: 1.8;
    opacity: 0;
    transition: opacity 0.15s ease;
    pointer-events: none;
}
.plnr_map_pin:hover .plnr_ring2,
.plnr_map_pin:focus-visible .plnr_ring2 { opacity: 0.4; }
.plnr_map_pin.pin-selected .plnr_ring2 {
    opacity: 1;
    filter: drop-shadow(0 0 4px rgba(210, 210, 210, 0.7));
}

/* Step 2 tab bar (replaced the sideways accordion). Panels keep the
   .plnr_vtile class + plnr_vt_* ids so all lane-switching code is
   untouched; only the presentation changed -- one panel shown at a
   time inside a shared body box under the bar. */
.plnr_tabbar {
    display: flex;
    gap: 4px;
    align-items: flex-end;
    margin-top: 10px;
    border-bottom: 1px solid var(--borderColor31);
}
.plnr_tab {
    padding: 9px 16px;
    margin-bottom: -1px; /* sits on the bar's border so .on can cover it */
    border: 1px solid transparent;
    border-radius: 8px 8px 0 0;
    color: var(--textColor);
    font-family: 'Shentox Medium';
    user-select: none;
    white-space: nowrap;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: color 0.15s ease;
}
.plnr_tab:hover { color: var(--textColor3); }
.plnr_tab.on {
    background-color: var(--tileBg);
    border-color: var(--borderColor31);
    border-bottom-color: var(--tileBg); /* opens into the body box */
    color: var(--textColor3);
}
/* the Results tab's freshness dot: green right after a run, greyed by
   plannerMarkSimStale when anything changes */
.plnr_tab_dot { font-style: normal; color: #6fcc3d; margin-left: 7px; font-size: 0.8em; }
.plnr_tab_dot.stale { color: var(--textColor); opacity: 0.6; }
.plnr_tab_space { flex: 1; }
.plnr_tabbar .plnr_btn { margin: 0 0 6px; }
.plnr_tabbar #plnr_sim_status { margin: 0 10px 10px; }
.plnr_tabbody {
    background-color: var(--tileBg);
    border: 1px solid var(--borderColor31);
    border-top: none;
    border-radius: 0 0 12px 12px;
}
.plnr_vtile { display: none; }
.plnr_vtile.open { display: block; }
/* every panel is content-sized -- tabs need no reserved height (the
   per-cycle table still scrolls inside its own box) */
.plnr_vbody { padding: 12px 16px 14px; }

/* Extractor lane */
.plnr_ext_cols { display: flex; gap: 18px; align-items: flex-start; }
/* Fixed basis (not content-sized): the Output row's text appearing or
   changing must never re-share the row's width -- the chart keeps its
   size no matter what the left column shows. */
.plnr_ext_inputs { flex: 0 0 27em; max-width: 27em; }
/* min-width low enough that the chart shrinks before the lane ever
   needs a horizontal scrollbar (below ~1100px the lanes stack anyway). */
.plnr_ext_chart { flex: 1 1 auto; min-width: 14em; }
.plnr_frow { display: flex; align-items: center; gap: 10px; margin-top: 7px; flex-wrap: wrap; }
.plnr_fk {
    color: var(--textColor);
    font-size: 0.9em;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    min-width: 9.6em;
}
.plnr_fnote { color: var(--textColor); font-size: 0.93em; }
.plnr_derived { color: var(--textColor2); font-size: 0.9em; font-style: italic; }
.plnr_frow input[type="number"] {
    width: 8em;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 7px 10px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 1em;
    outline: none;
    transition: border-color 0.15s ease;
}
.plnr_frow input[type="number"]:focus { border-color: var(--borderColor62); }

.plnr_chart_k {
    color: var(--textColor);
    font-size: 0.8em;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 4px;
}
.plnr_prevchart {
    background-color: #101010;
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
}
.plnr_prevchart svg { width: 100%; height: 8.9em; display: block; }
.plnr_chart_axis {
    display: flex;
    justify-content: space-between;
    color: var(--textColor);
    font-size: 0.85em;
    margin-top: 3px;
}

@media (max-width: 1100px) {
    .plnr_s2_top { flex-direction: column; }
    .plnr_tabbar { flex-wrap: wrap; align-items: center; border-bottom: none; gap: 6px; }
    .plnr_tab { border: 1px solid var(--borderColor31); border-radius: 8px; margin-bottom: 0; }
    .plnr_tab.on { border-color: var(--borderColor62); border-bottom-color: var(--borderColor62); }
    .plnr_tabbody { border-top: 1px solid var(--borderColor31); border-radius: 12px; }
}

/* Extractor lane selects (Resource) -- same field language as the
   number inputs beside them. */
.plnr_frow select {
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 7px 10px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 1em;
    outline: none;
    transition: border-color 0.15s ease;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.plnr_frow select:focus { border-color: var(--borderColor62); }

/* Resource switch row -- one icon button per P0 the planet offers,
   radio-style (exactly one active). Same boxed-button language as the
   toolbar icons; active = the module's usual #D2D2D2 accent. */
.plnr_res_row { display: flex; flex-direction: row; gap: 6px; flex-wrap: wrap; }
.plnr_res_btn {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 2.9em;
    width: 2.9em;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 0;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.plnr_res_btn img { height: 2.2em; width: 2.2em; }
.plnr_res_btn:hover { border-color: var(--borderColor62); }
.plnr_res_btn.active {
    border-color: var(--hoverColor);
    background-color: var(--backgroundColor);
}

/* Output row under the resource switches -- icon + P1 name + the
   schematic's per-cycle ratio. */
/* min-height reserves the icon's height even while the placeholder
   text is showing, so picking a resource never makes the line jump. */
.plnr_res_out { display: inline-flex; align-items: center; gap: 8px; min-height: 2.2em; }
.plnr_res_out img { height: 2.2em; width: 2.2em; }
.plnr_res_out b { color: var(--textColor3); font-weight: 600; }


/* No spinner arrows on ANY planner number field -- typed values only
   (the arrows nudged tiny steps and mostly invited misclicks). */
.plnr_wrap input[type="number"]::-webkit-outer-spin-button,
.plnr_wrap input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.plnr_wrap input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

/* Heads note flips to the warning red when the chosen count exceeds
   what the remaining CPU/powergrid can afford. */
.plnr_fnote.over { color: #dc3201; }

/* Resource-button tooltips open DOWNWARD -- the default opens above,
   and the switch row sits at the top of the accordion body, whose
   overflow-y:auto clips anything above its edge (same fix as the
   .pTools nav tooltips at the top of the page). */
.plnr_res_btn[data-tooltip]::before {
    bottom: auto;
    top: 100%;
    margin-bottom: 0;
    margin-top: 5px;
}
.plnr_res_btn[data-tooltip]::after {
    bottom: auto;
    top: 150%;
}

/* Chart with its Y legend OUTSIDE the plot box -- a narrow gutter
   column carries the gridline values (script.js positions each span at
   its gridline's height); the X legend was already outside below. */
.plnr_chart_row { display: flex; gap: 6px; align-items: stretch; }
.plnr_chart_yaxis { position: relative; flex: 0 0 2.4em; }
.plnr_chart_yaxis span {
    position: absolute;
    right: 0;
    transform: translateY(-50%);
    color: var(--textColor);
    font-size: 0.78em;
    font-variant-numeric: tabular-nums;
}
.plnr_chart_row .plnr_prevchart { flex: 1 1 auto; }
/* The X legend aligns with the plot box, not the gutter. */
.plnr_chart_axis { padding-left: 2.9em; }


/* Hints under the chart -- the cycle/fit line and the install-window
   note live below the plot now, out of the inputs column. */
.plnr_chart_hints { display: flex; flex-direction: column; gap: 3px; margin-top: 6px; padding-left: 2.9em; }

/* Output line never wraps (icon + name stay beside the OUTPUT label)
   -- so resource selection can't change the lane's height by a pixel. */
.plnr_res_output_row { flex-wrap: nowrap; }
.plnr_res_out b { white-space: nowrap; }


/* The resource-ratio hint under the chart keeps its line height even
   while empty, so picking a resource never shifts the hint block. */
#plnr_res_ratio { display: block; min-height: 1.25em; }

/* ---- Factories lane: schematic chips + clusters ------------------- */
.plnr_fac_top select {
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 7px 10px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 1em;
    outline: none;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease;
}
.plnr_fac_top select:focus { border-color: var(--borderColor62); }

.plnr_fac_grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.plnr_fac_chip {
    background-color: var(--backgroundColor21);
    border: 1px solid var(--borderColor31);
    border-left: 3px solid var(--borderColor31);
    border-radius: 8px;
    padding: 8px 10px;
    width: 13em;
    box-sizing: border-box;
}
.plnr_fac_chip .t { display: flex; justify-content: space-between; font-size: 0.9em; margin-bottom: 6px; }
.plnr_fac_chip .t .id { font-weight: 600; color: var(--textColor3); }
.plnr_fac_chip .t .cluster { color: var(--textColor); }
.plnr_fac_chip select {
    width: 100%;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 6px 8px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 0.93em;
    outline: none;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease;
}
.plnr_fac_chip select:focus { border-color: var(--borderColor62); }
/* Cluster identity as a left edge tint -- four hues cycled, matching
   the sketch's blue/purple plus two spares for bigger colonies. */
.plnr_fac_chip.plnr_cluster_c0 { border-left-color: #4c9be0; }
.plnr_fac_chip.plnr_cluster_c1 { border-left-color: #9a6fd0; }
.plnr_fac_chip.plnr_cluster_c2 { border-left-color: #6fcc3d; }
.plnr_fac_chip.plnr_cluster_c3 { border-left-color: #cc793d; }

/* Map <-> chip link highlight: the chip matching the clicked pin (and
   vice versa) carries the same #D2D2D2 accent as every other selection
   on the site. The cluster tint stays on the left edge. */
.plnr_fac_chip { cursor: url(../img/cursors/cursor_pointer.svg), pointer; transition: border-color 0.15s ease; }
.plnr_fac_chip.hl {
    border-top-color: var(--hoverColor);
    border-right-color: var(--hoverColor);
    border-bottom-color: var(--hoverColor);
}
.plnr_fac_chip.hl:not([class*="plnr_cluster_"]) { border-left-color: var(--hoverColor); }

/* ---- Storage & Routes lane ---------------------------------------- */
.plnr_route_line { display: flex; align-items: center; gap: 10px; margin-top: 8px; flex-wrap: wrap; }
.plnr_route_tag {
    background-color: var(--backgroundColor21);
    border: 1px solid var(--borderColor31);
    border-radius: 5px;
    padding: 3px 9px;
    font-size: 0.95em;
    color: var(--textColor3);
}
.plnr_route_arrow { color: var(--textColor2); }
.plnr_route_line select {
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 7px 10px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 1em;
    outline: none;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease;
}
.plnr_route_line select:focus { border-color: var(--borderColor62); }
.plnr_ok { color: #6fcc3d; }
.plnr_bad { color: #dc3201; }
.plnr_route_sep { color: var(--textColor2); margin: 0 8px; }

/* ---- Building letters, schematic icons, routes tiles, planet bg ---- */

/* Map pin letter badge -- same identity tag the tiles carry. */
.plnr_pin_badge { fill: #161616; stroke: var(--borderColor62); stroke-width: 1; }
.plnr_pin_badge_t { fill: #d2d2d2; font-size: 7px; }
.plnr_s2_map { background-size: cover; background-position: center; }

/* Letter tag on tiles -- small bordered pill, matching the map badge. */
.plnr_tile_badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5em;
    height: 1.5em;
    padding: 0 0.2em;
    box-sizing: border-box;
    border: 1px solid var(--borderColor62);
    border-radius: 999px;
    background-color: var(--color16);
    color: var(--textColor3);
    font-size: 0.85em;
    font-weight: 600;
}

/* Chosen-schematic commodity icon (chips + apply row) -- native
   <option>s can't carry images, so the icon sits beside the picker and
   tracks its selection. */
.plnr_schem_ico { height: 1.7em; width: 1.7em; visibility: hidden; }
.plnr_fac_chip .t { align-items: center; gap: 6px; }

/* Routes lane building tiles -- same chip language as the factories
   grid; the cluster tint reuses the same left-edge classes. */
.plnr_route_grid { display: flex; flex-wrap: wrap; gap: 8px; }
.plnr_route_tile {
    background-color: var(--backgroundColor21);
    border: 1px solid var(--borderColor31);
    border-left: 3px solid var(--borderColor31);
    border-radius: 8px;
    padding: 8px 10px;
    width: 17em;
    box-sizing: border-box;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease;
}
.plnr_route_tile.plnr_cluster_c0 { border-left-color: #4c9be0; }
.plnr_route_tile.plnr_cluster_c1 { border-left-color: #9a6fd0; }
.plnr_route_tile.plnr_cluster_c2 { border-left-color: #6fcc3d; }
.plnr_route_tile.plnr_cluster_c3 { border-left-color: #cc793d; }
.plnr_route_tile.hl {
    border-top-color: var(--hoverColor);
    border-right-color: var(--hoverColor);
    border-bottom-color: var(--hoverColor);
}
.plnr_route_tile.hl:not([class*="plnr_cluster_"]) { border-left-color: var(--hoverColor); }
.plnr_route_tile_head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.plnr_route_tile_head img { height: 1.7em; width: 1.7em; }
.plnr_route_tile_name { color: var(--textColor3); font-size: 0.93em; }
.plnr_route_tile_cluster { color: var(--textColor); }
.plnr_route_tile_row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.plnr_route_tile_k {
    color: var(--textColor);
    font-size: 0.85em;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    min-width: 3.4em;
}
.plnr_route_tile_row select {
    flex: 1 1 auto;
    min-width: 0;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 6px 8px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 0.93em;
    outline: none;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease;
}
.plnr_route_tile_row select:focus { border-color: var(--borderColor62); }
.plnr_route_tile_roles { margin-top: 6px; }

/* One grid per building group in Storage & Routes (hubs / extractors /
   factories) -- stacked rows with a clear seam between groups. */
.plnr_route_grid + .plnr_route_grid { margin-top: 8px; }

/* ---- Drag & Wire on the Step 2 map --------------------------------- */
/* hold-to-arm progress ring around a pressed building */
.plnr_armring {
    fill: none;
    stroke: var(--hoverColor);
    stroke-width: 2;
    opacity: 0.9;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: plnr_armfill 0.5s linear forwards;
    pointer-events: none;
}
@keyframes plnr_armfill { to { stroke-dashoffset: 0; } }

/* set routes: ONE colour, direction carried by the arrows; each segment
   rides beside its link on the lane matching its travel direction */
.plnr_route_flow {
    stroke: var(--hoverColor);
    stroke-width: 1.6;
    fill: none;
    opacity: 0.95;
    stroke-dasharray: 5 5;
    animation: plnr_flow 1s linear infinite;
    pointer-events: none;
}
@keyframes plnr_flow { to { stroke-dashoffset: -10; } }
.plnr_route_arrowhead { fill: var(--hoverColor); pointer-events: none; }
.plnr_ghostwire {
    stroke: var(--hoverColor);
    stroke-width: 2;
    stroke-dasharray: 3 4;
    fill: none;
    pointer-events: none;
}
/* fat invisible grab twin for hold-to-delete */
.plnr_routehit { stroke: transparent; stroke-width: 10; fill: none; pointer-events: stroke; cursor: url(../img/cursors/cursor_pointer.svg), pointer; }
.plnr_routegrp.deleting .plnr_route_flow { stroke: #dc3201; animation-duration: 0.25s; }
.plnr_routegrp.deleting .plnr_route_arrowhead { fill: #dc3201; }

/* wire-drag candidates pulse; the hovered drop target thickens */
.plnr_map_pin.candidate circle:first-child { stroke-dasharray: 4 3; animation: plnr_candspin 1.4s linear infinite; }
@keyframes plnr_candspin { to { stroke-dashoffset: -14; } }
.plnr_map_pin.droptarget circle:first-child { stroke-width: 3.5; }

/* ---- Schematic dropdowns with commodity icons ---------------------- */
.plnr_dd { position: relative; width: 100%; margin-top: 6px; }
.plnr_fac_top .plnr_dd { width: 16em; margin-top: 0; }
.plnr_dd_btn {
    display: flex;
    align-items: center;
    gap: 7px;
    width: 100%;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 5px 8px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 0.93em;
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
    transition: border-color 0.15s ease;
}
.plnr_dd_btn:hover { border-color: var(--borderColor62); }
.plnr_dd_btn img, .plnr_dd_opt img { height: 1.6em; width: 1.6em; flex: 0 0 auto; }
.plnr_dd_noico { height: 1.6em; width: 1.6em; flex: 0 0 auto; }
.plnr_dd_name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.plnr_dd_caret { color: var(--textColor); flex: 0 0 auto; }
.plnr_dd_panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 40; /* over neighbouring chips; same family as the filter panels */
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    max-height: 13em;
    overflow-y: auto;
}
.plnr_dd_panel[hidden] { display: none !important; }
.plnr_dd_opt {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 6px 8px;
    font-size: 0.93em;
    color: var(--textColor);
    cursor: url(../img/cursors/cursor_pointer.svg), pointer;
}
.plnr_dd_opt:hover { background-color: #272727; color: var(--textColor3); }

/* factory chips now share the routes tiles' head; give the chip a bit
   more room for icon + name */
.plnr_fac_chip { width: 15em; }

.plnr_routes_top { margin-bottom: 4px; }

/* ---- Slice 3: simulation results row ------------------------------- */
.plnr_resrow { display: flex; gap: 12px; align-items: stretch; flex-wrap: wrap; margin-top: 10px; }
.plnr_resrow.stale { opacity: 0.45; }
.plnr_res_tile { flex: 1 1 0; min-width: 17em; }
.plnr_res_k {
    color: var(--textColor);
    font-size: 0.8em;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 8px;
}
.plnr_res_body { min-height: 2.4em; }
.plnr_bignum { font-size: 1.6em; font-weight: 700; color: var(--textColor3); }
.plnr_bignum small { font-size: 0.55em; color: var(--textColor); font-weight: 400; }
.plnr_bignum small img { height: 1.5em; width: 1.5em; vertical-align: -0.4em; margin: 0 2px 0 6px; }
.plnr_bignum.plnr_warn { color: #dc3201; }
.plnr_dutyrow { display: flex; align-items: center; gap: 8px; margin-top: 5px; font-size: 0.9em; }
.plnr_dutyrow .lbl { width: 1.8em; color: var(--textColor); text-align: center; }
.plnr_dutybar {
    flex: 1;
    height: 9px;
    background-color: var(--backgroundColor21);
    border: 1px solid var(--borderColor31);
    border-radius: 4px;
    overflow: hidden;
}
.plnr_dutybar i { display: block; height: 100%; background-color: #6fcc3d; }
.plnr_dutybar i.low { background-color: #cc793d; }
.plnr_dutybar i.zero { background-color: #dc3201; }
.plnr_dutyrow .pc { width: 3em; text-align: right; font-variant-numeric: tabular-nums; }

/* Pre-existing storage load field on hub tiles */
.plnr_hub_load {
    width: 6.5em;
    box-sizing: border-box;
    background-color: var(--color16);
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
    padding: 6px 8px;
    color: var(--textColor3);
    font-family: inherit;
    font-size: 0.93em;
    outline: none;
    transition: border-color 0.15s ease;
}
.plnr_hub_load:focus { border-color: var(--borderColor62); }

/* Per-cycle results -- toggle button in the Program result tile, and
   the full-width table panel that wraps onto its own line inside the
   (flex-wrap) results row so it dims and hides with the other tiles. */
.plnr_pc_btn { align-self: flex-start; margin-top: 4px; padding: 5px 10px; font-size: 0.9em; }
/* integrated section of the Results tab, not a nested tile: the
   .plnr_panel chrome is stripped so only the heading + scrollable
   table remain, on the tab's own ground */
.plnr_percycle {
    flex-basis: 100%;
    border: none;
    background-color: transparent;
    padding: 6px 0 0;
    gap: 8px;
}
.plnr_pc_scroll {
    max-height: 21em;
    overflow: auto;
    border: 1px solid var(--borderColor31);
    border-radius: 6px;
}
.plnr_pc_table {
    border-collapse: collapse;
    width: 100%;
    font-size: 0.9em;
    font-variant-numeric: tabular-nums;
}
.plnr_pc_table th, .plnr_pc_table td { padding: 4px 10px; text-align: right; white-space: nowrap; }
.plnr_pc_table th {
    position: sticky; /* scroll lives on .plnr_pc_scroll, so this holds */
    top: 0;
    background-color: var(--tileBg);
    color: var(--textColor);
    font-size: 0.85em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    border-bottom: 1px solid var(--borderColor31);
    z-index: 2;
}
.plnr_pc_table td { color: var(--textColor); }
.plnr_pc_table tr:nth-child(even) td { background-color: rgba(255, 255, 255, 0.025); }
.plnr_pc_table td.over { color: #dc3201; }


