/* Hand-drawn SVG charts (views/charts.js) and the PnL panel recipe (.pn: header, curve, currencies, winners and
   losers), used by the PnL board (views/dash/pnl.js).
   One recipe at every size: straight segments between snapshots; a signed series split at zero (green above, red
   below); an area that fades from the line to the baseline; a dotted baseline; a ringed dot on "now". */

/* ------------------------------------------------------------------ the series
   Its colours are custom properties, so "stale" is one switch for line, area and dots. Marks use the *-line
   tokens (the text tokens are too dark for a 2px stroke in light mode and too pale in dark). */
.spark, .lc { --c-pos: var(--pos-line); --c-neg: var(--neg-line); --c-neu: var(--chart-neutral); }
.stale-2 .spark, .spark.stale, .stale-2 .lc, .lc.stale { --c-pos: var(--chart-neutral); --c-neg: var(--chart-neutral); }
.ch-pos { stop-color: var(--c-pos); }
.ch-neg { stop-color: var(--c-neg); }
.ch-neu { stop-color: var(--c-neu); }
.ch-a { stop-opacity: var(--chart-area-top); }
.ch-0 { stop-opacity: 0; }
/* a sparkline's area is only a few pixels tall, so the same gradient averages out to almost nothing: it starts
   stronger at the line (the optical correction that also makes its stroke 1.5px instead of 2px) */
.spark .ch-a { stop-opacity: calc(var(--chart-area-top) + .12); }
/* dots: the fill is the colour of their side of zero; the ring is the surface they sit on (painted under the fill,
   so a 3px stroke leaves a 1.5px ring outside the dot) */
.spark-dot, .lc-pt, .lc-end, .lc-cursor { fill: var(--c-neu); stroke: var(--ring-bg); paint-order: stroke; }
.spark-dot.up, .lc-pt.up, .lc-end.up, .lc-cursor.up, .lc-halo.up { fill: var(--c-pos); }
.spark-dot.dn, .lc-pt.dn, .lc-end.dn, .lc-cursor.dn, .lc-halo.dn { fill: var(--c-neg); }

/* ------------------------------------------------------------------ sparkline */
.spark { display: block; flex: none; overflow: visible; }
.spark-line { fill: none; stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.spark-area { stroke: none; }
.spark-zero, .lc-zero { stroke: var(--chart-baseline); stroke-width: 1; stroke-dasharray: 2 3; stroke-linecap: round; }
.spark-dot { stroke-width: 3; }
/* old data: the dot on "now" goes hollow */
.stale-2 .spark-dot, .spark.stale .spark-dot, .stale-2 .lc-end, .lc.stale .lc-end { fill: var(--ring-bg); stroke: var(--c-neu); stroke-width: 1.5; paint-order: normal; }

/* ------------------------------------------------------------------ line chart */
.lc { position: relative; height: var(--lc-h, 132px); -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.lc.compact { height: 44px; }
.lc-plot { position: absolute; inset: 0; border-radius: var(--radius-sm); touch-action: pan-y; }
.lc-plot.live { cursor: crosshair; }
/* the focus ring stands clear of the labels at the plot's edges instead of hugging them: one ring, on the
   pseudo-element (the global :focus-visible ring on the box itself would run through the labels) */
.lc-plot:focus-visible { outline: none; box-shadow: none; }
.lc-plot:focus-visible::after { content: ""; position: absolute; inset: -4px -6px; border-radius: var(--radius-sm); box-shadow: var(--ring); pointer-events: none; }
.lc-svg { display: block; overflow: visible; }
.lc-grid { stroke: var(--chart-grid); stroke-width: 1; shape-rendering: crispEdges; }
.lc-ylab, .lc-xlab, .lc-ext, .lc-note { font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
.lc-ylab, .lc-xlab, .lc-note { fill: var(--text-3); }
.lc-ylab { text-anchor: end; }
.lc-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.lc-area { stroke: none; }
.lc-pt { stroke-width: 3; }
.lc-end, .lc-cursor { stroke-width: 4; }
/* the day's high and low, and the caption beside a lone first point: a halo of the surface keeps them off the grid */
.lc-ext { font-weight: 600; fill: var(--text-2); }
.lc-ext, .lc-note { paint-order: stroke; stroke: var(--ring-bg); stroke-width: 3px; stroke-linejoin: round; }
/* "now" answers a new point with one halo */
.lc-halo { stroke: none; pointer-events: none; transform-box: fill-box; transform-origin: center; animation: lc-halo var(--t-tick) ease-out both; }
@keyframes lc-halo { from { opacity: .35; transform: scale(1); } to { opacity: 0; transform: scale(2.5); } }
/* scrubbing: a rule and a ringed dot on the nearest point, what lies right of it fades; the resting marks step aside.
   Arriving is instant, leaving takes 150ms. */
.lc-scrub { opacity: 0; pointer-events: none; transition: opacity 150ms ease-out; }
.lc-scrub.on { opacity: 1; transition: none; }
.lc-cross { stroke: var(--chart-crosshair); stroke-width: 1; shape-rendering: crispEdges; }
.lc-rest { transition: opacity 150ms ease-out; }
.lc-rest.on { opacity: .35; transition: none; }
.lc-quiet { transition: opacity 150ms ease-out; }
.lc.is-scrub .lc-quiet { opacity: 0; transition: none; }
/* first appearance: the line draws itself in once and the area follows; later data replaces the path at once */
.lc.is-in .lc-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: lc-draw var(--t-slow) var(--ease-smooth) forwards; }
.lc.is-in .lc-area, .lc.is-in .lc-pt, .lc.is-in .lc-quiet { animation: lc-fade var(--t-slow) var(--ease-smooth) both; }
@keyframes lc-draw { to { stroke-dashoffset: 0; } }
@keyframes lc-fade { from { opacity: 0; } }
/* what stands in an empty frame (the default slot) */
.lc-over { position: absolute; inset: 0 0 18px 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; padding: 0 12px; text-align: center; text-wrap: balance; pointer-events: none; }
.lc-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .lc.is-in .lc-line { stroke-dasharray: none; animation: none; }
  .lc.is-in .lc-area, .lc.is-in .lc-pt, .lc.is-in .lc-quiet { animation: none; }
  .lc-halo { display: none; }
}

/* ------------------------------------------------------------------ PnL panel */
.pn { container-type: inline-size; }
.pn-top { display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 8px; }
.pn-ctx { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); white-space: nowrap; }
.pn-top .delta { align-self: center; }
.pn-top .chip { align-self: center; }
.pn-top .fig-v.na { color: var(--text-4); }
/* leaving a scrub: the header's own figures come back with a short fade */
.pn-top.back > * { animation: pn-back var(--t-out) ease-out; }
@keyframes pn-back { from { opacity: .3; } }
.pn.is-scrub .pn-top .chip { visibility: hidden; }
/* the day's extremes in words; the line keeps its height when there is nothing to say, so the chart never moves.
   (keep-all: 「最低」 is never split; with the no-break spaces of pnlSummary a narrow card breaks only between the
   two parts) */
.pn-take { margin-top: 2px; min-height: var(--lh-md); font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-3); word-break: keep-all; }
.pn-chart { --lc-h: 132px; margin-top: 12px; }
.pn.compact .pn-chart { margin-top: 8px; }
/* data twice past its age limit: the figure and its change lose their colour, the curve goes grey (see .stale-2
   above) — and so does everything below them, which comes from the same old snapshot: the currency bars, the
   day's figures and their changes, the winners' and losers' grounds */
.pn.stale-2 .pn-top .fig-v { color: var(--text-2); }
.pn.stale-2 .pn-top .delta { background: var(--fill-2); color: var(--text-2); }
.pn.stale-2 .pn-bar i { background: var(--chart-neutral); }
.pn.stale-2 .pn-ccy > b, .pn.stale-2 .pn-d:not(.na), .pn.stale-2 .pn-pos > b { color: var(--text-2); }
.pn.stale-2 .pn-pos > b::before { background: var(--fill-2); }

/* section headings carry the column names, on the grid of the rows below them */
.pn-h { margin-top: 16px; }
.pn-h-t { font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; color: var(--text-2); white-space: nowrap; }
.pn-h-t small { font-size: var(--fs-xs); font-weight: 400; color: var(--text-3); }
/* (a heading without a title — the losers' column of a wide card — keeps the line of its neighbour, so the rows of
   both columns stay level) */
.pn-h-t:empty::before { content: "\200b"; }
.pn-h-c { font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: 500; color: var(--text-3); text-align: right; white-space: nowrap; }

/* by currency: bars that diverge from a shared zero axis */
.pn-ccy { display: grid; grid-template-columns: 2.8em minmax(0, 1fr) 5.4em 4.2em; align-items: center; gap: 8px; padding: 3px 0; font-size: var(--fs-md); line-height: var(--lh-md); }
.pn-h.pn-ccy, .pn-h.pn-pos { align-items: baseline; padding: 0 0 4px; }
.pn-h.pn-ccy > .pn-h-t { grid-column: 1 / 3; }
.pn-ccy-n { color: var(--text-2); font-weight: 500; }
.pn-ccy > b { text-align: right; font-weight: 600; }
.pn-bar { position: relative; height: 6px; border-radius: var(--pill); background: var(--fill-2); }
.pn-bar i { position: absolute; top: 0; bottom: 0; min-width: 3px; border-radius: var(--pill); transition: width var(--t-base) var(--ease), left var(--t-base) var(--ease), right var(--t-base) var(--ease); }
.pn-bar i.pos { background: var(--pos-line); }
.pn-bar i.neg { background: var(--neg-line); }
/* both signs present: square ends on the axis, and the axis itself as one rule through the rows */
.pn-ccys.two .pn-bar i.pos { border-radius: 0 var(--pill) var(--pill) 0; }
.pn-ccys.two .pn-bar i.neg { border-radius: var(--pill) 0 0 var(--pill); }
.pn-ccys.two .pn-bar::after { content: ""; position: absolute; left: var(--ax); top: -9px; bottom: -9px; width: 1px; margin-left: -.5px; background: var(--chart-baseline); }
.pn-d { text-align: right; font-size: var(--fs-xs); color: var(--text-3); }
.pn-d.pos { color: var(--pos); }
.pn-d.neg { color: var(--neg); }
.pn-d.na { color: var(--text-4); }

/* winners / losers / movers: name, move, PnL — the PnL sits on a soft bar as long as its share of the largest */
.pn-cols { display: flex; flex-direction: column; margin-top: 16px; }
.pn-cols .pn-h { margin-top: 0; }
.pn-cols > div + div { margin-top: 4px; padding-top: 4px; border-top: var(--hair) solid var(--hairline); }
.pn-cols > div + div > .pn-h { display: none; }
.pn-pos { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 4em 5.4em; align-items: baseline; gap: 8px; padding: 3px 0; font-size: var(--fs-md); line-height: var(--lh-md); }
.pn-pos.wide { grid-template-columns: minmax(0, 1fr) auto 5.4em; }
.pn-pos > * { position: relative; z-index: 1; }
/* (a name too long for its cell takes more lines rather than losing its end, where an A share and its H share
   differ: on a phone the cell is 154px — 132px beside today's trades' buy / sell mark, less beside a long move
   since the last update — and Enfusion's names run to 35 characters, "… ORD A", four lines on a 375px phone;
   text-wrap: pretty keeps a last word such as that "A" from standing on a line of its own where the browser can) */
.pn-pos > .ellipsis { white-space: normal; overflow-wrap: break-word; text-wrap: pretty; font-size: var(--fs-sm); letter-spacing: .02em; }
.pn-pos > i { font-style: normal; font-size: var(--fs-sm); color: var(--text-3); text-align: right; white-space: nowrap; }
.pn-pos > b { justify-self: end; font-weight: 600; text-align: right; }
/* The bar hangs on the figure itself (its box is exactly as wide as the text): as long as the figure's share of
   the PnL column and its bleed — so it never reaches the move beside it — but never shorter than the figure plus
   6px on each side, so its end can never fall inside a digit. It sits under the text and above the row's hover
   ground (the figure is its own stacking context). */
.pn-pos > b::before { content: ""; position: absolute; z-index: -1; top: 0; bottom: 0; right: -6px; width: calc(var(--m, 0) * (5.4em + 8px)); min-width: calc(100% + 12px); border-radius: var(--radius-xs); background: var(--pos-soft); transition: width var(--t-base) var(--ease); }
.pn-pos.dn > b::before { background: var(--neg-soft); }
/* (no bar for what moved since the last update, nor for a share too small to count) */
.pn-pos.wide > b::before, .pn-pos.flat > b::before { display: none; }
.pn-pos::before { content: ""; position: absolute; z-index: 0; inset: 0 -6px; border-radius: calc(var(--radius-ctl) - 1px); transition: background-color var(--t-fast); }
@media (hover: hover) { .pn-pos:hover::before { background: var(--bg-hover); } }
.pn-h.pn-pos::before { display: none; }

/* a wide card: winners and losers side by side */
/* (two columns of equal content width; the rule between them starts at the heading row, not above it)
   Only from 680px, with the desktop's type: each column's name cell is then at least 180px, and the book's names
   are up to 165px (the longest of 206 names over 25 trading days, at 12px). At 492px, the old threshold, the cell
   was 87px: an A share and its H share, whose names differ only at the end, were cut to the same "… IN…". Not in
   a window under 768px either, whose larger type (tokens.css) would leave 162-174px for names of up to 179px: there
   the card is at most 703px wide. Otherwise one column, one list under the other, where a name that does not fit
   takes more lines (see .pn-pos > .ellipsis). */
@media (min-width: 768px) {
  @container (min-width: 680px) {
    .pn-cols { flex-direction: row; }
    .pn-cols > div { flex: 1 1 0; min-width: 0; }
    .pn-cols > div:first-child:not(:only-child) { padding-right: 20px; }
    .pn-cols > div + div { margin-top: 0; padding-top: 0; border-top: 0; padding-left: 20px; border-left: var(--hair) solid var(--hairline); }
    .pn-cols > div + div > .pn-h { display: grid; }
  }
}

/* no data yet: the card keeps its shape — a figure that says "—" and a chart frame that says why */
.pn-none b, .pn-none span { display: block; }
.pn-none b { font-size: var(--fs-md); line-height: var(--lh-md); font-weight: 500; color: var(--text-2); }
.pn-none span { margin-top: 2px; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }
/* (the sentence breaks between its two clauses, never inside one) */
.pn-none span i { display: inline-block; font-style: normal; }
/* (a preview is one line tall: the title alone, centred on the whole frame) */
.pn.compact .pn-none span { display: none; }
.lc.compact .lc-over { inset: 0; }
/* loading: the final sizes, a figure bar and a flat line where the curve will be */
/* (the bar is 28px tall inside the line box of the figure it stands for — 1.25 × its size — so the rows below
   do not move by a pixel when the figure arrives) */
.pn-sk-fig { width: 60%; height: 28px; margin: calc((var(--fs-title1) * 1.25 - 28px) / 2) 0; border-radius: var(--radius-xs); }
.pn-sk-chart { position: relative; height: var(--lc-h); }
.pn-sk-chart::after { content: ""; position: absolute; left: 0; right: 40px; top: 50%; height: 2px; background: var(--fill-2); }
/* (a preview has no gutter and its chart is 44px tall: the skeleton takes the same box, so nothing jumps) */
.pn.compact .pn-sk-chart { height: 44px; }
.pn.compact .pn-sk-chart::after { right: 0; }
/* the setup hint under an empty chart breaks between its clauses (like the sentence in the frame above it), so a
   line never starts with a two-character orphan or an opening bracket; the UI path in it stays in one piece */
.pn > .note { text-wrap: pretty; }
.pn > .note i { display: inline-block; font-style: normal; }
.pn-path { white-space: nowrap; }

@media (max-width: 767px) {
  .pn-chart { --lc-h: 148px; }
}
