/* ===========================================================================
   Tug-Of-War - interface polish

   Loaded LAST, after tug-of-war.css, so it can refine the app's own classes
   without editing them. Kept in its own file so the whole polish pass is
   reviewable (and revertable) in one place: delete the <link> and the app looks
   exactly as it did before.

   Deliberately conservative. It touches surface, border, radius, shadow, hover
   and focus - never width, display or padding on data-dense screens, because
   those reflow tables and charts.
   =========================================================================== */

:root{
    /* Depth. A single hairline plus a very soft drop is enough on black; heavy
       shadows read as grey smudges rather than elevation. */
    --tow-lift-1: 0 1px 2px rgb(0 0 0 / var(--tow-shadow-a, .5));
    --tow-lift-2: 0 4px 12px rgb(0 0 0 / var(--tow-shadow-b, .55));
    --tow-lift-3: 0 12px 32px rgb(0 0 0 / var(--tow-shadow-c, .6));

    /* A 1px inner highlight along the top edge is what makes a dark panel read
       as raised rather than as a hole cut in the page. */
    --tow-bevel: inset 0 1px 0 rgb(255 255 255 / var(--tow-bevel-a, .045));

    /* Surface tints used for hover states. On dark these lighten, on light they
       darken - one variable instead of two rule sets. */
    --tow-hover:  rgb(var(--c-ink-strong) / .06);
    --tow-hover-2:rgb(var(--c-ink-strong) / .1);

    --tow-radius:    8px;
    --tow-radius-sm: 6px;
    --tow-radius-lg: 12px;

    --tow-ease: cubic-bezier(.4, 0, .2, 1);
}


/* ---------------------------------------------------------------------------
   Focus. One consistent ring everywhere, and only for keyboard users -
   :focus-visible keeps it off mouse clicks.
   --------------------------------------------------------------------------- */
:focus-visible{
    outline: 2px solid var(--tow-accent, #2f81f7);
    outline-offset: 2px;
    border-radius: var(--tow-radius-sm);
}

::selection{
    background: rgb(var(--c-accent) / .3);
    color: rgb(var(--c-ink-strong));
}


/* ---------------------------------------------------------------------------
   Top bar

   Sticky, so the instrument name and feed-health tag stay put while you scroll
   a long stack of charts - previously they scrolled away and you lost your
   anchor. It is a sibling of .container, not a child, so nothing clips it.
   --------------------------------------------------------------------------- */
.topbar{
    position: sticky;
    top: 0;
    z-index: 50;
    background: rgb(var(--c-surface-sunken));
    border-bottom: 1px solid var(--tow-edge);
    /* Full-bleed bar so the hairline spans the window, but its contents stay
       aligned to the same 1200px column the rest of the page uses. */
    max-width: none !important;
    padding: 10px max(16px, calc((100vw - 1200px) / 2)) !important;
}

.tow-lbl{
    letter-spacing: -0.01em;
    font-size: 17px !important;
    font-weight: 600;
}

/* The gear was a bare icon inheriting the global button rules. Make it a real
   icon button with a target area and a hover state. */
#menuBtn{
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    width: 34px !important;
    height: 34px !important;
    min-width: 0 !important;
    padding: 0 !important;
    background: transparent !important;
    border: 1px solid transparent !important;
    border-radius: var(--tow-radius-sm) !important;
    box-shadow: none !important;
    transition: background .15s var(--tow-ease), border-color .15s var(--tow-ease);
}

#menuBtn:hover{
    background: var(--tow-hover) !important;
    border-color: var(--tow-edge) !important;
}

#menuBtn:active{
    background: var(--tow-hover-2) !important;
}


/* ---------------------------------------------------------------------------
   Menu

   Was a flat 180px square box flush against the viewport edge with no shadow,
   no scroll and no hover feedback. It carries up to ~39 entries once a
   simulator is running, so it needs to be able to scroll.
   --------------------------------------------------------------------------- */
.menu{
    width: 232px !important;
    margin-top: 8px;
    padding: 6px !important;
    background: rgb(var(--c-surface-raised)) !important;
    border: 1px solid var(--tow-edge-strong) !important;
    border-radius: var(--tow-radius-lg) !important;
    box-shadow: var(--tow-lift-3), var(--tow-bevel) !important;
    z-index: 60 !important;

    /* Never taller than the screen. */
    max-height: min(72vh, 560px);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.menu button{
    position: relative;
    display: block;
    width: 100%;
    min-width: 0 !important;
    padding: 9px 12px 9px 14px !important;
    margin: 0 !important;
    background: transparent !important;
    border: 0 !important;
    border-radius: var(--tow-radius-sm) !important;
    box-shadow: none !important;

    /* The labels are set in caps in the markup; sentence-cased and tracked they
       scan far faster in a long list. */
    text-transform: none !important;
    text-align: left;
    font-size: 13px !important;
    font-weight: 500 !important;
    letter-spacing: 0 !important;
    line-height: 1.35;
    color: rgb(var(--c-ink-300)) !important;
    cursor: pointer;
    transition: background .13s var(--tow-ease), color .13s var(--tow-ease);
}

/* A 2px accent bar on hover marks the row without shifting any text. */
.menu button::before{
    content: "";
    position: absolute;
    left: 4px;
    top: 50%;
    width: 2px;
    height: 0;
    border-radius: 2px;
    background: var(--tow-accent, #2f81f7);
    transform: translateY(-50%);
    transition: height .15s var(--tow-ease);
}

.menu button:hover{
    background: var(--tow-hover) !important;
    color: rgb(var(--c-ink-strong)) !important;
}

.menu button:hover::before{
    height: 16px;
}

.menu button:active{
    background: var(--tow-hover-2) !important;
}











/* ---------------------------------------------------------------------------
   Controls

   The app has ~20 bespoke button classes, each setting its own colours. Rather
   than restyle them (which would fight the meaning already encoded in those
   colours) this only adds the shared mechanics: radius, a press, and a focus
   ring. Size, padding and colour are left to the original rules.
   --------------------------------------------------------------------------- */
.menu-opt-btn,
.hedge-btn,
.simulator-process-btn,
.distro-strategy-btn,
.distro-strategy-exit-position-btn,
.buyer-premium-chart-focus-btn,
.seller-premium-chart-focus-btn,
.chest-buyer-hedge-sell-btn, .chest-seller-hedge-sell-btn,
.chest-buyer-hedge-buy-btn,  .chest-seller-hedge-buy-btn,
.chest-buyer-hedge-exit-btn, .chest-seller-hedge-exit-btn,
.chest-straddle-hedge-exit-btn,
.market-force-chart-filter,
.pick-btn{
    border-radius: var(--tow-radius-sm);
    -webkit-user-select: none;
    user-select: none;
    transition: background-color .15s var(--tow-ease),
                border-color .15s var(--tow-ease),
                color .15s var(--tow-ease),
                transform .06s var(--tow-ease),
                box-shadow .15s var(--tow-ease);
}

/* The press. A single pixel is enough to feel like the control accepted the
   click; more than that reads as a bug. */
.menu-opt-btn:active,
.hedge-btn:active,
.simulator-process-btn:active,
.distro-strategy-btn:active,
.buyer-premium-chart-focus-btn:active,
.seller-premium-chart-focus-btn:active,
.market-force-chart-filter:active,
.pick-btn:active{
    transform: translateY(1px);
}







/* Instrument tiles already carry their own utilities; add only the lift. */
.pick-btn:hover{
    box-shadow: var(--tow-lift-1);
}


/* ---------------------------------------------------------------------------
   Text

   Value-over-label pairs are the dominant pattern on this UI. Labels stay
   small, tracked and dim so the eye lands on the number first.
   --------------------------------------------------------------------------- */
.label,
.bx--label{
    letter-spacing: .06em;
}

/* Anything numeric reads better with fixed-width digits - live values stop
   jittering sideways as they tick. */
.value,
.pnl,
.spread-strike-pick-block,
#spot-price-dom,
#node-spot-spot-price,
.call-equilibrium-node-pnl,
.put-equilibrium-node-pnl{
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum';
}

/* Status strip cells: keep a long browser error or ping message to two lines
   instead of letting it stretch the row. */
#pwa-offline-status,
#remote-village-ping-test,
#current-expiry-tag{
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}





/* ---------------------------------------------------------------------------
   Motion preference
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce){
    *,
    *::before,
    *::after{
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
    }
}


/* ---------------------------------------------------------------------------
   Light theme adjustments

   Almost everything follows the --c-* channels automatically. These are the
   few things that cannot: shadow strength (a black drop shadow at 60% is a
   smudge on white) and the top bevel (a white inner highlight is invisible on
   a white card, so it becomes a faint dark line instead).
   --------------------------------------------------------------------------- */
:root[data-theme='light']{
    --tow-shadow-a: .10;
    --tow-shadow-b: .12;
    --tow-shadow-c: .16;
    --tow-bevel-a:  0;
    --tow-bevel: inset 0 1px 0 rgb(255 255 255 / .6);
}

/* The instrument-tile accent dots were tuned against black; on white the
   ring needs a touch more presence to read. */
:root[data-theme='light'] .pick-btn span:first-child{
    box-shadow: 0 0 0 1px rgb(0 0 0 / .06);
}


/* ---------------------------------------------------------------------------
   Theme switch (first row of the gear menu)
   --------------------------------------------------------------------------- */
#theme-switch-row,
#chart-style-row{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 10px 8px 14px;
    border-radius: var(--tow-radius-sm);
}

.theme-switch-label{
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-size: 13px;
    font-weight: 500;
    color: rgb(var(--c-ink-300));
    white-space: nowrap;
}

.theme-switch-icon{
    flex: none;
    color: rgb(var(--c-warn));
    transition: color .2s var(--tow-ease), transform .35s var(--tow-ease);
}

:root[data-theme='light'] .theme-switch-icon{
    color: rgb(var(--c-accent));
    transform: rotate(150deg);
}

/* The track. A real <button role="switch">, so it is keyboard operable and
   announces its state, rather than a div pretending to be a control. */
.theme-switch{
    position: relative;
    flex: none;
    width: 40px !important;
    height: 22px !important;
    min-width: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
    cursor: pointer;
    transition: background-color .22s var(--tow-ease);
}

/* THE TRACK, ON THE ID RATHER THAN THE CLASS.

   These declarations were on `.theme-switch` and never reached the screen:
   the switch is a <button> inside .menu, and `.menu button` a few hundred
   lines up resets every menu row with

       background: transparent !important;
       border: 0 !important;
       box-shadow: none !important;
       border-radius: 9px !important;

   Both rules are !important, and among important declarations SPECIFICITY
   decides, not source order - so `.menu button` (0,1,1) beat `.theme-switch`
   (0,1,0) even though it comes first in the file. The switch rendered as a
   bare knob with no track, and the comment below described a fill nobody
   could see.

   An id is (1,0,0) and settles it. The layout half stays on the class above,
   where nothing contests it. */
#theme-switch{
    /* Zero padding has to be restated here for the same reason the paint does.
       `.menu button` sets 11px/12px/16px !important, and with border-box a box
       cannot be shorter than its own padding plus border - so the declared
       22px track was being used at 24px, which pushed the 16px knob 3px from
       the top and 5px from the bottom. Visibly off-centre in a control that is
       mostly circle. */
    padding: 0 !important;

    /* The "off" track sat at edge-strong, which is almost invisible against the
       menu surface. A tinted fill plus a hairline keeps it readable in both
       themes without shouting. */
    background: rgb(var(--c-ink-strong) / .14) !important;
    border: 1px solid rgb(var(--c-edge-strong)) !important;
    border-radius: 999px !important;
    box-shadow: inset 0 1px 2px rgb(0 0 0 / .25) !important;
}

#theme-switch[aria-checked='false']{
    background: rgb(var(--c-accent)) !important;
    border-color: rgb(var(--c-accent)) !important;
}

.theme-switch-knob{
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgb(0 0 0 / .45);
    transition: transform .22s var(--tow-ease);
}

/* aria-checked=true means "dark" (the default), knob left. Light pushes it
   right - the state the eye reads as "on". */
.theme-switch[aria-checked='false'] .theme-switch-knob{
    transform: translateX(18px);
}

#theme-switch:hover{
    filter: brightness(1.12);
}




/* ---------------------------------------------------------------------------
   Two literals that cannot follow the theme on their own
   --------------------------------------------------------------------------- */

/* tug-of-war.css has a global `svg { position:absolute; width:100%; height:100% }`
   meant for the chart canvases. Any inline icon gets swallowed by it - the theme
   switch's 15px sun rendered full-menu-size. Icons must opt out explicitly. */
.theme-switch-icon,
#menuBtn svg{
    position: static !important;
    width: 15px !important;
    height: 15px !important;
    z-index: auto !important;
    pointer-events: none;
}

#menuBtn svg{
    width: 17px !important;
    height: 17px !important;
    /* was fill:white !important - invisible on a light background */
    fill: rgb(var(--c-ink-300)) !important;
}

#menuBtn:hover svg{
    fill: rgb(var(--c-ink-strong)) !important;
}

/* .tow-lbl sets color:white !important, which is literally white rather than
   "primary text", so the product name vanished in light mode. */
.tow-lbl{
    color: rgb(var(--c-ink-strong)) !important;
}


/* ---------------------------------------------------------------------------
   Modal scrim

   A scrim must always DARKEN whatever is behind it, so it cannot simply be a
   themed surface colour - bg-shell/80 would tint the page pale-on-pale in light
   mode. Black is right for dark; light mode wants a softer, bluer veil so the
   card still reads as lifted off a white page rather than sitting in a hole.
   --------------------------------------------------------------------------- */
.tow-modal{
    background: rgb(0 0 0 / .75);
}

:root[data-theme='light'] .tow-modal{
    background: rgb(15 23 42 / .42);
}


/* ---------------------------------------------------------------------------
   Service-worker precache progress

   A 3px bar pinned above everything while the worker stores the offline
   bundle, plus a small pill saying how far it has got. Both are created and
   destroyed by the controller in index.html - they only exist during an install.
   --------------------------------------------------------------------------- */
#sw-precache-bar{
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: 9600;              /* above the sticky topbar (50) and toasts */
    background: rgb(var(--c-edge));
    pointer-events: none;
}

#sw-precache-bar > span{
    display: block;
    height: 100%;
    width: 0;
    background: linear-gradient(90deg,
                rgb(var(--c-accent)) 0%,
                rgb(var(--c-accent-hover)) 100%);
    box-shadow: 0 0 8px rgb(var(--c-accent) / .55);
    transition: width .25s var(--tow-ease);
}

/* The bar alone does not say what is happening; the pill does. */
#sw-precache-pill{
    position: fixed;
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9600;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border-radius: 999px;
    background: rgb(var(--c-surface-raised));
    border: 1px solid var(--tow-edge);
    box-shadow: var(--tow-lift-2);
    font-size: 12px;
    font-weight: 500;
    color: rgb(var(--c-ink-300));
    white-space: nowrap;
    pointer-events: none;
}

#sw-precache-pill .sw-dot{
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgb(var(--c-accent));
    animation: sw-pulse 1s ease-in-out infinite;
}

#sw-precache-pill .sw-count{
    font-variant-numeric: tabular-nums;
    color: rgb(var(--c-ink-strong));
}

@keyframes sw-pulse{
    0%,100%{ opacity: 1; }
    50%    { opacity: .3; }
}

#sw-precache-bar.is-done > span{
    background: rgb(var(--c-bull));
    box-shadow: 0 0 8px rgb(var(--c-bull) / .5);
}

#sw-precache-bar.is-leaving,
#sw-precache-pill.is-leaving{
    opacity: 0;
    transition: opacity .35s var(--tow-ease);
}

@media (prefers-reduced-motion: reduce){
    #sw-precache-pill .sw-dot{ animation: none; }
}


/* Indeterminate sweep: shown from the moment an update is found until the new
   worker is active. A bar sitting at 0% reads as stalled; a sweep reads as
   working. Replaced by the real percentage as soon as progress arrives. */
#sw-precache-bar.is-indeterminate > span{
    width: 35% !important;
    transition: none;
    animation: sw-sweep 1.15s var(--tow-ease) infinite;
}

@keyframes sw-sweep{
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(340%); }
}

@media (prefers-reduced-motion: reduce){
    #sw-precache-bar.is-indeterminate > span{
        animation: none;
        width: 100% !important;
        opacity: .5;
    }
}


/* ---------------------------------------------------------------------------
   Expiry-day marker

   On expiry day an option has no time value left and the spread behaves
   nothing like it does on any other day, so this needs to read as a warning
   rather than as decoration - but it must not shout over live P&L either.
   Amber, not red: red is already the loss colour on every chart and number on
   this page, and reusing it here would read as "you are losing money".
   --------------------------------------------------------------------------- */
.tow-expiry-tag{
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: 999px;

    font-size: 10px;
    font-weight: 700;
    letter-spacing: .08em;
    line-height: 1.6;
    white-space: nowrap;

    color: #fbbf24;
    background: rgb(251 191 36 / .12);
    border: 1px solid rgb(251 191 36 / .35);
}

/* A slow pulse on the dot only. Animating the whole tag would drag the eye off
   the numbers next to it; a 2.4s dot is noticeable without being restless. */
.tow-expiry-tag::before{
    content: "";
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: currentColor;
    animation: tow-expiry-pulse 2.4s var(--tow-ease) infinite;
}

@keyframes tow-expiry-pulse{
    0%, 100% { opacity: 1; }
    50%      { opacity: .25; }
}

/* Light theme: the same amber is too pale on white, so darken the ink and keep
   the fill as the tint. */
:root[data-theme='light'] .tow-expiry-tag{
    color: #92400e;
    background: rgb(245 158 11 / .16);
    border-color: rgb(180 83 9 / .4);
}


/* Picker tile ------------------------------------------------------------- */
.pick-btn{
    position: relative;   /* anchor for the badge */
}

.pick-btn.tow-expires-today{
    border-color: rgb(251 191 36 / .45);
}

.pick-btn.tow-expires-today:hover{
    border-color: rgb(251 191 36 / .7);
}





/* Respect the OS setting - a pulsing dot is exactly the kind of motion this
   preference exists to suppress. The colour still carries the meaning. */
@media (prefers-reduced-motion: reduce){
    .tow-expiry-tag::before{ animation: none; }
}


/* The tile's own gap is sized for icon + label; with a third child it is one
   gap too many on the narrowest columns. */
.pick-btn.tow-expires-today{
    gap: 6px;
    padding-right: 8px;
}

/* At five columns a tile is 144px, and the badge leaves the longest label
   ("Crude Oil") 12px short - it was rendering as "Cru...". The decorative
   colour dot costs 14px of that, and on an expiry-day tile the amber chip is
   already doing the dot's job of giving the tile an accent. Swapping one for
   the other pays for the badge exactly, so the name stays whole.
   The dot is aria-hidden, so nothing is lost to assistive tech either. */
.pick-btn.tow-expires-today > [aria-hidden="true"]{
    display: none;
}




















































/* The header is the hover target, so make it look like one - but only on the
   option spreads that actually get a tooltip. */
/* The strike header becomes a row: label on the left, theta chip on the right.
   Scoped to the two spread headers so the other blocks sharing this class keep
   their original single-line behaviour. */
#bull-call-spread-strike,
#bear-put-spread-strike{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}









/* Alive but with nothing to report - reads as watching, not as a value. */
.tow-div-chip.is-wait{
    color: rgb(var(--c-ink-600));
    font-weight: 500;
    border-style: dashed;
}

/* Both series doing the same thing reads as ordinary; the two disagreeing is
   the state worth noticing, so only that one is tinted. */
/* Structure state, coloured the same way the divergence chip is. --c-ink-strong
   is near-black in the light theme and near-white in dark, so "black" stays
   readable either way rather than vanishing on the dark background. */
.tow-div-chip.is-up   { color: rgb(var(--c-bull)); }
.tow-div-chip.is-down { color: rgb(var(--c-bear)); }






































/* The chart-style icon is the same size and rhythm as the theme sun, but it
   must not inherit the sun's light-theme rotation - a rotated candle glyph
   reads as a mistake. */
.chart-style-icon{
    color: rgb(var(--c-accent));
}

:root[data-theme='light'] .chart-style-icon{
    color: rgb(var(--c-accent));
    transform: none;
}

/* Same opt-out as the theme icon: tug-of-war.css has a global
   `svg { position:absolute; width:100%; height:100% }` that swallows inline
   icons. */
.chart-style-icon{
    position: static !important;
    width: 15px !important;
    height: 15px !important;
    z-index: auto !important;
    pointer-events: none;
}


/* ---------------------------------------------------------------------------
   Underlying reference price (top bar)

   Sits between the sync clock and the expiry tag. Deliberately quieter than
   the expiry tag: that one is a warning, this is a reading. Neutral ink on a
   plain surface, so amber still means "something needs your attention".
   --------------------------------------------------------------------------- */
.tow-ltp-tag{
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    padding: 2px 9px;
    border-radius: 999px;

    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    font-weight: 600;
    /* Digits keep their column as the price ticks, so the tag does not jitter
       the tags next to it. */
    font-variant-numeric: tabular-nums;
    line-height: 1.6;
    white-space: nowrap;

    color: rgb(var(--c-ink-200));
    background: rgb(var(--c-ink-strong) / .06);
    border: 1px solid var(--tow-edge);
}

:root[data-theme='light'] .tow-ltp-tag{
    background: rgb(var(--c-ink-strong) / .04);
}


/* ---------------------------------------------------------------------------
   Startup screen

   Covers the app until the session is restored. Opaque rather than a scrim:
   the picker behind it is not usable yet, and showing it greyed out invites
   clicks that do nothing.
   --------------------------------------------------------------------------- */
.boot-screen{
    position: fixed;
    inset: 0;
    z-index: 80;              /* over the app, under the modals (100+) */
    display: grid;
    place-items: center;
    padding: 24px;
    background: rgb(var(--c-shell));
}

.boot-screen.hide,
.boot-screen[hidden]{ display: none !important; }

.boot-card{
    width: 100%;
    max-width: 520px;
    padding: 18px 20px 16px;
    background: rgb(var(--c-surface));
    border: 1px solid var(--tow-edge);
    border-radius: var(--tow-radius-lg);
    box-shadow: var(--tow-lift-2), var(--tow-bevel);
}

.boot-head{
    display: flex;
    align-items: center;
    gap: 10px;
    padding-bottom: 10px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--tow-edge);
}

.boot-title{
    font-size: 13px;
    font-weight: 600;
    letter-spacing: .01em;
    color: rgb(var(--c-ink-200));
}

.boot-spinner{
    flex: none;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    border: 2px solid rgb(var(--c-ink-500) / .35);
    border-top-color: rgb(var(--c-accent));
    animation: boot-spin .8s linear infinite;
}

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

/* Once the outcome is known the spinner would be a lie. */
.boot-screen.is-done .boot-spinner,
.boot-screen.is-failed .boot-spinner{
    animation: none;
    border-color: rgb(var(--c-ink-500) / .35);
}

.boot-log{
    margin: 0;
    padding: 0;
    list-style: none;
    max-height: 46vh;
    overflow-y: auto;
    overscroll-behavior: contain;

    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11.5px;
    line-height: 1.85;
    color: rgb(var(--c-ink-300));
}

.boot-log li{
    display: grid;
    grid-template-columns: 58px 14px 1fr;
    gap: 8px;
    align-items: baseline;
}

.boot-time{ color: rgb(var(--c-ink-500)); font-variant-numeric: tabular-nums; }
.boot-mark{ text-align: center; }

.boot-log li.ok   .boot-mark{ color: rgb(var(--c-bull)); }
.boot-log li.fail .boot-mark{ color: rgb(var(--c-bear)); }
.boot-log li.warn .boot-mark{ color: rgb(var(--c-warn)); }
.boot-log li.step .boot-mark{ color: rgb(var(--c-ink-500)); }

.boot-log li.fail .boot-msg{ color: rgb(var(--c-bear)); }
.boot-log li.ok   .boot-msg{ color: rgb(var(--c-ink-200)); }

@media (prefers-reduced-motion: reduce){
    .boot-spinner{ animation: none; }
}

/* Shown only when a load could not finish - :empty keeps it out of the layout
   the rest of the time, so the page is charts and book and nothing else. */
.tow-pm-alert{
    margin: 0 0 10px;
    padding: 7px 10px;
    border: 1px solid rgb(var(--c-bear) / .45);
    border-radius: 6px;
    background: rgb(var(--c-bear) / .08);
    font-size: 12px;
    line-height: 1.45;
    color: rgb(var(--c-bear));
}

.tow-pm-alert:empty{ display: none; }

/* Waiting for Load is not a failure. Same line, no alarm. */
.tow-pm-alert.is-hint{
    border-color: var(--tow-edge);
    background: rgb(var(--c-surface-sunken) / .6);
    color: rgb(var(--c-ink-400));
}

/* Portfolio Manager: two panes side by side, stacking under 900px so the
   charts keep a usable width on a phone rather than being squeezed. */
.tow-pm-charts{
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    margin-bottom: 16px;
}

@media (max-width: 900px){
    .tow-pm-charts{ grid-template-columns: 1fr; }
}

.tow-pm-pane{
    border: 1px solid var(--tow-edge);
    border-radius: 8px;
    padding: 10px 10px 4px;
    background: rgb(var(--c-surface-sunken) / .35);
    min-width: 0;
}

.tow-pm-pane-name{
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-600));
}

/* MOBILE-ONLY CHART SWITCH.
   Hidden above the 640px breakpoint - PM_SMALL_SCREEN's own cutoff - where
   both charts already sit stacked full-width and a switch would only be
   one more control for a screen with room to spare. Below it, only one
   chart is meant to be on screen at a time; see the .tow-pm-pane rule
   further down that this pairs with. */
.tow-pm-mobile-switch{
    display: none;
    gap: 8px;
    margin-bottom: 12px;
}

.tow-pm-mobile-tab{
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 8px 6px;
    border: 1px solid var(--tow-edge);
    border-radius: 8px;
    background: rgb(var(--c-surface-sunken) / .35);
    cursor: pointer;
}

.tow-pm-mobile-tab.is-active{
    border-color: rgb(var(--c-accent));
    background: rgb(var(--c-accent) / .1);
}

.tow-pm-mobile-tab-name{
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-600));
}

.tow-pm-mobile-tab-pnl{
    font-size: 14px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: rgb(var(--c-ink-strong));
}

.tow-pm-mobile-tab-pnl.is-up  { color: rgb(var(--c-bull)); }
.tow-pm-mobile-tab-pnl.is-down{ color: rgb(var(--c-bear)); }

@media (max-width: 640px){
    .tow-pm-mobile-switch{ display: flex; }

    /* Only the active side's chart stays on screen; the other is taken
       out of layout rather than merely hidden, so nothing under it -
       the books, the meters - is pushed down by an empty box. */
    .tow-pm-pane[data-pm-side]:not(.is-active){ display: none; }
}

.pm-value{
    font-size: 15px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: rgb(var(--c-ink-strong));
}

.pm-value.is-up  { color: rgb(var(--c-bull)); }
.pm-value.is-down{ color: rgb(var(--c-bear)); }

/* A floor for the very first paint only, before any chart exists. The real
   height is written by pm_size_host from pm_chart_height(), because that is
   the one place that knows whether this is a wide screen or a narrow one.
   The 320 here used to be the whole story and never tracked the JS: on a
   phone the chart was 230 tall inside a 320 box, leaving dead space under it
   that grew to 100px when the delta pane was removed. */
.tow-pm-chart{ width: 100%; min-height: 120px; }

td.pm-up  { color: rgb(var(--c-bull)); }
td.pm-down{ color: rgb(var(--c-bear)); }

/* The two books, on the same grid as the charts so each sits under its own. */
.tow-pm-books{
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
}

@media (max-width: 900px){
    .tow-pm-books{ grid-template-columns: 1fr; }
}

.tow-pm-book{
    border: 1px solid var(--tow-edge);
    border-radius: 8px;
    background: rgb(var(--c-surface-sunken) / .35);
    min-width: 0;

    /* No padding on the box. The header row carries its own background, and
       with the box padded that dark strip floated inset from the border with a
       sliver of surface showing all the way round it - which is what read as
       bad spacing. The space now lives on the cells, so the strip meets the
       border and the whole thing reads as one panel.

       overflow is hidden, not auto. The table's own scrollWidth rounds a
       single pixel past the box, which is enough for `auto` to park a
       scrollbar under every book permanently - for content that fits.
       Grouping the rows under an instrument heading made the leg names short
       (Future buy, 1060 CE sell), so the columns fit even at phone width and
       there is nothing to scroll to. Hidden also clips the header strip to the
       radius. */
    overflow: hidden;
}

/* Padded to match the cells below it, so the heading and the first column
   share a left edge. */
.tow-pm-book-head{
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 9px 12px 7px;
}

/* The total is pushed to the far edge, so the heading reads name then
   number with the space between them. */
.tow-pm-book-head .pm-value{ margin-left: auto; }

/* A global `table { table-layout: fixed; min-width: 900px }` sits above this,
   which split five columns into 180px each and pushed Qty and P&L out of a
   575px book. Both have to be undone, not just the min-width: fixed layout
   ignores content, so the columns would stay equal even once they fit. */
.tow-pm-table{
    width: 100%;
    min-width: 0;
    table-layout: auto;
    font-size: 12px;
}

/* A global `tbody td { text-align: center }` centres every cell in the app.
   A statement is read down its left edge for the name and down its right edge
   for the figure - centred text gives neither, so both are pinned here. */
.tow-pm-table tbody td{ text-align: right; }

.tow-pm-table tbody td:first-child,
.tow-pm-table thead th:first-child{ text-align: left; }

/* Compact. Line-height is what actually sets a table's density - trimming
   the padding alone leaves tall rows with cramped text, which reads as
   squeezed rather than tight. */
.tow-pm-table th,
.tow-pm-table td{ padding: 3px 8px; line-height: 1.25; }

/* The P&L column gets a floor.

   The name column asks for width:100%, and auto table layout answers that by
   squeezing every other column towards its minimum - which it computed as
   50px while the Net cell, set in the larger total font, needed 58. The eight
   pixels spilled past the column and the book's overflow:hidden ate them, so
   a six-figure Net rendered as "-59,54". Wrong number, no warning.

   A min-width is honoured ahead of the 100% request, so the figures win the
   argument and the name column takes what is left. Sized for a signed
   seven-digit value plus its padding. */
.tow-pm-table th:last-child,
.tow-pm-table td:last-child{ min-width: 74px; }

/* Same reasoning, smaller numbers. */
.tow-pm-table th:nth-child(2),
.tow-pm-table td:nth-child(2){ min-width: 66px; }

.tow-pm-table th:nth-child(3),
.tow-pm-table td:nth-child(3){ min-width: 58px; }

.tow-pm-table th:nth-child(4),
.tow-pm-table td:nth-child(4){ min-width: 54px; }

/* The outer columns take the inset the box used to provide, so nothing sits
   flush against the border. */
.tow-pm-table th:first-child,
.tow-pm-table td:first-child{ padding-left: 12px; }

.tow-pm-table th:last-child,
.tow-pm-table td:last-child{ padding-right: 12px; }

/* --- statement styling -------------------------------------------------

   Read like an account statement: quiet column headings, figures in the
   monospaced face so digits line up in columns, a named block per position
   with its own result, and one ruled total at the foot. */
.tow-pm-table thead th{
    background: none;
    border-bottom: 1px solid var(--tow-edge);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-600));
    padding-top: 5px;
    padding-bottom: 5px;
}

.tow-pm-table td{ border-bottom: 0; }

/* Every figure in the monospaced face - this is the difference between a
   table of numbers and a statement. */
.tow-pm-num{
    font-family: 'JetBrains Mono', ui-monospace, Consolas, monospace;
    font-size: 10.5px;
    letter-spacing: -.02em;
}

/* The instrument, and the spread's own result. */
.tow-pm-grp > td{
    padding-top: 7px;
    border-top: 1px solid rgb(var(--c-edge) / .55);
}

.tow-pm-table tbody tr:first-child > td{ border-top: 0; padding-top: 4px; }

.tow-pm-grp-name{
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .02em;
    color: rgb(var(--c-ink-strong));
}

.tow-pm-grp .tow-pm-num{ font-weight: 600; }

/* The legs explain the number above them, so they sit under it and read
   quieter - indented with a hairline rather than a bullet. */
.tow-pm-leg > td{ padding-top: 0; padding-bottom: 0; }

.tow-pm-leg-name{
    /* 12px to clear the border, then the indent - the tick below is positioned
       against this, so the two move together. */
    padding-left: 21px !important;
    position: relative;
    font-size: 10.5px;
    color: rgb(var(--c-ink-600));
}

.tow-pm-leg-name::before{
    content: "";
    position: absolute;
    left: 12px;
    top: 50%;
    width: 5px;
    height: 1px;
    background: rgb(var(--c-edge-strong));
}

.tow-pm-leg .tow-pm-num{ color: rgb(var(--c-ink-400)); }

/* Hover marks the whole line, so the eye can track a row across to its
   figure without a zebra stripe shouting on every other row. */
.tow-pm-table tbody tr:hover > td{ background: rgb(var(--c-ink-strong) / .035); }

/* The leg name takes what is left AND is the only column allowed to give way.

   Every cell is nowrap, so when the row is wider than the box something has to
   lose - and with overflow hidden on the book that something was the right
   edge, which is where the P&L sits. A six-figure Net (-59,540) in the larger
   total font was enough to cut the last digit off, silently: the number still
   read as a number, just the wrong one.

   width:100% with max-width:0 is the table-layout trick that lets a cell take
   all remaining space and still truncate rather than push. The numbers keep
   their natural width, the name ellipses, and nothing is ever clipped. */
.tow-pm-table th:first-child,
.tow-pm-table td:first-child{
    width: 100%;
    max-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tow-pm-table td,
.tow-pm-table th{ white-space: nowrap; }

.tow-pm-num{
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Showing the last candle close because no quote has arrived for this leg. */
.tow-pm-stale{ opacity: .55; }

/* The foot rule is doubled the way a statement's is - it reads as "nothing
   below this line" without needing a heavier weight. */
.tow-pm-total > td{
    padding-top: 6px;
    padding-bottom: 8px;
    border-top: 1px solid var(--tow-edge);
    box-shadow: inset 0 3px 0 -2px rgb(var(--c-edge) / .9);
    font-weight: 600;
    font-size: 11px;
    letter-spacing: .02em;
    color: rgb(var(--c-ink-strong));
}

.tow-pm-total .tow-pm-num{ font-size: 11.5px; }

/* Portfolio Manager - loading view. Only in the layout while a pull is
   running; .is-on is added for the duration. */
.tow-pm-loading{ display: none; }

/* Centred in the viewport rather than sitting at the top of an otherwise
   empty page. Fixed, not centred in flow: the charts and book are hidden while
   the pull runs, so there is no content height to centre against - in flow it
   would just sit at the top again.

   Width is capped and clamped to the viewport so the contract names, which can
   run to twenty characters, do not stretch the card off a phone screen. */
.tow-pm-loading.is-on{
    display: block;
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 40;
    width: min(400px, calc(100vw - 32px));
    border: 1px solid var(--tow-edge-strong);
    border-radius: 12px;
    padding: 16px 18px;
    background: rgb(var(--c-surface-raised));
    box-shadow: var(--tow-lift-3), var(--tow-bevel);
}

.tow-pm-loading-head{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 10px;
}

.tow-pm-loading-title{
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-600));
}

.tow-pm-loading-count{
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: rgb(var(--c-ink-400));
}

.tow-pm-steps{ margin: 0; padding: 0; list-style: none; }

.tow-pm-step{
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 4px 0;
    font-size: 12px;
    color: rgb(var(--c-ink-600));
}

.tow-pm-step-name{ min-width: 96px; font-weight: 600; }

.tow-pm-step-what{
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The mark is the state: hollow while waiting, pulsing while in flight, solid
   when it lands. Colour alone would not read for everyone. */
.tow-pm-step-mark{
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: 1px solid rgb(var(--c-ink-600));
}

.tow-pm-step.is-live{ color: rgb(var(--c-ink-strong)); }

.tow-pm-step.is-live .tow-pm-step-mark{
    border-color: rgb(var(--c-accent));
    background: rgb(var(--c-accent));
    animation: tow-pm-pulse 1s ease-in-out infinite;
}

.tow-pm-step.is-done{ color: rgb(var(--c-ink-400)); }

.tow-pm-step.is-done .tow-pm-step-mark{
    border-color: rgb(var(--c-bull));
    background: rgb(var(--c-bull));
}

.tow-pm-step.is-fail{ color: rgb(var(--c-bear)); }

.tow-pm-step.is-fail .tow-pm-step-mark{
    border-color: rgb(var(--c-bear));
    background: rgb(var(--c-bear));
}

@keyframes tow-pm-pulse{
    0%, 100% { opacity: 1; }
    50%      { opacity: .35; }
}

@media (prefers-reduced-motion: reduce){
    .tow-pm-step.is-live .tow-pm-step-mark{ animation: none; }
}

.tow-pm-loading-foot{
    margin: 8px 0 0;
    font-size: 11px;
    color: rgb(var(--c-ink-600));
    min-height: 14px;
}

/* The previous load's charts and figures are hidden rather than left standing
   while a new pull replaces them piece by piece.

   Keyed off the attribute rather than a list of class names, for the same
   reason the JS is: the two lists had to agree, and when the reach strip
   arrived neither knew about it. */
[data-pm-hide-on-load].is-loading{ display: none !important; }

/* Portfolio Config - the pair chips. A toggle, so it reads as pressed rather
   than as a link: filled and accented when in the book, outlined when not. */
/* ------------------------------------------------ the sync countdown --

   One line under the instrument names, and the only boot fact left on the
   page: how long the instrument dump has before it goes stale. Every symbol
   here is resolved against that dump, so it is the one number that explains an
   empty portfolio. Small and bold - read at a glance, ignored otherwise.

   The urgency colours live in tug-of-war.css, keyed off the data-urgency
   attribute the countdown script sets. */
/* Type and colour are set on the id in tug-of-war.css, which outranks any
   class - see the note there. This is only the empty state. */
.tow-pm-sync:empty{ display: none; }

/* ------------------------------------------------ the dashboard bar --

   The portfolio page is the app's front door, and this is its only chrome:
   what the book holds, and the date it is all measured from. It was a modal
   until the page became the dashboard.

   Deliberately quiet. It sits above two charts that are the actual subject,
   so it reads as a strip of controls, not as a panel competing with them. */
.tow-pm-bar{
    padding: 10px 14px 12px;
    border-bottom: 1px solid var(--tow-edge);
    background: rgb(var(--c-surface-sunken) / .45);
}

.tow-pm-bar-row{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-bottom: 10px;
}

.tow-pm-bar-title{
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-500));
}

/* Label and field on one line - the word is doing the work a placeholder
   would, and a stacked label would push the charts down for nothing. */
.tow-pm-bar-field{
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    font-weight: 500;
    color: rgb(var(--c-ink-400));
}

.tow-pm-bar-field input{
    height: 30px;
    padding: 0 9px;
    font-size: 12px;
    color: rgb(var(--c-ink-100));
    background: rgb(var(--c-surface-input));
    border: 1px solid var(--tow-edge);
    border-radius: 4px;
}

.tow-pm-bar-field input:focus-visible{
    outline: none;
    border-color: rgb(var(--c-accent));
    box-shadow: 0 0 0 1px rgb(var(--c-accent));
}

.tow-pm-bar-go{
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    height: 30px;
    padding: 0 15px;
    color: #fff;
    background: rgb(var(--c-accent-dim));
    border: 1px solid transparent;
    border-radius: 4px;
    cursor: pointer;
    transition: background .14s var(--tow-ease);
    -webkit-appearance: none;
    appearance: none;
}

.tow-pm-bar-go:hover{ background: rgb(var(--c-accent)); }

.tow-pm-bar-go:focus-visible{
    outline: 2px solid rgb(var(--c-accent));
    outline-offset: 2px;
}

/* Narrow: the row wraps on its own, but the date field should take the width
   rather than sit stranded beside a wrapped button. */
@media (max-width: 640px){
    .tow-pm-bar-field{ flex: 1 1 100%; }
    .tow-pm-bar-field input{ flex: 1 1 auto; min-width: 0; }
}


.tow-pm-chips{
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* The interval chips sit inside the bar row rather than under it, so they are
   a size down from the instrument chips and packed tighter. */
.tow-pm-chips.is-tight{ gap: 4px; display: inline-flex; }

.tow-pm-chip.is-tiny{
    font-size: 11px;
    padding: 5px 9px;
    min-width: 34px;
    text-align: center;
}

.tow-pm-interval{ gap: 8px; }

/* The rule between chip groups. Not a chip: no border, no background, not
   focusable - it is punctuation. Centred against the chips rather than the
   line box, so it lines up when the row wraps. */
.tow-pm-chip-sep{
    align-self: center;
    color: rgb(var(--c-ink-600));
    font-size: 13px;
    font-weight: 400;
    line-height: 1;
    padding: 0 2px;
    user-select: none;
    pointer-events: none;
}

.tow-pm-chip{
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .02em;
    line-height: 1;
    padding: 8px 13px;
    border-radius: 999px;
    border: 1px solid var(--tow-edge);
    background: rgb(var(--c-surface-sunken) / .6);
    color: rgb(var(--c-ink-400));
    cursor: pointer;
    transition: background .14s var(--tow-ease), border-color .14s var(--tow-ease),
                color .14s var(--tow-ease);
    -webkit-appearance: none;
    appearance: none;
}

.tow-pm-chip:hover{
    border-color: var(--tow-edge-strong);
    color: rgb(var(--c-ink-200));
}

.tow-pm-chip.is-on{
    border-color: rgb(var(--c-accent));
    background: rgb(var(--c-accent) / .16);
    color: rgb(var(--c-accent));
}

.tow-pm-chip.is-on:hover{ background: rgb(var(--c-accent) / .24); }

.tow-pm-chip:focus-visible{
    outline: 2px solid rgb(var(--c-accent));
    outline-offset: 2px;
}


/* ==================================================================
   THE DARK THEME - DEEP TEAL

   A retune of the dark palette, not a rewrite of any component. Everything on
   this page already draws itself from the --c-* tokens, so changing the
   tokens changes the app; the rules below that are not tokens are the three
   things tokens cannot carry - the shape of a box, the rhythm of the type,
   and how much air sits between things.

   WHY IT IS AN OVERRIDE AND NOT AN EDIT. The palette is defined in
   css/tailwind.css, which is COMPILED - `cd build && npm run css` rewrites it
   from the source config, and an edit made here would be silently discarded
   the next time anyone ran that. This file loads last, so redefining the same
   custom properties on :root wins without touching the build output.

   THE LIGHT THEME IS NOT TOUCHED. Only :root and [data-theme='dark'] are
   redefined, so the light theme keeps every value it had.

   Delete this whole block to go back to the slate palette. Nothing else
   depends on it. */

:root,
:root[data-theme='dark']{
    /* Ink on near-black with a green cast, rather than blue-grey slate. The
       page reads as one material lit from behind rather than a stack of
       panels. */
    --c-shell:          6 17 14;
    --c-surface:       10 24 21;
    --c-surface-raised:14 32 25;
    --c-surface-input:  8 20 16;
    --c-surface-sunken: 5 14 12;

    /* Borders carry the same hue as the ground. A neutral grey edge on a
       green ground reads as a seam; a green one reads as an edge. */
    --c-edge:          22 51 43;
    --c-edge-strong:   35 82 68;

    /* THE ONE BRIGHT COLOUR. Everything interactive is this mint; nothing
       else is, which is what makes it mean "you can touch this". */
    --c-accent:        53 224 161;
    --c-accent-hover:  94 240 185;
    --c-accent-dim:    27 168 118;

    /* Up and down still have to be told apart at a glance, and on a green
       ground a green "up" is nearly invisible - so up is brighter and cooler
       than the accent, and down is warm enough to separate from both. */
    --c-bull:          63 224 141;
    --c-bull-bright:  126 245 178;
    --c-bull-dim:      26 140 90;
    --c-bear:         255 107 107;
    --c-bear-bright:  255 138 138;
    --c-bear-dim:     176 58 58;
    --c-warn:         240 191 106;
    --c-warn-bright:  255 214 128;

    /* Text tinted towards the ground rather than pure grey, so nothing looks
       pasted on. */
    --c-ink-strong:   232 255 248;
    --c-ink-100:      214 245 236;
    --c-ink-200:      191 230 218;
    --c-ink-300:      159 207 193;
    --c-ink-400:      127 175 159;
    --c-ink-500:       98 138 125;
    --c-ink-600:       71 102  92;

    color-scheme: dark;
}

/* ---------------------------------------------------------- the shape --

   One radius, one border, one background - applied to the things that are
   already boxes rather than to a new wrapper, so nothing has to move in the
   markup. The corner is generous because the panels are large; a 4px radius
   on a 900px panel reads as a mistake rather than as a choice. */
:root{
    --tow-box-radius: 14px;
    --tow-box-pad:    18px;
}

.tow-pm-bar,
.tow-pm-book,
#pm-left-chart,
#pm-right-chart{
    border-radius: var(--tow-box-radius);
    border: 1px solid var(--tow-edge);
    background: rgb(var(--c-surface));
}

.tow-pm-bar,
.tow-pm-book{
    padding: var(--tow-box-pad);
}

/* The charts hold a canvas that paints to its own edges, so the rounding has
   to be enforced by the container rather than requested of the child. */
#pm-left-chart,
#pm-right-chart{
    overflow: hidden;
    padding: 6px;
}

/* Air between the panels. The page was built tight; at this scale the
   breathing room is what makes it read as composed rather than crowded. */
.tow-pm-bar{ margin-bottom: 18px; }
.tow-pm-book{ margin-top: 18px; }

/* ----------------------------------------------------------- the type --

   Two faces, used for two jobs. Inter carries prose. JetBrains Mono - already
   loaded for the numbers - carries every LABEL: the small uppercase words
   that name a thing rather than say something about it. Wide tracking is what
   makes a short uppercase word read as a label and not as shouting. */
.tow-pm-bar-title,
.tow-pm-pane-name,
.tow-pm-bar-field > span:first-child,
.tow-pm-table thead th{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    text-transform: uppercase;
    letter-spacing: .16em;
    font-weight: 500;
}

.tow-pm-bar-title{
    font-size: 11px;
    color: rgb(var(--c-ink-400));
}

.tow-pm-pane-name{
    font-size: 11px;
    color: rgb(var(--c-ink-300));
}

.tow-pm-bar-field > span:first-child,
.tow-pm-table thead th{
    font-size: 9.5px;
    color: rgb(var(--c-ink-500));
    letter-spacing: .18em;
}

/* The one number that matters on each book, given the room to be the largest
   thing in its row. */
.tow-pm-book-head .pm-value{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 20px;
    font-weight: 600;
    letter-spacing: -.01em;
}

/* --------------------------------------------------------- the chips --

   Pill, one-pixel edge, mint when chosen. The chosen state is a fill rather
   than a colour change: at this size a coloured word on a dark ground is not
   different enough from an uncoloured one to be read at a glance. */
.tow-pm-chip{
    border-radius: 999px;
    border-color: var(--tow-edge);
    background: rgb(var(--c-surface-sunken) / .7);
    color: rgb(var(--c-ink-400));
    letter-spacing: .06em;
}

.tow-pm-chip:hover{
    border-color: rgb(var(--c-accent) / .45);
    color: rgb(var(--c-ink-200));
}

.tow-pm-chip.is-on{
    background: rgb(var(--c-accent) / .14);
    border-color: rgb(var(--c-accent) / .55);
    color: rgb(var(--c-accent));
}

/* Load - the only button on the page that starts something. */
.tow-pm-bar-go{
    border-radius: 10px;
    background: rgb(var(--c-accent));
    color: rgb(var(--c-surface-sunken));
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    font-size: 11px;
    padding: 9px 20px;
    border: 0;
}

.tow-pm-bar-go:hover{ background: rgb(var(--c-accent-hover)); }

/* The date field, matching the chips rather than the browser. */
.tow-pm-bar-field input[type='date']{
    border-radius: 8px;
    border: 1px solid var(--tow-edge);
    background: rgb(var(--c-surface-sunken) / .7);
    color: rgb(var(--c-ink-100));
    padding: 8px 10px;
}

.tow-pm-bar-field input[type='date']:focus{
    outline: none;
    border-color: rgb(var(--c-accent) / .6);
}


/* ---------------------------------------------- the book, and the chips --

   The panel, the type and the chips were themed; the two things INSIDE them
   were not, so a teal panel held a slate table and the instrument row still
   read as the old design wearing a new frame.

   THE BOOK READS AS A STATEMENT, which it already did - this only retunes it
   to the ground it now sits on. Column headings recede, the instrument line
   is the one bright row, its legs sit under it in a quieter colour, and the
   rules between them are the panel's own edge rather than a neutral grey. */

.tow-pm-book-head{
    /* The heading sits on the panel rather than floating above it: a hairline
       under it does the work a background would, without adding a second
       surface colour inside a surface. */
    padding: 4px 4px 12px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--tow-edge);
    gap: 12px;
}

.tow-pm-table thead th{
    border-bottom: 1px solid rgb(var(--c-edge) / .7);
    color: rgb(var(--c-ink-600));
    padding-top: 8px;
    padding-bottom: 8px;
}

/* The instrument line. Brighter than its legs and separated from the block
   above by the panel's own edge colour, so the table reads as a list of
   positions rather than a grid of cells. */
.tow-pm-grp > td{
    padding-top: 10px;
    border-top: 1px solid rgb(var(--c-edge) / .6);
}

.tow-pm-grp-name{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10.5px;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-100));
}

.tow-pm-leg-name{ color: rgb(var(--c-ink-500)); }

/* The tick joining a leg to its instrument, in the edge colour like every
   other rule on the page. */
.tow-pm-leg-name::before{ background: rgb(var(--c-edge-strong)); }

.tow-pm-leg .tow-pm-num{ color: rgb(var(--c-ink-400)); }

/* Hover: the faintest wash of the accent rather than white. On a green ground
   a white overlay greys the row; the accent keeps its hue. */
.tow-pm-table tbody tr:hover{
    background: rgb(var(--c-accent) / .05);
}

/* Money keeps its meaning. The cells carry pm-up / pm-down and the book's
   own total carries is-up / is-down - two names for one idea, which is worth
   knowing before writing a rule for either. Both are listed rather than
   renamed, because renaming one means touching the markup that emits it.
   They point at the same two tokens, retuned above to stand off this
   ground. */
td.pm-up,
.tow-pm-book-head .pm-value.is-up{ color: rgb(var(--c-bull)); }

td.pm-down,
.tow-pm-book-head .pm-value.is-down{ color: rgb(var(--c-bear)); }

/* ---- the instrument row ---- */

/* The chips sit in their own band inside the bar, ruled off from the controls
   above them. They are a different KIND of thing - what the book holds, not
   how it is drawn - and the rule says so without a heading. */
.tow-pm-chips#pm-pair-chips{
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid rgb(var(--c-edge) / .8);
    gap: 7px;
}

/* The rule between chip groups, in the same colour as every other rule. */
.tow-pm-chip-sep{
    color: rgb(var(--c-edge-strong));
    padding: 0 5px;
}

/* The instrument-sync line, last thing in the bar: small, quiet, and given
   room rather than tucked against the chips. */
.tow-pm-sync{
    margin-top: 14px;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-600));
}

/* The alert, when there is one. Warm against a green ground, and shaped like
   the panels rather than like a browser default. */
.tow-pm-alert{
    border-radius: 10px;
    border: 1px solid rgb(var(--c-warn) / .35);
    background: rgb(var(--c-warn) / .08);
    color: rgb(var(--c-warn-bright));
    padding: 10px 14px;
}

.tow-pm-alert.is-hint{
    border-color: rgb(var(--c-edge-strong));
    background: rgb(var(--c-surface-sunken) / .6);
    color: rgb(var(--c-ink-400));
}


/* --------------------------------------------- the menu and the modals --

   Both were already token-driven, so the palette reached them on its own -
   these are the places where it did NOT, because something had been written
   as a literal or as a neutral that only worked on slate.

   --tow-accent IS ONE OF THEM. It has a hard-coded #2f81f7 fallback and is
   never set anywhere, so every rule reading it - the focus ring, the menu's
   hover bar - drew the old blue on the new ground. Setting it once here fixes
   all of them, and it is set from the token rather than repeated as a hex so
   there is one place the accent lives. */
:root,
:root[data-theme='dark']{
    --tow-accent: rgb(var(--c-accent));

    /* Hover washes. Tinting with ink-strong means white, and a white wash on
       a green ground drains the hue out of whatever is under it - rows go
       grey rather than lighter. The accent keeps its colour at the same
       weight. */
    --tow-hover:   rgb(var(--c-accent) / .08);
    --tow-hover-2: rgb(var(--c-accent) / .14);
}

/* ---- the side menu ---- */

.menu{
    border-radius: var(--tow-box-radius) !important;
    padding: 8px !important;
    background: rgb(var(--c-surface)) !important;
    border-color: var(--tow-edge) !important;
}

/* The rows read as a list, so the labels take the same mono-uppercase
   treatment as every other label on the page. They are the only text in the
   menu, which is what lets them carry the tracking without crowding. */
.menu button{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace !important;
    font-size: 10.5px !important;
    letter-spacing: .12em !important;
    text-transform: uppercase !important;
    color: rgb(var(--c-ink-400)) !important;
    padding: 11px 12px 11px 16px !important;
    border-radius: 9px !important;
}

.menu button:hover{ color: rgb(var(--c-accent)) !important; }

/* The hover bar was already 2px of --tow-accent; it is mint now that the
   variable is set, and taller so it reads against the larger row. */
.menu button:hover::before{ height: 20px; }

/* The two setting rows are not list items - they get the sunken treatment so
   the eye separates "a thing you set" from "a page you go to". */
#theme-switch-row,
#chart-style-row{
    border-radius: 10px;
    background: rgb(var(--c-surface-sunken) / .55);
    border: 1px solid rgb(var(--c-edge) / .7);
    margin-bottom: 6px;
    padding: 10px 10px 10px 14px;
}

.theme-switch-label{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-400));
}

/* The knob was pure white, which is the brightest thing on the page and the
   least important. Tinted to the ground it sits on. */
.theme-switch-knob{ background: rgb(var(--c-ink-100)); }

#menuBtn{
    border-radius: 10px;
    border: 1px solid var(--tow-edge);
    background: rgb(var(--c-surface) / .8);
}

#menuBtn:hover{
    border-color: rgb(var(--c-accent) / .5);
    background: rgb(var(--c-accent) / .08);
}

/* ---- the modals ---- */

/* The card. Its classes are Tailwind utilities reading the same tokens, so
   the colours already followed; only the corner did not - rounded-lg is 8px
   and every panel on the page is now 14. */
.tow-modal > div{
    border-radius: var(--tow-box-radius) !important;
    border-color: var(--tow-edge) !important;
}

/* The scrim is deliberately NOT a themed surface - it has to darken whatever
   is behind it, and a tinted one would wash the page rather than recede it.
   A trace of the ground's hue keeps it from reading as a grey hole. */
.tow-modal{ background: rgb(2 8 6 / .78); }

/* Headings and field labels, in the same voice as the rest of the app. */
.tow-modal p.text-lg{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px !important;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-200)) !important;
}

.tow-modal label{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9.5px !important;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-500)) !important;
}

/* Inputs match the date field in the portfolio bar rather than the browser. */
.tow-modal input,
.tow-modal select,
.tow-modal textarea{
    border-radius: 9px !important;
    background: rgb(var(--c-surface-sunken) / .7) !important;
    border-color: var(--tow-edge) !important;
}

.tow-modal input:focus,
.tow-modal select:focus,
.tow-modal textarea:focus{
    border-color: rgb(var(--c-accent) / .6) !important;
    box-shadow: 0 0 0 1px rgb(var(--c-accent) / .35) !important;
}

/* The header and footer rules, in the panel's own edge colour. */
.tow-modal .border-b,
.tow-modal .border-t{ border-color: var(--tow-edge) !important; }

/* The close button: quiet until wanted, then mint rather than white. */
.tow-modal .close-modal{ border-radius: 8px !important; }

.tow-modal .close-modal:hover{
    background: rgb(var(--c-accent) / .12) !important;
    color: rgb(var(--c-accent)) !important;
}


/* ------------------------------------- the loading panel and the bars --

   Colour reached these on its own - every one of them was already written
   against the tokens. What did not reach them is the SHAPE and the VOICE: a
   12px corner beside 14px panels, sentence-case titles beside mono-uppercase
   labels everywhere else, and a card sitting on surface-raised while every
   other card on the page sits on surface.

   The states are left exactly as they were. Waiting is a hollow ring, in
   flight is a pulsing accent dot, landed is solid bull, failed is bear - and
   the mark carries it as well as the colour, so it reads without relying on
   hue. That was right before the palette changed and is right after it. */

.tow-pm-loading.is-on{
    border-radius: var(--tow-box-radius);
    border-color: var(--tow-edge);
    background: rgb(var(--c-surface));
    padding: 20px 22px;
    width: min(440px, calc(100vw - 32px));
}

.tow-pm-loading-head{
    padding-bottom: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid rgb(var(--c-edge) / .8);
}

.tow-pm-loading-title{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
    letter-spacing: .18em;
    color: rgb(var(--c-ink-500));
}

.tow-pm-loading-count{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    letter-spacing: .04em;
    color: rgb(var(--c-ink-300));
}

/* The contract names are the content here, so they get the monospaced face -
   twenty-character symbols line up instead of ragging. */
.tow-pm-step{
    padding: 5px 0;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10.5px;
    letter-spacing: .02em;
    color: rgb(var(--c-ink-600));
}

.tow-pm-step-name{
    min-width: 104px;
    letter-spacing: .1em;
    text-transform: uppercase;
    font-weight: 500;
}

.tow-pm-loading-foot{
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid rgb(var(--c-edge) / .6);
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-600));
}

/* ---- the precache bar and its pill ---- */

/* The bar is 3px of accent across the top of the page. On the old ground its
   glow was a blue halo; the token change makes it mint, and the halo is
   softened because a saturated accent on a near-black ground blooms further
   than the same colour did on slate. */
#sw-precache-bar{ background: rgb(var(--c-edge) / .8); }

#sw-precache-bar > span{
    box-shadow: 0 0 10px rgb(var(--c-accent) / .35);
}

#sw-precache-pill{
    border-radius: 10px;
    background: rgb(var(--c-surface));
    border-color: var(--tow-edge);
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-400));
    padding: 8px 14px;
}

#sw-precache-pill .sw-count{ color: rgb(var(--c-accent)); }

/* ---- the instrument-sync ring ---- */

/* Drawn as SVG strokes, so it reads its colours from these two rules rather
   than from a fill. Both were already tokens; the track is thinned against
   the darker ground, where the old weight read as a solid circle. */


/* ------------------------------- the top bar, and the buttons in modals --

   THE TOP BAR was already on the palette - surface-sunken and the edge token -
   so only its type and its depth needed doing. It is sticky, so the page
   scrolls under it; a translucent ground with a blur behind it reads as a
   pane the content passes beneath rather than a lid sitting on top. The
   fallback matters: browsers without backdrop-filter get the solid colour, so
   the bar is never transparent-and-unreadable. */
.topbar{
    background: rgb(var(--c-surface-sunken));
    padding-top: 12px !important;
    padding-bottom: 12px !important;
}

@supports (backdrop-filter: blur(8px)){
    .topbar{
        background: rgb(var(--c-surface-sunken) / .82);
        backdrop-filter: blur(10px);
    }
}

/* The app's name, in the same voice as every other label. It was the one
   piece of 17px sentence-case left on the page. */
.tow-lbl{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px !important;
    font-weight: 600;
    letter-spacing: .22em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-200)) !important;
}

/* The memory tag sits beside the name and is diagnostic, not a warning - it
   was a red pill shouting next to the title. Quiet unless it is actually
   telling you something, which is what the red was for. */
#used-memory.bx--tag{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
    letter-spacing: .1em;
    background: rgb(var(--c-surface) / .8);
    color: rgb(var(--c-ink-500));
    box-shadow: inset 0 0 0 1px rgb(var(--c-edge) / .9);
}

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

   THIS IS A CONTRAST FIX, not a preference. The modal footers use Tailwind's
   .bg-accent-dim with .text-white. On the old blue that was white on #1f6feb,
   about 5:1 - fine. The new accent-dim is a mint, and white on mint is about
   2.8:1, which fails AA for body text and is genuinely hard to read.

   Mint is a LIGHT colour, so it takes dark text, exactly like the Load button
   on the portfolio bar. Dark ink on full accent measures about 13:1. */
.tow-modal .bg-accent-dim,
.tow-modal .bg-accent{
    background: rgb(var(--c-accent)) !important;
    color: rgb(var(--c-surface-sunken)) !important;
    border-color: transparent !important;
    font-weight: 600;
}

.tow-modal .bg-accent-dim:hover,
.tow-modal .bg-accent:hover{
    background: rgb(var(--c-accent-hover)) !important;
}

/* Secondary buttons in the same row keep the ground and take an edge, so the
   primary is the only filled thing and reads as the one that acts. */
.tow-modal .border-t button:not(.bg-accent):not(.bg-accent-dim),
.tow-modal .border-b button:not(.close-modal){
    background: rgb(var(--c-surface-sunken) / .7) !important;
    border: 1px solid var(--tow-edge) !important;
    color: rgb(var(--c-ink-300)) !important;
}

.tow-modal .border-t button:not(.bg-accent):not(.bg-accent-dim):hover{
    border-color: rgb(var(--c-accent) / .5) !important;
    color: rgb(var(--c-ink-100)) !important;
}

/* Every button in a modal gets the panel's corner rather than the 4px
   default, so nothing in the footer disagrees with the card holding it. */
.tow-modal button{ border-radius: 9px !important; }

/* The footer band, one step down from the body so the actions read as a
   separate register. */
.tow-modal .bg-surface-sunken{
    background: rgb(var(--c-surface-sunken) / .55) !important;
}

/* ---- the credential modal ----

   Nothing here is special-cased for looks; it takes the modal rules above.
   The one addition is that its fields are the only password inputs in the
   app, and a masked value in a proportional face is a row of dots of
   different widths. */
#cred-prompt-modal input[type='password']{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    letter-spacing: .12em;
}


/* ------------------------------------------------------- small screens --

   The grid already stacks below 900px, so nothing OVERFLOWED on a phone -
   what it did instead was waste the width it had. A 14px-radius panel with
   18px of padding inside a 16px page margin leaves 305px of a 375px screen
   for the content, and the book's five columns were being asked to fit in
   that at desktop sizes.

   Nothing here changes structure. It gives back margins, lets the one thing
   that genuinely cannot shrink - a five-column table of figures - scroll on
   its own instead of squeezing, and takes a few pixels off the type. */

@media (max-width: 640px){

    /* The page's own gutter, halved. Sixteen a side on a phone is a tenth of
       the screen spent on nothing. */
    .container{ padding-left: 10px !important; padding-right: 10px !important; }

    .tow-pm-bar,
    .tow-pm-book{ padding: 13px; }

    .tow-pm-bar{ margin-bottom: 12px; }
    .tow-pm-book{ margin-top: 12px; }

    .tow-pm-charts,
    .tow-pm-books{ gap: 10px; }

    .tow-pm-pane{ padding: 6px 6px 2px; }

    /* THE BAR STACKS. Reference, Interval, Chart and Load in a row need about
       560px; below that they wrap into a ragged two-and-a-half lines. Each
       control on its own row reads as a short form instead. */
    .tow-pm-bar-row{
        flex-wrap: wrap;
        gap: 10px;
    }

    .tow-pm-bar-field{
        flex: 1 1 100%;
        justify-content: space-between;
    }

    .tow-pm-bar-field input[type='date']{ flex: 0 0 auto; }

    /* Load spans the row - it is the only action on the page, and a
       full-width target is the easiest thing to hit with a thumb. Full width
       did not make it tall, though: it measured 30px. Same treatment as the
       chips. */
    .tow-pm-bar-go{
        display: inline-flex;
        align-items: center;
        justify-content: center;
        flex: 1 1 100%;
        min-height: 44px;
        padding: 0 20px;
    }

    /* THE TABLE SCROLLS RATHER THAN SQUEEZES.

       Five columns of figures have a floor: below it the numbers wrap inside
       their cells and a statement stops being readable. So the book keeps its
       desktop column widths and the row scrolls sideways within the panel -
       one thing moving, deliberately, rather than everything compressing. */
    .tow-pm-book > .tow-pm-table,
    .tow-pm-book table{ min-width: 300px; }

    .tow-pm-book{
        overflow-x: auto;
        overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
    }

    /* The heading stays put while the figures scroll under it. */
    .tow-pm-book-head{ position: sticky; left: 0; }

    /* Type, one notch down. The mono labels carry their tracking at 9px; the
       book total does not need to be 20px on a 375px screen. */
    .tow-pm-bar-title{ font-size: 10px; }
    .tow-pm-book-head .pm-value{ font-size: 17px; }
    .tow-pm-table th,
    .tow-pm-table td{ padding: 3px 6px; }

    /* TAP TARGETS, SIZED RATHER THAN HOPED FOR.

       The rule here used to say the chips "clear the 44px target height".
       They did not: measured on a 375px screen every one of the thirteen was
       27px tall, and the narrowest - the EMA "50" - was 38 wide. Padding plus
       an 11px line box simply does not reach 44, and nothing was checking.

       Set the height directly instead of inferring it from padding, and
       centre the label inside it. min-height rather than height so a chip
       whose text wraps grows instead of clipping. 44 is the figure both
       Apple and Google publish for a comfortable thumb; WCAG 2.2 AA's floor
       is 24, which the old chips technically cleared - "not a violation" and
       "easy to hit" are different claims.

       The cost is real and accepted: taller rows, so the indicator group
       wraps to three lines on the narrowest phones instead of two. A control
       that is awkward to hit is worse than one that takes another line. */
    .tow-pm-chip,
    .tow-pm-chip.is-tiny{
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
    }

    .tow-pm-chip{ padding: 0 14px; }
    .tow-pm-chip.is-tiny{ padding: 0 12px; min-width: 44px; }

    /* The modals were max-w-2xl with 24px of side padding - on a phone that
       is a card with almost no room in it. */
    .tow-modal{ padding: 10px !important; }
    .tow-modal .px-6{ padding-left: 14px !important; padding-right: 14px !important; }
    .tow-modal .border-t{ flex-wrap: wrap; }

    /* The footer buttons - Done, Save, Confirm - are the action the dialog
       exists for, and they measured 29px. Every modal on the page shares this
       footer, so one rule covers all of them. */
    .tow-modal .border-t button{
        display: inline-flex;
        align-items: center;
        justify-content: center;
        flex: 1 1 auto;
        min-height: 44px;
    }

    /* And the close cross, which Tailwind sizes at w-8 h-8 - 32px, and which
       sits in the corner where a thumb is least accurate. The icon inside
       keeps its own size; only the hit area grows. */
    .tow-modal button[data-modal-close]{
        min-width: 44px;
        min-height: 44px;
    }

    /* The menu was a fixed 232px panel; on the narrowest phones that is most
       of the screen and it could sit half off the edge. */
    .menu{
        width: min(232px, calc(100vw - 20px)) !important;
        max-height: min(70vh, 480px);
    }

    /* The top bar keeps its hairline but gives back the padding. */
    .topbar{
        padding-left: 12px !important;
        padding-right: 12px !important;
    }

    .tow-lbl{ font-size: 11px !important; letter-spacing: .16em; }

    /* THE LEG NAME WAS BEING EATEN.

       The four figure columns carry min-widths (74/66/58/54) that add up to
       252 of the 305px a phone panel has, leaving about fifty for the name -
       so every row read "RELIA... / 143... / CRUDE...", which loses the one
       thing that identifies the leg: its strike.

       The columns are narrowed rather than one being dropped. Hiding a column
       looks tempting and is a trap here: the instrument rows use a
       `colspan=3` spacer, so hiding the second column would shift their total
       out of the P&L column and quietly mis-align the block that matters
       most.

       And the name wraps instead of truncating. Two short lines say
       "RELIANCE 1430 CE"; one truncated line says nothing. */
    .tow-pm-table th:last-child,
    .tow-pm-table td:last-child{ min-width: 56px; }

    .tow-pm-table th:nth-child(2),
    .tow-pm-table td:nth-child(2){ min-width: 50px; }

    .tow-pm-table th:nth-child(3),
    .tow-pm-table td:nth-child(3){ min-width: 46px; }

    .tow-pm-table th:nth-child(4),
    .tow-pm-table td:nth-child(4){ min-width: 38px; }

    .tow-pm-grp-name,
    .tow-pm-leg-name{
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
        word-break: break-word;
        line-height: 1.3;
    }

    .tow-pm-leg-name{ padding-left: 18px !important; }

    .tow-pm-num{ font-size: 10px; }
}



/* ------------------------------- the instrument picker, by screen size --

   Wide: the chips sit in the bar, where they have always been.
   Narrow: the same chips move behind a button, because nine of them wrap to
   three rows on a phone and push the charts off the first screen entirely.

   Both hosts are always in the document; only their visibility changes. That
   is what keeps one render path and one click handler for the two of them. */
#pm-pairs-btn{ display: none; }

/* The modal's copy is the one hidden on a wide screen. */
#pm-pair-chips-modal{ display: none; }

@media (max-width: 640px){
    #pm-pair-chips{ display: none; }

    #pm-pair-chips-modal{
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
    }

    #pm-pairs-btn{
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 6px;
        flex: 1 1 100%;

        /* Not the Load button. That one starts a pull; this one opens a
           drawer, and two identical mint blocks would say they do the same
           kind of thing. */
        background: rgb(var(--c-surface-sunken) / .7);
        color: rgb(var(--c-ink-200));
        border: 1px solid var(--tow-edge);
    }

    #pm-pairs-btn:hover{
        border-color: rgb(var(--c-accent) / .5);
        background: rgb(var(--c-accent) / .08);
    }

    #pm-pairs-count{ color: rgb(var(--c-accent)); }

    /* Roomier in the modal than in the bar - there is space for it there, and
       a chip that is easy to hit is the point of moving them. */
    /* Roomier than the bar's, and the 44px minimum above still applies. */
    #pm-pair-chips-modal .tow-pm-chip{ padding: 0 16px; font-size: 12px; }
}


/* ---------------------------------------------- the net delta chip --

   Beside the book's total, in the same three colours the histogram below uses
   - so the column at the right edge of the pane and the chip in the heading
   are visibly the same reading, one as a shape over time and one as a number
   now. Sharing the palette is the whole point; two different greens for one
   quantity would be worse than no colour at all. */
/* ------------------------------------------- the overlay switchboard --

   CPR | PDH PDL | EMA 50 200, in the bar after the interval and the chart
   style. The group box exists for one reason: it makes the row wrap at a
   group boundary and nowhere else. Without it the flex row broke between
   "50" and "200" on a phone, which put a group separator at the end of a
   line and the rest of that group on the next - the punctuation contradicting
   the layout. */
.tow-pm-overlay-group{
    display: inline-flex;
    gap: 4px;
    flex: none;
}

/* The chips are wider than the interval's "1m", so the row is allowed to
   wrap here rather than forcing the bar to scroll. */
#pm-overlay-chips{ flex-wrap: wrap; row-gap: 6px; }


/* Load sits at the right end of whatever line it lands on.

   Adding the overlay switchboard made the bar wider than the container, so
   Load wrapped to a second line and sat at the LEFT of it, under Portfolio -
   which reads as a stray button rather than the end of a row of controls.
   An auto margin puts it at the far right either way: on one line when the
   bar fits, on its own line when it does not, and in both cases at the end
   of the row rather than the start of one. */
.tow-pm-bar-row #pm-config-save-btn{ margin-left: auto; }


/* ------------------------------------------- the second bar row --------

   The indicators sit on their own line under the pull-and-draw controls. It
   is set apart rather than merely below: a hairline above it, and a step down
   in weight, so the eye reads two groups rather than one long wrapped row.
   Without the rule the second row looked like the first row having overflowed,
   which is the impression the split exists to remove. */
.tow-pm-bar-row.is-overlays{
    margin-top: 2px;
    margin-bottom: 0;
    padding-top: 11px;
    border-top: 1px solid var(--tow-edge);
}

/* No flex override on the field here. `flex: none` was tried and it was both
   pointless and harmful: the base .tow-pm-bar-field sets no flex at all, so it
   changed nothing on a wide screen, while on a phone it beat the small-screen
   `flex: 1 1 100%` that gives the field the full row - and the chips then ran
   past the right edge of the panel and were clipped, with "200" half gone.
   The row inherits the same wrapping every other bar row has. */


/* ------------------------------------------ the menu, by thumb --------

   Same treatment the bar's controls got, for the same reason and with the
   same number: the menu rows measured 214x36, the gear that opens it 34x34,
   and the theme toggle 40x22. All clear WCAG 2.2 AA's 24px floor and none of
   them is comfortable to hit.

   The toggle is the interesting one. Making the switch itself 44px would draw
   a huge lozenge for a control whose whole job is to look like a small
   switch, so the TRACK keeps its 40x22 and the hit area is grown around it
   with padding plus a negative margin - the target is 44 tall, the paint is
   not. background-clip keeps the tinted track from bleeding into the padding.

   The menu grows by about 40px in total, which it has room for: it is capped
   at min(70vh, 480px) and scrolls. */
@media (max-width: 640px){

    .menu-opt-btn{
        display: flex;
        align-items: center;
        min-height: 44px;
    }

    /* The gear. It sits in the top bar's corner, which is the least accurate
       place on the screen for a thumb. */
    #menuBtn{
        width: 44px !important;
        height: 44px !important;
    }

    #theme-switch-row{ min-height: 44px; }

    /* A HIT AREA, NOT A BIGGER SWITCH.

       Padding was tried first and was the wrong tool twice over: `.menu
       button { padding: .6rem 1rem }` is more specific than `.theme-switch`,
       so the horizontal half never applied, and growing the box at all
       changes what the row lays out around.

       An absolutely positioned pseudo-element takes no part in layout and
       paints nothing. The switch is already `position: relative`, so this
       centres a 44x44 catcher over a 22px control and leaves the drawing
       exactly as it was. */
    .theme-switch::after{
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
    }
}


/* ------------------------------------------- the Custom chip ----------

   Same shape as an instrument chip because it sits in the same row, but it
   is not one: an unselected instrument and an action look identical in the
   outlined state, and "Custom" is never selected - it opens a dialog.

   A dashed edge is the difference. It reads as "not one of the fixed set"
   rather than "one of the set, currently off", and it needs no colour, which
   keeps the mint reserved for what is actually in the book. */
.tow-pm-chip.is-action{
    border-style: dashed;
}

.tow-pm-chip.is-action:hover{
    border-style: dashed;
    border-color: rgb(var(--c-accent) / .55);
    color: rgb(var(--c-ink-200));
}


/* ------------------------------------------ the custom book editor ----

   One panel per instrument - the "inner chunk". Boxed rather than laid out
   as a bare table because each line is a single instrument's whole
   definition, and a box says that more plainly than alignment does; on a
   phone the cells stack inside the box and the grouping still holds. */
.tow-pm-custom-list{
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 14px;
}

/* A GRID, NOT A FLEX ROW.

   Four cells on one line was tried and does not survive the two that carry a
   side button as well as an input: giving those a bigger flex-basis pushed
   the total past the row and it wrapped anyway, in the wrong place - Call
   ended up alone on the first line and Put stretched across the second.

   Two by two is stable at every width, and it pairs the way the fields read:
   what the instrument IS on the top line, what is written on it below. The
   remove button sits in a column of its own spanning both. */
.tow-pm-custom-row{
    display: grid;
    /* One field per line, always. The row used to spread across six tracks
       when the dialog was a single column wide; it now sits inside one of two
       columns and carries three legs rather than two, so stacking is what
       fits - and it reads down the page in the order the position is held. */
    grid-template-columns: 1fr auto;
    gap: 10px 12px;
    align-items: end;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid var(--tow-edge);
    background: rgb(var(--c-surface-sunken) / .5);
}

/* Every cell is a line of its own. */
.tow-pm-custom-cell{
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-width: 0;
    grid-column: 1;
}

/* A LEG TAKES THE WHOLE LINE.

   It carries four controls - side, strike, quantity, average price - and two
   of them hold prices. The strike and the average both need room for five
   digits and a decimal, and side by side one or the other clipped. */
.tow-pm-custom-cell.is-leg{ grid-column: 1; }

/* The same mono-uppercase label the bar uses, so the two read as one page. */
.tow-pm-custom-cell > span{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-500));
}

.tow-pm-custom-cell input,
.tow-pm-custom-cell select{
    width: 100%;
    height: 34px;
    min-width: 0;
    padding: 0 9px;
    font: inherit;
    font-size: 12px;
    color: rgb(var(--c-ink-100));
    background: rgb(var(--c-surface-input));
    border: 1px solid var(--tow-edge);
    border-radius: 6px;
}

.tow-pm-custom-cell input:focus-visible,
.tow-pm-custom-cell select:focus-visible{
    outline: none;
    border-color: rgb(var(--c-accent));
    box-shadow: 0 0 0 1px rgb(var(--c-accent));
}

/* Destructive, so it is not given the mint every other control here wears.
   Sized to the 44px rule on a phone by the media block further down. */
.tow-pm-custom-del{
    grid-column: 2;
    /* Instrument, Future, and however many option legs the row has grown -
       there is no fixed count of these any more, so this reaches the last
       implicit row rather than naming a span that a third or fourth leg
       would run past. */
    grid-row: 1 / -1;
    align-self: stretch;
    width: 34px;
    min-width: 0;
    padding: 0;
    font-size: 16px;
    line-height: 1;
    color: rgb(var(--c-ink-500));
    background: transparent;
    border: 1px solid var(--tow-edge);
    border-radius: 6px;
    cursor: pointer;
}

.tow-pm-custom-del:hover{
    color: rgb(var(--c-bear));
    border-color: rgb(var(--c-bear) / .55);
    background: rgb(var(--c-bear) / .08);
}

.tow-pm-custom-empty{
    margin: 0;
    font-size: 12px;
    line-height: 1.6;
    color: rgb(var(--c-ink-500));
}

@media (max-width: 640px){
    /* One field per line. Two columns of roughly 140px cannot hold a 52px
       side button, a gap and a five-digit strike without clipping it again.

       Every cell is placed in column 1 EXPLICITLY. Narrowing the template
       alone was not enough: the remove button kept the `grid-column: 3` it
       carries on a wide screen, which no longer existed, and auto-placement
       then put pairs of cells into the same area - the Instrument select drew
       on top of the Expiry input, and Call on top of Put. */
    .tow-pm-custom-row{ grid-template-columns: 1fr auto; }
    .tow-pm-custom-cell,
    .tow-pm-custom-cell.is-leg{ grid-column: 1; }
    .tow-pm-custom-del{ grid-column: 2; grid-row: 1 / -1; width: 44px; }

    .tow-pm-custom-cell input,
    .tow-pm-custom-cell select{ height: 44px; }
}


/* -------------------------------------------- buy / sell on a strike --

   Bull and bear, the same two colours the books and the candles use, so the
   side of a hand-written leg reads the way the side of everything else on the
   page does. Filled rather than outlined: this is the one field in the row
   whose value flips the sign of the P&L, and an outline is what the rest of
   the form wears. */
.tow-pm-custom-leg{
    display: flex;
    align-items: stretch;
    gap: 6px;
    min-width: 0;
}

/* The strike carries five digits, the average a price with decimals, the
   quantity usually one. Weighted rather than equal so none has to be read out
   of a box it does not fit. */
.tow-pm-custom-leg input.is-strike{ flex: 3 1 0; min-width: 0; }
.tow-pm-custom-leg input.is-qty{ flex: 1 1 0; min-width: 0; }
.tow-pm-custom-leg input.is-avg{ flex: 3 1 0; min-width: 0; }

/* Stands in place of the option's leg until it has one - see opt_untouched
   in pm_custom_row_html. Same 34px as the inputs beside it, dashed rather
   than solid so it reads as "nothing here yet" rather than a filled field. */
.tow-pm-custom-add-opt{
    width: 100%;
    height: 34px;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    color: rgb(var(--c-ink-500));
    background: transparent;
    border: 1px dashed var(--tow-edge);
    border-radius: 6px;
    cursor: pointer;
}

.tow-pm-custom-add-opt:hover{
    color: rgb(var(--c-accent));
    border-color: rgb(var(--c-accent));
}

/* Punctuation between the two, so the leg reads "sell 24050 x 2". */
.tow-pm-custom-x{
    flex: none;
    align-self: center;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-600));
    user-select: none;
    white-space: nowrap;
}

.tow-pm-side{
    flex: none;
    width: 52px;
    /* tug-of-war.css puts min-width: 60px on every button, which beat the
       width above and stole eight pixels from the strike beside it. */
    min-width: 0;
    padding: 0;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: .08em;
    line-height: 1;
    border-radius: 6px;
    border: 1px solid transparent;
    cursor: pointer;
    transition: background .14s var(--tow-ease), color .14s var(--tow-ease);
}

.tow-pm-side.is-buy{
    color: rgb(var(--c-bull));
    border-color: rgb(var(--c-bull) / .5);
    background: rgb(var(--c-bull) / .14);
}

.tow-pm-side.is-sell{
    color: rgb(var(--c-bear));
    border-color: rgb(var(--c-bear) / .5);
    background: rgb(var(--c-bear) / .14);
}

.tow-pm-side:hover{ filter: brightness(1.18); }

.tow-pm-side:focus-visible{
    outline: none;
    box-shadow: 0 0 0 2px rgb(var(--c-accent) / .6);
}

@media (max-width: 640px){
    /* The 44px rule applies to it too - it is the smallest control in the row
       and the one that must not be hit by accident. */
    .tow-pm-side{ width: 56px; min-height: 44px; }
}


/* The one date these rows answer to, named where they are edited. */
.tow-pm-custom-ref{
    margin: 0 0 14px;
    font-size: 12px;
    line-height: 1.6;
    color: rgb(var(--c-ink-500));
}

.tow-pm-custom-ref b{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-weight: 600;
    color: rgb(var(--c-ink-200));
}


/* ------------------------------- the leg, on a phone -------------------

   Appended AFTER the base .tow-pm-custom-leg rule on purpose. The first
   attempt put these inside the editor's own @media block, which sits earlier
   in this file than the flex rule the buy/sell work added later - equal
   specificity, so the later rule won and the grid never applied. Nothing was
   wrong with the rules; they were simply in the wrong place.

   FOUR CONTROLS WILL NOT SIT ON ONE 255px LINE, AND FLEX WILL NOT SPLIT THEM
   WHERE IT SHOULD. flex-wrap with a 100% basis on the strike was tried too:
   flex items shrink below their basis by default, so nothing wrapped and the
   average price rendered as 185.5 - its last digit cut off in a box 8px too
   narrow.

   A grid says exactly where each control goes:

       [SELL] [       24050       ]
              x [ 2 ]  @ [ 185.50 ]                                        */
@media (max-width: 640px){
    .tow-pm-custom-leg{
        display: grid;
        /* The quantity column carries the word QTY beside it now, which is
           wider than the x it replaced and left the field a pixel short of
           its own content. Weighted so a five-figure quantity still fits. */
        grid-template-columns: auto auto 1.35fr auto 1.5fr;
        gap: 6px;
        align-items: center;
    }

    /* Stretched, not centred: it anchors the two lines it belongs to, and a
       button the height of the whole leg is a better target than one floating
       in the middle of it. */
    .tow-pm-custom-leg .tow-pm-side{
        grid-row: 1 / span 2;
        grid-column: 1;
        align-self: stretch;
        width: 56px;
    }
    .tow-pm-custom-leg input.is-strike{ grid-row: 1; grid-column: 2 / span 4; }
    .tow-pm-custom-leg .is-times{ grid-row: 2; grid-column: 2; }
    .tow-pm-custom-leg input.is-qty{ grid-row: 2; grid-column: 3; }
    .tow-pm-custom-leg .is-at{ grid-row: 2; grid-column: 4; }
    .tow-pm-custom-leg input.is-avg{ grid-row: 2; grid-column: 5; }
}


/* The unit count beside a leg's label. Quieter than the label it follows -
   it is the consequence of two fields, not a heading of its own. */
.tow-pm-custom-units{
    margin-left: 6px;
    font-weight: 500;
    letter-spacing: .08em;
    color: rgb(var(--c-ink-600));
    text-transform: none;
    cursor: help;
}





/* ---------------------------------------------- the two columns ---------

   One per chart. A row's column is the entirety of its answer to "which
   chart" - there is no control for it, which is the point: a row cannot be
   written under one heading and drawn under the other. */
.tow-pm-custom-cols{
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
    align-items: start;
}

.tow-pm-custom-col{ min-width: 0; }

.tow-pm-custom-col-head{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin: 0 0 10px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--tow-edge);
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-300));
}

.tow-pm-custom-col-head span{
    font-size: 9px;
    letter-spacing: .1em;
    color: rgb(var(--c-ink-600));
}

/* The empty slot standing in for the strike the future does not have. Same
   flex as .is-strike, so the quantity and the price below it land in the same
   columns as the two option legs' - which is the whole reason it exists. */
/* The same box as the input it stands in for, padding and border included:
   flex-basis 0 distributes the CONTENT width, so a slot without the input's
   9px padding and 1px border grows to a different size and slides the
   quantity and the price out of line with the option legs by twelve pixels. */
.tow-pm-custom-gap{
    flex: 3 1 0;
    min-width: 0;
    padding: 0 9px;
    border: 1px solid transparent;
}

@media (max-width: 640px){
    /* Stacked: two columns of a 360px screen is 160px each, which is narrower
       than a single leg. One column at a time, headings and all. */
    .tow-pm-custom-cols{ grid-template-columns: 1fr; gap: 22px; }

    /* On a phone the option leg wraps onto two lines because four controls
       will not fit on one. The future has three and they do, so it stays on
       a single line - and the empty strike slot, which would otherwise hold
       open a blank first line of its own, is taken out of the grid. */
    .tow-pm-custom-gap{ display: none; }

    .tow-pm-custom-leg.is-nostrike{
        grid-template-columns: auto auto 1fr auto 1.6fr;
    }
    .tow-pm-custom-leg.is-nostrike .tow-pm-side{ grid-row: 1; grid-column: 1; }
    .tow-pm-custom-leg.is-nostrike .is-times{ grid-row: 1; grid-column: 2; }
    .tow-pm-custom-leg.is-nostrike input.is-qty{ grid-row: 1; grid-column: 3; }
    .tow-pm-custom-leg.is-nostrike .is-at{ grid-row: 1; grid-column: 4; }
    .tow-pm-custom-leg.is-nostrike input.is-avg{ grid-row: 1; grid-column: 5; }
}


/* -------------------------------------------- call or put on a row ------

   The same shape and weight as the side button it sits beside, because it is
   the same kind of control - a state that flips, not an action. Deliberately
   NOT bull/bear coloured: green and red already mean bought and sold on the
   button next to it, and a call is neither. */
.tow-pm-right{
    flex: none;
    width: 40px;
    /* tug-of-war.css puts min-width: 60px on every button, which would beat
       the width above - the same trap .tow-pm-side had to escape. */
    min-width: 0;
    padding: 0;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: .08em;
    line-height: 1;
    color: rgb(var(--c-ink-300));
    background: rgb(var(--c-surface-input));
    border: 1px solid var(--tow-edge);
    border-radius: 6px;
    cursor: pointer;
    transition: color .14s var(--tow-ease), border-color .14s var(--tow-ease);
}

.tow-pm-right:hover{
    color: rgb(var(--c-ink-100));
    border-color: rgb(var(--c-accent) / .45);
}

.tow-pm-right:focus-visible{
    outline: 2px solid rgb(var(--c-accent) / .6);
    outline-offset: 1px;
}

/* The future has no call-or-put button, so its whole line is indented by
   exactly what that button and its gap take - 40px and 6px. Two things come
   of it: the quantity and the price stay in the columns the option leg puts
   them in, which is the only reason the empty strike slot exists at all; and
   the future's BUY lands under the option's SELL rather than under its CE,
   so the coloured buttons read as one column of sides. */
.tow-pm-custom-leg.is-nostrike .tow-pm-side{
    margin-left: 46px;
}

@media (max-width: 640px){
    /* THE OPTION LEG ON A PHONE: TWO LINES, AND NOTHING SPANS THEM.

       The side button used to stretch across both rows as an anchor. With a
       call-or-put button beside it that anchor cost 100px of the second line
       - 44 and 56 - and there is only 233px to spend, so the average price
       was rendering as "185." with its last digit outside the box.

       Both buttons sit on the first line now, with the strike. The second
       line starts back at the left edge and has the whole width for the
       quantity and the price:

           [CE] [SELL] [    24050    ]
           QTY  [ 130 ]  @ [ 185.50 ]

       Four tracks. The second is a fr rather than auto so the quantity box
       does not collapse to a number input's intrinsic width; its floor is the
       56px side button sharing the track above it. */
    .tow-pm-custom-leg{
        grid-template-columns: auto 1fr auto 1.5fr;
    }
    .tow-pm-custom-leg .tow-pm-right{
        grid-row: 1;
        grid-column: 1;
        align-self: stretch;
        width: 44px;
    }
    .tow-pm-custom-leg .tow-pm-side{
        grid-row: 1;
        grid-column: 2;
        justify-self: start;
    }
    .tow-pm-custom-leg input.is-strike{ grid-row: 1; grid-column: 3 / span 2; }

    /* Hard against the box it labels, rather than adrift in a track sized by
       the 44px button above it. */
    .tow-pm-custom-leg .is-times{ grid-row: 2; grid-column: 1; justify-self: end; }
    .tow-pm-custom-leg input.is-qty{ grid-row: 2; grid-column: 2; }
    .tow-pm-custom-leg .is-at{ grid-row: 2; grid-column: 3; }
    .tow-pm-custom-leg input.is-avg{ grid-row: 2; grid-column: 4; }

    /* On a phone the option leg's right button sits in a track of its own, so
       the flex indent has nothing to line up with and would only push the
       future's side button off toward the middle of the row. */
    .tow-pm-custom-leg.is-nostrike .tow-pm-side{ margin-left: 0; }
}


/* The one line a side shows when it is holding nothing. Quiet - it is an
   explanation for a blank panel, not a result to be read. */
.tow-pm-empty td{
    padding: 14px 2px;
    font-size: 12px;
    line-height: 1.6;
    color: rgb(var(--c-ink-600));
}


/* ------------------------------------------- the custom dialog's receipt --

   Left of the Done button, in the footer. Empty and invisible until there is
   something to say, so the dialog is not carrying a blank reserved strip
   around with it. */
.tow-pm-custom-status{
    flex: 1 1 240px;
    min-width: 0;
    margin: 0;
    font-size: 12px;
    line-height: 1.5;
    color: rgb(var(--c-ink-500));
}

.tow-pm-custom-status.is-ok{ color: rgb(var(--c-bull)); }
.tow-pm-custom-status.is-bad{ color: rgb(var(--c-bear)); }

/* The beat between the click and the answer. The word shortens to "Saving"
   and the ring turns beside it - the button keeps its width either way, so
   the footer does not jump as it changes. */
#pm-custom-done{ min-width: 96px; }

#pm-custom-done.is-busy::after{
    content: '';
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-left: 8px;
    vertical-align: -1px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: tow-spin .6s linear infinite;
}

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

/* A ring that never closes is worse than no ring for anyone who cannot see
   it move; the disabled state and the changed label already say "busy". */
@media (prefers-reduced-motion: reduce){
    #pm-custom-done.is-busy::after{ animation: none; opacity: .5; }
}

@media (max-width: 640px){
    /* The message takes the line above the button rather than squeezing in
       beside it on a 375px footer. */
    .tow-pm-custom-status{ flex: 1 1 100%; order: -1; }
}


/* ------------------------------------------- the reference-price fill ---

   Under both columns, on a rule of its own: it acts on every row at once, so
   it belongs to neither column. */
.tow-pm-custom-fill{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 10px;
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--tow-edge);
}

.tow-pm-custom-fill-label{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-500));
    white-space: nowrap;
}

/* The same box as every other input in the dialog. Date and time are given
   widths because their intrinsic ones vary by browser and the pair should
   read as one control. */
.tow-pm-custom-fill input{
    height: 34px;
    min-width: 0;
    padding: 0 9px;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    color: rgb(var(--c-ink-100));
    background: rgb(var(--c-surface-input));
    border: 1px solid var(--tow-edge);
    border-radius: 6px;
    color-scheme: dark;
}
.tow-pm-custom-fill input[type="date"]{ width: 150px; }
.tow-pm-custom-fill input[type="time"]{ width: 104px; }
:root[data-theme='light'] .tow-pm-custom-fill input{ color-scheme: light; }

#pm-custom-autofill{ min-width: 168px; }

/* How many the button will touch, on the button. Empty - and so invisible -
   when there is nothing to fill, rather than a zero to read. */
.tow-pm-custom-fill-count:not(:empty){
    display: inline-block;
    min-width: 18px;
    margin-left: 8px;
    padding: 2px 6px;
    font-size: 9px;
    line-height: 1;
    text-align: center;
    color: rgb(var(--c-accent));
    background: rgb(var(--c-accent) / .14);
    border-radius: 9px;
}

/* The report. On its own line under the controls, since a fill of four legs
   names four prices and that is a sentence, not a badge. */
.tow-pm-custom-fill-note{
    flex: 1 1 100%;
    margin: 0;
    font-size: 12px;
    line-height: 1.6;
    color: rgb(var(--c-ink-500));
}
.tow-pm-custom-fill-note:empty{ display: none; }
.tow-pm-custom-fill-note.is-ok{ color: rgb(var(--c-bull)); }
.tow-pm-custom-fill-note.is-warn{ color: rgb(var(--c-ink-300)); }
.tow-pm-custom-fill-note.is-bad{ color: rgb(var(--c-bear)); }

@media (max-width: 640px){
    .tow-pm-custom-fill-label{ flex: 1 1 100%; }
    .tow-pm-custom-fill input[type="date"]{ flex: 1 1 0; width: auto; height: 44px; }
    .tow-pm-custom-fill input[type="time"]{ flex: 0 1 112px; width: auto; height: 44px; }
    #pm-custom-autofill{ flex: 1 1 100%; height: 44px; }
}


/* A blank price that is waiting on a mark. The placeholder already says
   "at 10:07"; the dashed edge says it is a promise rather than a number, in
   the same language the Add button uses for "not yet". */
.tow-pm-custom-leg input.is-avg.is-marked{
    border-style: dashed;
    border-color: rgb(var(--c-accent) / .55);
}
.tow-pm-custom-leg input.is-avg.is-marked::placeholder{
    color: rgb(var(--c-accent));
    opacity: .9;
}


/* ----------------------------------------------- no future on this row --

   N/A is deliberately NOT bull or bear: green and red mean bought and sold
   on the button beside it, and the absence of a leg is neither. The same
   quiet ink the CE/PE switch wears, so the two neutral controls in a row
   read as the same kind of thing. */
/* AN OPTION THAT IS NOT THERE, said the same way the future says it.

   The right button had no per-state colour at all - CE and PE looked alike,
   which was fine while those were the only two answers. N/A has to read as
   OFF at a glance or the row looks like it holds a call it does not, so it
   takes the same muted treatment .tow-pm-side.is-na does below. */
.tow-pm-right.is-na{
    color: rgb(var(--c-ink-500));
    border-style: dashed;
}

.tow-pm-right.is-na:hover{
    color: rgb(var(--c-ink-200));
    border-color: rgb(var(--c-accent) / .45);
}

.tow-pm-side.is-na{
    color: rgb(var(--c-ink-400));
    border-color: var(--tow-edge);
    background: rgb(var(--c-surface-input));
}
.tow-pm-side.is-na:hover{
    color: rgb(var(--c-ink-200));
    border-color: rgb(var(--c-accent) / .45);
}

/* The fields of a future that is not there. Dimmed rather than hidden: the
   line keeps its place so the row does not reflow when the side comes back,
   and a dimmed box with a number in it says the number is kept. */
.tow-pm-custom-leg.is-off input,
.tow-pm-custom-leg.is-off .tow-pm-custom-x{
    opacity: .38;
}
.tow-pm-custom-leg.is-off input{ cursor: not-allowed; }


/* Why a pasted credential was not accepted. Invisible until there is
   something to say, so the dialog does not carry a blank reserved line. */
.tow-cred-status{
    margin: 10px 0 0;
    font-size: 12px;
    line-height: 1.6;
    color: rgb(var(--c-ink-500));
}
.tow-cred-status:empty{ display: none; }
.tow-cred-status.is-bad{ color: rgb(var(--c-bear)); }


/* ------------------------------------------- Smart Portfolio Selection ---

   A picker, a bar of actions, and a table of what was built. The table is the
   point: two sides per instrument, side by side, so the mirror is visible
   rather than described. */
.tow-pm-smart-lead{
    margin: 0 0 10px;
    font-size: 12px;
    line-height: 1.65;
    color: rgb(var(--c-ink-400));
}
.tow-pm-smart-lead b{ color: rgb(var(--c-ink-200)); font-weight: 600; }
.tow-pm-smart-lead.is-quiet{ color: rgb(var(--c-ink-600)); }

.tow-pm-smart-ticks{
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 14px 0 12px;
    padding-top: 12px;
    border-top: 1px solid var(--tow-edge);
}

/* The tick is the whole chip, not a box beside a word - a 12px checkbox is a
   poor target and the label is already the thing being aimed at. */
.tow-pm-smart-tick{
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 32px;
    padding: 0 10px;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .06em;
    color: rgb(var(--c-ink-500));
    background: rgb(var(--c-surface-input));
    border: 1px solid var(--tow-edge);
    border-radius: 999px;
    cursor: pointer;
    user-select: none;
}
.tow-pm-smart-tick:hover{ color: rgb(var(--c-ink-200)); border-color: rgb(var(--c-accent) / .45); }
.tow-pm-smart-tick.is-on{
    color: rgb(var(--c-accent));
    border-color: rgb(var(--c-accent) / .55);
    background: rgb(var(--c-accent) / .12);
}
.tow-pm-smart-tick input{ width: 12px; height: 12px; margin: 0; accent-color: rgb(var(--c-accent)); }

.tow-pm-smart-bar{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
}

.tow-pm-smart-note{
    flex: 1 1 100%;
    margin: 0;
    font-size: 12px;
    line-height: 1.6;
    color: rgb(var(--c-ink-500));
}
.tow-pm-smart-note:empty{ display: none; }
.tow-pm-smart-note.is-ok{ color: rgb(var(--c-bull)); }
.tow-pm-smart-note.is-warn{ color: rgb(var(--c-ink-300)); }
.tow-pm-smart-note.is-bad{ color: rgb(var(--c-bear)); }

/* Its own scroller: seven columns will not fit a phone, and the dialog body
   must not scroll sideways as a whole. */
.tow-pm-smart-wrap{ overflow-x: auto; border-top: 1px solid var(--tow-edge); }

.tow-pm-smart-table{
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
    white-space: nowrap;
}
.tow-pm-smart-table th,
.tow-pm-smart-table td{
    padding: 7px 10px;
    text-align: left;
    border-bottom: 1px solid var(--tow-edge);
}
.tow-pm-smart-table thead th{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-500));
}

/* The two side headings carry the only colour in the head, because which
   column is which is the one thing worth finding at a glance. */
.tow-pm-smart-sides th.is-up{ color: rgb(var(--c-bull)); border-left: 1px solid var(--tow-edge); }
.tow-pm-smart-sides th.is-down{ color: rgb(var(--c-bear)); border-left: 1px solid var(--tow-edge); }
.tow-pm-smart-table tbody td:nth-child(4),
.tow-pm-smart-table tbody td:nth-child(6),
.tow-pm-smart-table thead tr:last-child th:nth-child(4),
.tow-pm-smart-table thead tr:last-child th:nth-child(6){ border-left: 1px solid var(--tow-edge); }

.tow-pm-smart-name{ font-weight: 600; color: rgb(var(--c-ink-100)); }
.tow-pm-smart-exp,
.tow-pm-smart-table .tow-pm-num{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    color: rgb(var(--c-ink-300));
}
.tow-pm-smart-table .tow-pm-num{ text-align: right; }

/* The same two colours the custom editor uses for a side, so BUY and SELL
   mean the same thing in both dialogs. */
.tow-pm-smart-side{
    display: inline-block;
    min-width: 34px;
    padding: 1px 5px;
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: .06em;
    text-align: center;
    border-radius: 4px;
}
.tow-pm-smart-side.is-buy{ color: rgb(var(--c-bull)); background: rgb(var(--c-bull) / .14); }
.tow-pm-smart-side.is-sell{ color: rgb(var(--c-bear)); background: rgb(var(--c-bear) / .14); }
.tow-pm-smart-none{ color: rgb(var(--c-ink-600)); font-style: italic; }

.tow-pm-smart-skip{
    margin: 10px 0 0;
    font-size: 12px;
    line-height: 1.6;
    color: rgb(var(--c-ink-500));
}

#pm-smart-build.is-busy::after{
    content: '';
    display: inline-block;
    width: 11px; height: 11px;
    margin-left: 7px;
    vertical-align: -1px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: tow-spin .6s linear infinite;
}
@media (prefers-reduced-motion: reduce){
    #pm-smart-build.is-busy::after{ animation: none; opacity: .5; }
}

@media (max-width: 640px){
    .tow-pm-smart-tick{ min-height: 40px; }
    .tow-pm-smart-bar .tow-pm-chip{ flex: 1 1 auto; min-height: 40px; }
}





/* ------------------------------------ the age of a stored build -------

   Sits between the controls and the table. Quiet when the build is against
   the latest session; warned when it is not, because a table of strikes from
   a week ago looks exactly as authoritative as one from this morning.     */
.tow-pm-smart-age{
    margin: 0 0 10px;
    font-size: 11px;
    line-height: 1.55;
    color: rgb(var(--c-ink-500));
}

.tow-pm-smart-age.is-stale{
    padding: 8px 10px;
    border: 1px solid rgb(var(--c-warn) / .45);
    border-radius: 6px;
    background: rgb(var(--c-warn) / .08);
    color: rgb(var(--c-warn));
}


/* ----------------------------------- the side groups in the table -----

   The old header carried two column groups side by side, because a row
   described both sides at once. A row is now on ONE side, so the grouping
   moved into the body: a banded header row per side, with what that side
   holds spelled out beside it rather than left to the column titles.     */

.tow-pm-smart-group th{
    padding: 10px 10px 6px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    text-align: left;
    border-bottom: 1px solid rgb(var(--c-edge) / .7);
    background: rgb(var(--c-surface-sunken) / .6);
}

.tow-pm-smart-group.is-up th   { color: rgb(var(--c-bull)); }
.tow-pm-smart-group.is-down th { color: rgb(var(--c-bear)); }

/* The explanation beside the title is not the title. Same line, plainly
   lighter, and not shouting in capitals with it. */
.tow-pm-smart-group th em{
    font-style: normal;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: rgb(var(--c-ink-500));
}

/* The measurement the split was made on. Monospaced with the other figures
   so a column of percentages lines up, and quiet, because it is an input to
   a decision rather than a verdict. */
.tow-pm-smart-pos{
    color: rgb(var(--c-ink-400));
    cursor: help;
}





/* ------------------------------------------- the swing line -----------

   One line under each book's heading: the most this side has been up, the
   most it has been down, and where between the two it stands now. Three
   parts on one row, wrapping to two on a narrow screen rather than being
   truncated - every one of them is a number somebody came to read.       */

.tow-pm-swing{
    display: flex;
    flex-wrap: wrap;
    gap: 4px 18px;
    margin: -4px 0 12px;
    font-size: 11px;
    line-height: 1.5;
    color: rgb(var(--c-ink-500));
}

.tow-pm-swing[hidden]{ display: none; }

.tow-pm-swing-part{
    white-space: nowrap;
}

/* The figure carries the weight; the label and the timestamp sit back from
   it. Neither is coloured up or down: which end is which is already said in
   the word beside it, and tinting them would turn a measurement into a
   scoreboard. */
.tow-pm-swing-part b{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: rgb(var(--c-ink-200));
}

.tow-pm-swing-part em{
    font-style: normal;
    color: rgb(var(--c-ink-600));
}


/* ---------------------------------------------- the range meters ------

   Two dials on the charts' own grid, between the charts and the books.      */

.tow-pm-meters{
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    margin-bottom: 16px;
}

@media (max-width: 900px){
    .tow-pm-meters{ grid-template-columns: 1fr; }
}

.tow-pm-meter{
    border: 1px solid var(--tow-edge);
    border-radius: 8px;
    background: rgb(var(--c-surface-sunken) / .35);
    padding: 10px 10px 4px;
    min-width: 0;
    display: flex;
    justify-content: center;

    /* The needle, the hub and the ticks are all currentColor, so the dial
       follows the theme by inheriting rather than by being told twice. */
    color: rgb(var(--c-ink-200));
}

.tow-pm-meter[hidden]{ display: none; }

/* THE GLOBAL svg RULE, UNDONE - one declaration at a time.

   tug-of-war.css carries `svg { position:absolute; width:100%; height:100%;
   pointer-events:none; z-index:10; top:0; left:0 }` for the tension diagram.
   Any inline SVG anywhere on the site inherits it, comes out of the flow and
   gets a height of 100% of nothing. Every one of those is reversed here, on
   this class, rather than by weakening the rule that another page depends
   on. */
.tow-pm-meter-face{
    position: static;
    top: auto;
    left: auto;
    z-index: auto;
    pointer-events: none;
    width: 100%;
    max-width: 250px;
    height: auto;
    overflow: visible;
}

.tow-pm-meter-num{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 21px;
    font-weight: 600;
    fill: rgb(var(--c-ink-strong));
}

.tow-pm-meter-zone{
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    fill: rgb(var(--c-ink-500));
}

.tow-pm-meter-tick{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
    fill: rgb(var(--c-ink-600));
}

.tow-pm-meter-cap{
    font-size: 9px;
    fill: rgb(var(--c-ink-600));
}


/* ------------------------------------------- relative rotation -----

   A graph and a table side by side on a wide screen, stacked on a narrow
   one. The instrument study that stood here went with its JavaScript.     */

.tow-pm-rrg{
    margin-top: 18px;
    border: 1px solid var(--tow-edge);
    border-radius: 8px;
    background: rgb(var(--c-surface-sunken) / .35);
    padding: 10px 10px 12px;
}

.tow-pm-rrg-bar{
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px 14px;
    margin-bottom: 10px;
}

.tow-pm-rrg-vs{
    font-size: 11px;
    color: rgb(var(--c-ink-600));
}

/* The timeframe and yardstick switches. Small, because the graph is the
   subject and these are how it is framed. */
.tow-pm-rrg-pick{
    height: 26px;
    padding: 0 8px;
    font: inherit;
    font-size: 11px;
    color: rgb(var(--c-ink-100));
    background: rgb(var(--c-surface-input));
    border: 1px solid var(--tow-edge);
    border-radius: 6px;
    cursor: pointer;
}

.tow-pm-rrg-pick:focus-visible{
    outline: none;
    border-color: rgb(var(--c-accent));
    box-shadow: 0 0 0 1px rgb(var(--c-accent));
}

.tow-pm-rrg-pick:disabled{ opacity: .55; cursor: default; }

.tow-pm-rrg-pull{
    font-weight: 600;
    color: rgb(var(--c-accent));
}

.tow-pm-rrg-pull:hover:not(:disabled){
    background: rgb(var(--c-accent) / .12);
}

.tow-pm-rrg-when{
    margin-left: auto;
    font-size: 11px;
    color: rgb(var(--c-ink-600));
}

.tow-pm-rrg-body{
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(320px, 1fr);
    gap: 14px;
    align-items: start;
}

@media (max-width: 900px){
    .tow-pm-rrg-body{ grid-template-columns: 1fr; }
}

.tow-pm-rrg-graph,
.tow-pm-rrg-side{ min-width: 0; }

/* SIX COLUMNS DO NOT FIT A PHONE, and the page must not scroll sideways
   because of them. The table scrolls inside its own box instead; on a
   desktop width it fits and no scrollbar appears. */
.tow-pm-rrg-side{ overflow-x: auto; }

/* THE GLOBAL SVG RULE, UNDONE DECLARATION BY DECLARATION.

   css/tug-of-war.css sets every svg to absolute, full-size, click-through
   and z-indexed, for the tug-of-war canvas. Inline, that would lift the graph
   out of its box and over the table. Each property it sets is set back here
   rather than trusting any one of them to be enough. */
.tow-pm-rrg-svg{
    position: static;
    display: block;
    width: 100%;
    height: auto;
    top: auto;
    left: auto;
    z-index: auto;
    pointer-events: auto;
}

.tow-pm-rrg-quad{
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.tow-pm-rrg-tick{ font-size: 9px; }
.tow-pm-rrg-axis{ font-size: 10px; letter-spacing: .04em; }
.tow-pm-rrg-empty{ font-size: 12px; }

.tow-pm-rrg-name{
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
    font-weight: 600;
}

.tow-pm-rrg-dot-circle{
    cursor: pointer;
    transition: r 0.1s ease;
}

/* The same global table trap the book table undoes: fixed layout and a
   900px minimum, which would push Status and the numbers off the side. */
.tow-pm-rrg-table{
    width: 100%;
    min-width: 0;
    table-layout: auto;
    font-size: 11px;
}

.tow-pm-rrg-table th,
.tow-pm-rrg-table td{ padding: 4px 8px; }

.tow-pm-rrg-table tbody td{ text-align: left; }
.tow-pm-rrg-table .tow-pm-num{ text-align: right; white-space: nowrap; }
.tow-pm-rrg-table th.tow-pm-num{ text-align: right; }

.tow-pm-rrg-table .is-tick{
    width: 28px;
    text-align: center;
}

.tow-pm-rrg-table input[type="checkbox"]{
    width: 14px;
    height: 14px;
    margin: 0;
    cursor: pointer;
    accent-color: rgb(var(--c-accent));
    vertical-align: middle;
}

/* An unticked stock is still a row with a current status, so it dims
   rather than disappears. */
.tow-pm-rrg-table tr.is-off td:not(.is-tick){ opacity: .5; }

.tow-pm-rrg-dot{
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: 7px;
    border-radius: 50%;
    vertical-align: middle;
}

.tow-pm-rrg-pill{
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 10.5px;
    font-weight: 600;
    white-space: nowrap;
}

.tow-pm-rrg-stale,
.tow-pm-rrg-wait{
    font-size: 10.5px;
    color: rgb(var(--c-ink-600));
}

.tow-pm-rrg-note{
    margin: 10px 0 0;
    font-size: 11px;
    line-height: 1.55;
    color: rgb(var(--c-ink-600));
}

/* ========================================================== PORTFOLIO BASKETS ===

   Save and load named portfolio snapshots. Max 5 baskets per user, stored
   locally so the user can switch between saved setups. */
.tow-pm-basket-controls{
    margin: 16px 0;
    padding: 12px;
    border: 1px solid rgb(var(--c-edge));
    border-radius: 8px;
    background: rgb(var(--c-bg-500));
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.tow-pm-basket-row{
    display: flex;
    gap: 8px;
    align-items: center;
}

#pm-basket-select{
    flex: 1;
    min-width: 0;
    padding: 8px 12px;
    border: 1px solid rgb(var(--c-edge-strong));
    border-radius: 6px;
    background: rgb(var(--c-bg));
    color: rgb(var(--c-ink-200));
    font-family: inherit;
    font-size: 13px;
    cursor: pointer;
}

#pm-basket-select:focus-visible{
    outline: none;
    box-shadow: 0 0 0 2px rgb(var(--c-accent) / .6);
}

#pm-basket-select option{
    background: rgb(var(--c-bg-600));
    color: rgb(var(--c-ink-200));
}

.tow-pm-basket-save{
    display: flex;
    gap: 8px;
    align-items: stretch;
}

#pm-basket-name{
    flex: 1;
    min-width: 0;
    padding: 8px 12px;
    border: 1px solid rgb(var(--c-edge-strong));
    border-radius: 6px;
    background: rgb(var(--c-bg));
    color: rgb(var(--c-ink-200));
    font-family: inherit;
    font-size: 13px;
}

#pm-basket-name:focus-visible{
    outline: none;
    box-shadow: 0 0 0 2px rgb(var(--c-accent) / .6);
}
