/*
 * The chart page's skin.
 *
 * Every id and class in here is a contract with index.html, agent/chat.js,
 * agent/overlay.js, measure.js and snapshot.js - rename nothing without
 * reading those.
 * The canvas reads the same tokens (index.html's T()), and snapshot.js paints
 * the DOM layer from computed styles: keep overlay and strip backgrounds as
 * plain colours, because a gradient there comes out transparent in the picture.
 *
 * Two temperatures. Everything the user does is gold: the price axis, focus
 * rings, the active timeframe, their bubble in the chat, their drawings.
 * Everything the agent does is cool ice blue: the pill that says it is
 * connected, its tool calls, its name, its marks. A chart marked up by both should read as two hands at a glance.
 * Gold owns yellow, so warnings are orange.
 */

@font-face{font-family:"IBM Plex Sans";font-weight:400;font-style:normal;font-display:swap;src:url(./vendor/fonts/ibm-plex-sans-latin-400-normal.woff2) format("woff2")}
@font-face{font-family:"IBM Plex Sans";font-weight:500;font-style:normal;font-display:swap;src:url(./vendor/fonts/ibm-plex-sans-latin-500-normal.woff2) format("woff2")}
@font-face{font-family:"IBM Plex Sans";font-weight:600;font-style:normal;font-display:swap;src:url(./vendor/fonts/ibm-plex-sans-latin-600-normal.woff2) format("woff2")}
@font-face{font-family:"IBM Plex Mono";font-weight:400;font-style:normal;font-display:swap;src:url(./vendor/fonts/ibm-plex-mono-latin-400-normal.woff2) format("woff2")}
@font-face{font-family:"IBM Plex Mono";font-weight:500;font-style:normal;font-display:swap;src:url(./vendor/fonts/ibm-plex-mono-latin-500-normal.woff2) format("woff2")}
@font-face{font-family:"IBM Plex Sans Hebrew";font-weight:400;font-style:normal;font-display:swap;src:url(./vendor/fonts/ibm-plex-sans-hebrew-hebrew-400-normal.woff2) format("woff2")}
@font-face{font-family:"IBM Plex Sans Hebrew";font-weight:500;font-style:normal;font-display:swap;src:url(./vendor/fonts/ibm-plex-sans-hebrew-hebrew-500-normal.woff2) format("woff2")}
/* THE TEACHER'S HANDWRITING, AND IT HAD TO HAVE HEBREW (docs/מצב-לימוד.md §7:
   "גופן כתב יד מאוחסן אצלנו כמו IBM Plex"). Playpen Sans Hebrew, SIL Open Font
   License 1.1 - the licence text sits beside the file in vendor/fonts/.
   The VARIABLE font, so the board's 700 and the heading's 800 are real weights
   (the static 400 it replaced on 2026-10-03, §34, made them a synthesised
   bold), and its HEBREW half only, 53KB: its figures and Latin are
   deliberately not loaded, so every number on the board stays in the chrome's
   own face and is read as a number, never as handwriting - and a line in
   English is written in that face too. */
@font-face{font-family:"Playpen Hand";font-weight:100 800;font-style:normal;font-display:swap;src:url(./vendor/fonts/playpen-sans-hebrew-var-hebrew.woff2) format("woff2");unicode-range:U+0307-0308,U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F}

:root{
  /* BLACK GLASS, GOLD AND ICE (2026-09-27). The identity is Dor's and does not
     move: a black ground, gold for the user's hand, ice blue for the analyst's.
     What this pass changed is how the chrome AROUND the candles carries it:
     the candles are the light source and everything else is matte - depth by
     elevation rather than by outline, gold spent like jewellery (thin, precise,
     sparse), and every figure typeset in Plex Sans rather than printed in a
     monospace face. Three surfaces step up from the ground, each a little
     lighter, and a raised one carries a lit top edge (`--hi`) instead of a
     border on all four sides. */
  --bg:#090b10;            /* the chart ground: black glass, a breath of blue */
  --bg-deep:#060709;       /* the ground's lower edge, for a little depth */
  --panel:#0d1016;         /* header, rail, footer, chat */
  --panel2:#131821;        /* popovers and windows */
  --raise:#1a202b;         /* hover and the selected segment */
  --well:#07090c;          /* a field or a track, a step BELOW the panel it sits in */
  --hover:rgba(214,222,235,.055);   /* a resting control under the pointer */
  --press:rgba(214,222,235,.09);    /* ...and pressed */
  --hi:rgba(255,255,255,.05);       /* the lit top edge of anything raised */
  --line:rgba(148,163,184,.11);
  --line2:rgba(148,163,184,.2);
  --text:#e7eaf0; --muted:#949eae; --dim:#5e6778;
  /* THE GREEN IS EXACTLY WHAT IT ALWAYS WAS; THE RED WAS LIFTED ONE STEP
     (2026-09-27). Three brighter palettes were built, rendered on the frozen
     fixture at two densities and rejected on 2026-09-15: *"they all look worse
     than what was there before - I want something much more subtle, the same
     kind of candles, just slightly more lit"*. The answer then was the EMA
     stack coming down (`web/indicators/trend.js`) and a whisper of wick here.
     On 2026-09-27 Dor reopened the candle itself for the look pass ("if you
     think there is a candle that reads better"), and the one change that stays
     inside "much more subtle" is the imbalance the measurement had already
     named: green is intrinsically the brightest hue and red the dimmest, so
     the pair sat at 1.57:1 and every falling day was drawn in the weaker ink.
     The red keeps its hue (OKLCH 16) and gains one step of lightness, .696 to
     .715 - luminance .303 to .332, the pair now 1.43:1 - so a red day reads
     as clearly as a green one while nothing turns pink. `docs/skin.md` has the
     side-by-side it was chosen from.

     `--up-wick` / `--down-wick` are the whisper: ONE STEP of lightness over
     the body and nothing else. The wick is the only lever v4 has - the whole
     of CandlestickStyleOptions is ten colour and boolean fields, no width, no
     shadow, no glow - and at 1px against black a step that small is the
     difference between an edge you can see and one you cannot. */
  --up:#2fd08a; --down:#ff6b7c;
  --up-wick:#48d598; --down-wick:#ff818c;
  --up-soft:rgba(47,208,138,.13); --down-soft:rgba(255,107,124,.13);
  --up-vol:rgba(47,208,138,.38); --down-vol:rgba(255,107,124,.38);
  /* bars from IBKR's overnight venue: the same two colours, dimmed */
  --up-night:rgba(47,208,138,.42); --down-night:rgba(255,107,124,.42);
  --up-vol-night:rgba(47,208,138,.16); --down-vol-night:rgba(255,107,124,.16);
  --vol-avg:rgba(226,232,240,.6);       /* the average line through the volume */
  /* Gold is the user's hand and the page's own chrome. --accent is an alias
     for it, so every control that used to be blue follows without a rename. */
  --gold:#e4c778; --gold-hi:#f8e7b0; --gold-lo:#b3902f;
  --gold-soft:rgba(228,199,120,.13); --gold-line:rgba(228,199,120,.42);
  --gold-sheen:linear-gradient(135deg,#f9ecbf 0%,#e4c778 38%,#b9942f 62%,#f2dc98 100%);
  --accent:var(--gold); --accent-soft:var(--gold-soft); --accent-line:var(--gold-line);
  /* A GLOSSARY TERM IS GOLD AND A LITTLE HEAVIER, and the second value is the
     one under the finger. The full gold rather than the dim one: it is the
     user's own colour, and a term nobody notices is a definition nobody
     opens. */
  --gl-term:var(--gold); --gl-term-on:var(--gold-hi);
  --agent:#6ea9ff; --agent-hi:#d7e7ff; --agent-soft:rgba(110,169,255,.11); --agent-line:rgba(110,169,255,.4);
  /* Frozen structure is the agent's hand at half voice. It is the scaffolding
     the thesis stands on and there can be forty pieces of it, so it has to sit
     under the four marks that are the argument without disappearing. */
  --agent-dim:rgba(110,169,255,.45); --agent-dim-soft:rgba(110,169,255,.06);
  --agent-dim-line:rgba(110,169,255,.22);
  /* Frozen structure - the scaffolding a thesis is read against. This has been
     round the houses: the agent's blue at half voice, then gold at .45, then
     back to the agent's blue AT FULL STRENGTH - and that last one was the
     same value as `--agent`, so structure and thesis stopped separating by
     colour at all. Dor, 2026-09-14, looking at AEHR with the words off (T):
     eight pieces of structure, three thesis levels, a trigger, a target and an
     invalidation were ONE BLUE and nothing on the chart said which was which.
     SO THE HUE IS BACK, DESATURATED RATHER THAN DIMMED. Alpha is the lever
     that was already tried here and reverted - .45 was too faint to follow a
     line across a year of bars - and a slate blue is FULLY OPAQUE while still
     reading as the grid's own family rather than as a claim. Gold was tried
     too and is not available: it is the user's hand and the gaps.
     The WIDTH lever (the claim at 2, the scaffolding at 1) is still in
     reserve and is still the next one, ahead of dimming. */
  --struct:#8aa0bf; --struct-soft:rgba(138,160,191,.09);
  --struct-line:rgba(138,160,191,.35);
  /* THE TWO PRICES THAT MEAN GOOD AND BAD, AND THIS IS THE SECOND DELIBERATE
   * EXCEPTION TO THE TWO TEMPERATURES (after --alert; docs/skin.md). The two
   * temperatures answer WHOSE, and in front of six identical blue horizontals
   * the question is WHICH ONE - a price to sell into, a price that says you
   * were wrong, or a price to read the chart against. The role already carries
   * the LINE STYLE (target dashed, invalidation dotted), so colour and style
   * say the same thing twice, which is what makes it readable with the words
   * off - and off is how Dor reads it.
   *
   * THEY ARE NOT THE CANDLES' OWN GREEN AND RED, AND THAT IS THE POINT.
   * They were, byte-for-byte, from 2026-09-14: this chart already had a word
   * for better and worse, a hue was argued to collide only with something
   * drawn in the SAME SHAPE, and a candle body is not a line. Dor, 2026-09-17,
   * reading a marked US 30 daily on the phone: *"the analyst drew lines in
   * green and red - I don't want drawing lines in that colour, it is the
   * candles' colour and it is confusing."* A body is not a line, but on a
   * chart that is five hundred green and red bodies the EYE sorts by hue
   * first and by shape second, and every dotted red horizontal read as
   * something the tape had done rather than something the analyst claimed.
   * So the pair moves off both candle hues for good.
   *
   * THE WHEEL IS FULL AND THAT IS WHY THIS PAIR. Spent already: gold 45 (the
   * user's hand, the gaps, EMA 50), the up candle 154, `--shape` cyan 192,
   * the agent's blue 216 (and EMA 20 at 218, and `--struct` desaturated on
   * the same hue), EMA 100 at 271, `--formation` 254, `--alert` magenta 320,
   * the down candle 352. Every remaining arc of the circle is inside 25
   * degrees of one of those, so ONE strong slot is left - the orange at ~22,
   * which clears the down candle by 30 and is vivid where the gold is pale -
   * and the second colour has to come off the hue axis entirely.
   *
   * SO: INVALIDATION IS THE ORANGE, because *where you were wrong* is the one
   * of the three that is literally a warning, and TARGET IS A NEAR-WHITE
   * SILVER - no hue at all, so it collides with nothing on the circle and
   * cannot be read as a candle, a hand or a measurement. It is the brightest
   * ink on the chart and it is ALLOWED to be: a claim may be as loud as the
   * price (tests/skin.test.mjs exempts the hands for exactly this reason), and
   * of the ten marks a thesis gets there is at most ONE target. Dor's call,
   * 2026-09-17, picked against three alternatives (cyan/orange, muted
   * green/red, and everything back in one blue).
   *
   * The TRIGGER is deliberately NOT a third colour. It is the one of the three
   * that is neither good nor bad - it is where the argument starts - so it
   * stays the analyst's own blue, and the chart says: blue is what I think,
   * silver is where I am right, orange is where I am wrong. */
  --target:#dfe7f2; --target-soft:rgba(223,231,242,.10); --target-line:rgba(223,231,242,.42);
  --stop:#ff8b3c;   --stop-soft:rgba(255,139,60,.10);    --stop-line:rgba(255,139,60,.42);
  /* A SHAPE IS NOT A PRICE, AND THE BOX AT 101.00 IS WHY THIS EXISTS. Dor,
   * 2026-09-14, on an AEHR band 101.00-101.62: a range 0.62 wide on a chart
   * spanning 20 to 160 paints as a 2px box, so by SHAPE it is a line, and in
   * the analyst's blue it was a line among six others. What it actually says
   * is different in kind - a region of price rather than a price - and that is
   * the thing the colour now carries.
   *
   * IT SHIPPED FOR AN HOUR AS THE TEAL THE PROMPT WAS ALREADY PRESCRIBING -
   * `agent/MEMORY.md` told the analyst to pass `#3fd9d0` by hand on "a shape
   * carrying the argument - an arc, a neckline, compression boundaries", and
   * that literal appeared in NO stylesheet and NO module: the one colour on
   * this chart the code had never heard of, kept alive by the model
   * remembering to type it (rule 2). Promoting it to a token was right; the
   * VALUE was not. Dor, looking at it: *"green is one of the two candle
   * colours"* - and at 176 degrees that teal is 21 from the up candle, which
   * on a chart whose whole background language is green and red is a hue
   * nobody can spend. The prompt had been asking for it since before there was
   * anything to compare it against.
   *
   * IT WAS AMETHYST FOR AN HOUR AND THAT WAS THE SAME MISTAKE AGAIN. "A box
   * collides only with another box" was the rule applied, and it is wrong for
   * exactly this mark: the whole premise here is that a band 0.4% of the scale
   * PAINTS AS A LINE, so it has to clear the LINES. The violet was 14 degrees
   * from EMA 100 (#a855f7), which is a line, on the chart Dor reads every day.
   *
   * THE CHART IS FULL, AND THE EMA STACK IS WHY. The four moving averages hold
   * 45, 218, 271 and near-white - which puts EMA 50 ONE degree off the user's
   * gold and EMA 20 TWO degrees off the agent's blue. They are a measurement
   * rather than a hand or a claim, so they have the weakest title to any of
   * it, and re-colouring them as one family would free the violet and end both
   * of those collisions. DOR'S CALL, 2026-09-14: THE EMA COLOURS DO NOT MOVE.
   * They are what he reads the chart with. So they are immovable, and what is
   * left to place around is 45, 154, 217, 271, 320, 352 and white.
   *
   * WHICH LEAVES EXACTLY ONE GOOD SLOT, and it is arithmetic rather than
   * taste: the widest gap on the circle is 154 (up candle) to 217 (agent
   * blue), and its middle is 190. Every other gap caps at 27 degrees or less -
   * indigo 244 is 27 from both blues, orchid 295 is 24 from EMA 100, and the
   * 100-degree slot between gold and the up candle is GREEN, which is the one
   * thing this cannot be (Dor: *"green is one of the two candle colours"*).
   * At 190 and 88% saturation this is 36 from the up candle and 27 from the
   * agent's blue.
   *
   * IT IS NOT THE MINT THAT WAS REJECTED, and the difference is not a
   * technicality: that was 176 at 67% saturation, where G 217 > B 208 and the
   * thing reads as turquoise. Here B 240 > G 205, which is a cyan - the same
   * axis that separates the up candle (G 208, B 138) from anything blue.
   * Gold and aqua on black is deco, and it is the pairing left standing. */
  --shape:#35cdf0; --shape-soft:rgba(53,205,240,.12); --shape-line:rgba(53,205,240,.5);
  /* A formation the detector found is neither hand: not the user's gold and not
     the agent's blue, because nobody drew it - it was measured. One colour for
     every boundary of it, so a cup, its handle and its rails read as one
     object, and direction green/red is spent only on the pole. */
  --formation:#9a8cc7; --formation-soft:rgba(154,140,199,.10);
  --formation-line:rgba(154,140,199,.55);
  /* An open gap is not a hand either - nobody drew it, the tape left it. Dor's
     call is that it reads in gold rather than in the agent's blue: a price
     nobody traded through is a fact about the chart, closer to the user's own
     furniture than to anything the analyst argued for. Its own token rather
     than var(--gold) so it can be pulled apart from the user's hand later
     without hunting for which gold meant what. */
  /* A gap is measured rather than argued, so it is gold - and this exact value
     is what `agent/MEMORY.md` tells the analyst to pass for a gap box it draws
     by hand, so a box the layer painted and a box the analyst drew are one
     colour. It was briefly moved to the lighter `#f8e7b0` on a misreading of
     "lighter" as paler when it meant weaker, and put back: the weight is set
     by width and alpha, never by washing out the hue. `.nwtick` and `.mtick`
     share this token. */
  /* THE FILL WAS .13 AND NOBODY COULD SEE THE BOX. Dor, 2026-09-18, off an
     AVGO daily carrying three gap boxes: "the gold there is very thin, almost
     transparent". The hue did not move - it is the same #e4c778 the chrome
     wears, and washing it out or brightening it was the mistake made once
     already (above). What moved is the ALPHA: .28 for the fill, which is the
     midpoint between what it was and the .42 at which the candles inside the
     box stop being readable (BAND_MAX in drawings.js), and a fully opaque
     edge. The box has to be seen THROUGH - that is the whole reason a gap is
     a box and not a level - so louder than this is a different bug.
     These two are read by nothing but the draw palette (index.html), so this
     is the one place that changes it for every gap box on every chart. */
  --gap:#e4c778; --gap-soft:rgba(228,199,120,.28); --gap-line:#e4c778;
  /* The Fibonacci grid. Its own amber, and neither hand: a fib is a reading of
     a leg the pivots found, not a mark the user or the agent made. The bands
     alternate between two alphas so the grid reads as rungs rather than as one
     wash, and only the 0.618 is given full voice. */
  --fib:rgba(234,179,8,.5); --fib-key:rgba(234,179,8,.95);
  --fib-anchor:rgba(125,136,150,.8);
  --fib-soft:rgba(234,179,8,.055); --fib-soft2:rgba(234,179,8,.018);
  --fib-ext:rgba(234,179,8,.3);
  --warn:#f0a03c; --warn-soft:rgba(240,160,60,.1); --warn-line:rgba(240,160,60,.34);

  /* AN ARMED ALERT IS ITS OWN COLOUR, AND THAT IS A DELIBERATE EXCEPTION TO
   * THE TWO TEMPERATURES (docs/skin.md). Gold is the user's hand and ice blue
   * is the analyst's, and an alert line took its hand's colour for exactly one
   * afternoon: Dor read the chart and said *"put the alert in a different
   * colour so it will be easy to notice that it's an alert and not a
   * support/resistance line"*. He is right, and the reason is that the two
   * temperatures answer WHOSE, while the question in front of a horizontal
   * dashed line at 2,510 is WHAT KIND - a level somebody believes in, or a
   * promise to interrupt you when price gets there. Those are different
   * objects, and on this chart the hand is the smaller fact: the `A` window
   * and `get_chart` both still name the author.
   *
   * MAGENTA BECAUSE IT IS THE ONE HUE THIS CHART DOES NOT ALREADY SPEND, and
   * every alternative is adjacent to something drawn in the SAME SHAPE. Orange
   * sits 13 degrees of hue from gold, which is the user's own levels - the
   * exact confusion this exists to end. Cyan sits beside the agent's blue,
   * which is also horizontal lines. Magenta's only neighbour is the down
   * candle, and a candle body is not a line. */
  --alert:#ff5ec8; --alert-soft:rgba(255,94,200,.12); --alert-line:rgba(255,94,200,.45);
  /* ---- THE TEACHER'S THREE INKS (docs/מצב-לימוד.md §7, §17 decision 6) ----
   *
   * The board is the analyst's HAND, so ink 0 is its own blue and nothing
   * else. The other two were picked by the `--shape` method (docs/skin.md, "A
   * census of every hue drawn on the pane" and the section *The board's two
   * other inks* below it), and that census answered: THE HUE WHEEL IS SPENT.
   * Every arc left is inside 25 degrees of something, and the two names that
   * are free - lime and chartreuse - are the two this chart cannot say: the up
   * candle owns green and gold owns yellow.
   *
   * So the board does what `--target` did when the census ran out on it: one
   * ink comes OFF the hue axis entirely. `--ink-1` is WORKING LIGHT, a cool
   * white - since the board draws with light (docs/מצב-לימוד.md §34, Dor's
   * pick, 2026-10-03). It was a warm chalk until then, chosen so it could not
   * be read as a target's cool silver; that collision is not one the screen
   * can show, because every performance plays on a BARE chart (`bare()` in
   * web/lesson/page.js) and no target is up beside the ink. `--ink-2` takes
   * the widest arc that is left, at dE 35.7 from its nearest neighbour - wider
   * than `--shape`'s own 30.5 - and pays for the one collision it has, the
   * alert's magenta 17 degrees away, with the four signals nothing else on
   * this chart carries: it is a THICK lit stroke, it is drawn in front of you
   * at a hand's pace, it never lies flat as a price line, and it is gone in
   * three seconds. That is the "second signal, not another hue" the census
   * itself says is the next lever.
   *
   * ⚠ AND THE BOARD IS A DELIBERATE EXCEPTION TO "THE CANDLE IS THE BRIGHTEST
   * INK ON THE CHART" (§7). Its light is brighter than any candle - a white-hot
   * core and a bloom. It is the analyst's hand, it lives three seconds, and
   * drawing the eye is its whole job - the same exemption the two hands
   * already have in tests/skin.test.mjs, which measures the EMA stack and
   * nothing else.
   *
   * NONE OF THE THREE CARRIES A VERDICT. A check says it held and a cross says
   * it broke; the ink says which pen, never good or bad. That is why neither
   * of these is a green or a red, beyond the hue argument. */
  --ink-0:var(--agent);
  --ink-1:#cfe2ff;                   /* working light, a cool white (it was chalk, #f2eede) */
  --ink-2:#ff33f1;                   /* the widest arc left: 304, dE 35.7 */
  /* ...each drawn as a WHITE-HOT CORE inside a sheath of itself
     (web/sketch.js, `paintStroke`), the spark at the pen's tip and the
     eraser's edge in `--sketch-sheen`. */
  --ink-0-core:#eaf3ff;              /* the agent's blue, white-hot */
  --ink-1-core:#ffffff;
  --ink-2-core:#ffe2fc;
  --sketch-sheen:#ffffff;
  /* ...and the SENTENCE is the white of that core, with the analyst's blue
     glowing round it, because it is read rather than glanced at and it is the
     one piece of the board made of small shapes. Dor, 2026-09-20: "הכתב...
     יכול להיות פשוט גם יותר זוהר ובהיר, ואז ככה לא יהיה בעיה של הבנה." */
  --say:#ffffff;
  --say-glow:rgba(110,169,255,.62);
  --hand:"Playpen Hand","IBM Plex Sans Hebrew",var(--sans);
  /* the canvas */
  --grid:rgba(148,163,184,.055);
  --axis:rgba(148,163,184,.16);
  --axis-text:#cdb168;               /* the price and the clock, set in gold */
  --cross:rgba(148,163,184,.35);
  --cross-label:#dcbf66;             /* the crosshair tag; its text contrasts on its own */
  /* THE SAME CROSSHAIR, HELD. A finger that stays still opens the crosshair as
     a READING - what price is this point at - and a reading is the user's own
     question, exactly as the ruler is, so it wears the user's gold rather than
     the hover line's slate. Its own token rather than var(--gold-line): this
     is brighter than anything the user DREW, on purpose, because it is under a
     fingertip for a second and then gone, and .35 alpha of slate is not
     legible there at all. Nothing else may use it - a mark this colour would
     read as a reading somebody is taking. */
  --read:rgba(228,199,120,.8);
  --watermark:rgba(228,199,120,.05);
  /* The two weights of overlay ground. Flat colours on purpose: snapshot.js
     paints the DOM layer by reading backgroundColor and nothing else, so a
     gradient on either of these comes out of the picture transparent. */
  --ovl-box:rgba(9,11,16,.9);        /* a sentence, in its own box */
  --ovl-tag:rgba(9,11,16,.74);       /* a price or a count, out of the way */
  --aura-top:rgba(228,199,120,.13);  /* the wash under the close line */
  --aura-bottom:rgba(228,199,120,0);
  /* the same wash in the colour of the session the bar traded in:
     sky for the pre-market, pale gold after hours, slate overnight */
  --aura-pre:rgba(56,189,248,.32);      --aura-pre-bottom:rgba(56,189,248,.07);
  --aura-after:rgba(248,231,176,.3);    --aura-after-bottom:rgba(248,231,176,.06);
  --aura-night:rgba(148,163,184,.22);   --aura-night-bottom:rgba(148,163,184,.05);
  --sans:"IBM Plex Sans","IBM Plex Sans Hebrew",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  /* THE DESK SPEAKS SANS; THE SCALE SPEAKS MONO. Every figure in the chrome -
     the strip, the rail, the windows, the panels - is `--num`, which is Plex
     Sans: its digits are already tabular at 0.6em, the same advance as Plex
     Mono's, so a column of prices lines up exactly as it did while the words
     around it stop reading like a terminal log. `--mono` stays for code and
     for everything painted ON the pane beside the price axis (the axis itself,
     the caption tags, the ruler, the countdown, the tick rows), because the
     canvas axis is mono and `paintedRects()` measures a caption in it. */
  --num:var(--sans);
  --fs-cap:10.5px; --fs-meta:11.5px; --fs-sm:12.5px; --fs-body:13px; --fs-title:15px;
  /* Radius follows elevation: the smaller and lower a thing is, the tighter its
     corner - a chip 4, a control 6, a row or a field 8, a popover 12, a window 16. */
  --r-xs:4px; --r-sm:6px; --r:8px; --r-lg:12px; --r-xl:16px;
  /* HOW BIG A CARD ON THE RAIL IS, AS ONE NUMBER. Every type size and every
     pad on the four home-screen cards - All stocks, the account, a folder, a
     list - is `calc(base * --card-scale)`, so the whole block grows together
     and the proportions inside it cannot drift apart. 1 is the desktop, whose
     rail is a narrow COLUMN (`--rail-w`) and has no room to give; the phone's
     drawer is the width of the screen and sets its own, at the end of this
     file. A card is the only thing on that screen, so it is sized for the
     screen it is on. */
  --card-scale:1;
  /* HOW WIDE THE RAIL IS ON A DESKTOP, AS ONE NUMBER - `#watch` and the
     rail fixture both read it. It was 216px, and at 216 the rail cut its own
     words (Dor, 2026-09-24: *the lists bar on the computer is cut*): measured
     on his fifteen lists, a folder called Macro read `...`, both Hebrew
     folder names lost their ends, the meta line read `2 li…` and eleven of
     twenty-two company names in IGV were clipped. At 264 the home screen was
     whole; 280 is that plus a margin for a longer leader or a two-digit
     move, and leaves one company name short by a pixel. The phone never
     reads it: its drawer is the width of the screen.
     ...AND 328 SINCE THE EXTENDED PRICE (Dor, 2026-10-06): `pre 240.97`
     beside its percent took ~48px out of the company name, NVIDIA CO... down
     to NVI..., and his word was to widen the rail rather than lose it. */
  --rail-w:328px;
  /* Shadows are plain rgba over the ground, never a colour: a lit top edge and
     two soft falls - a near one that seats the thing, a far one that lifts it. */
  --shadow-sm:0 1px 2px rgba(0,0,0,.4);
  --shadow-pop:inset 0 1px 0 var(--hi),0 16px 38px -8px rgba(0,0,0,.66),0 3px 10px rgba(0,0,0,.36);
  --shadow:inset 0 1px 0 var(--hi),0 30px 72px -12px rgba(0,0,0,.74),0 10px 28px rgba(0,0,0,.42);
  --ring:0 0 0 3px var(--gold-soft);
  --ease:cubic-bezier(.2,.7,.2,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  --dur-1:.12s; --dur-2:.18s; --dur-3:.28s;
}

*{box-sizing:border-box}
/* Author-level display on .pill would otherwise beat the browser's own rule
   for [hidden], and "not saving" would show while every write succeeds. */
[hidden]{display:none!important}
/* CHROME ON ANDROID INFLATES TEXT AND SAFARI DOES NOT, WHICH IS WHY THE TWO
   PHONES DISAGREE. Chrome's Text Autosizing is `auto` by default: it grows the
   text of a "text block" by a factor it derives from the layout width and the
   device's own font-scale setting, so a page authored at 11px can arrive at 14
   or 16 on one device and 11 on another - and nothing in the page says so.
   Reported from Chrome on a phone, 2026-09-13, against an iPhone that looked
   right. `100%` means "render what I asked for": it does not stop the user
   zooming, it stops the browser second-guessing the size. */
html{-webkit-text-size-adjust:100%; text-size-adjust:100%}
/* ...AND THE SAME TWO PHONES FLASH A DIFFERENT COLOUR ON EVERY TAP. The tap
   highlight is the browser's, not ours: Chrome paints a translucent BLUE box
   (rgba(51,181,229,.4)) over whatever was pressed and Safari a grey one, on
   top of the `.on` and `:active` states this sheet already draws. Inherited,
   so one declaration on the root takes it off everything; the per-control
   `transparent`s further down predate it and are left as they were. */
html{-webkit-tap-highlight-color:transparent}
html,body{height:100%}
/* THE PAGE IS HELD UNTIL ITS FIRST FRAME IS ITS FINAL ONE - a phone since
   2026-10-03, the desktop since 2026-10-06 (`booting`, set at
   parse time after `#wrap` and taken off by the boot function once every
   surface is in place). The ground stays; if the wait is long enough to be
   noticed, the app's own mark fades in at its middle - a launch, not a page
   being assembled in front of you. */
html.booting{background:var(--bg)}
html.booting body{visibility:hidden}
html.booting::before{
  content:''; position:fixed; left:50%; top:50%; z-index:1;
  width:72px; height:72px; margin:-36px 0 0 -36px; border-radius:17px;
  background:url(icon-192.png) center/cover;
  box-shadow:0 10px 30px rgba(0,0,0,.5), 0 0 0 1px rgba(228,199,120,.14);
  opacity:0; animation:bootmark .45s ease-out .25s forwards;
}
@keyframes bootmark{from{opacity:0; transform:scale(.92)} to{opacity:1; transform:none}}
body{
  margin:0; background:var(--bg); color:var(--text);
  overflow-x:hidden;                     /* nothing on this page may widen the window */
  font:13px/1.4 var(--sans);
  font-variant-numeric:tabular-nums;     /* every figure on the page lines up */
  display:flex; flex-direction:column;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
}
::selection{background:rgba(228,199,120,.3); color:#fff}
button,input,textarea,select{font-family:inherit}
*{scrollbar-width:thin; scrollbar-color:rgba(148,163,184,.2) transparent}
::-webkit-scrollbar{width:8px; height:8px}
::-webkit-scrollbar-thumb{background:rgba(148,163,184,.18); border-radius:4px; border:2px solid transparent; background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:rgba(148,163,184,.3); background-clip:padding-box}
::-webkit-scrollbar-track{background:transparent}
:focus-visible{outline:1.5px solid var(--accent); outline-offset:2px}
#q:focus-visible,textarea:focus-visible,input[type=number]:focus-visible{outline:none}

@keyframes spin{to{transform:rotate(360deg)}}
/* THE LIVE DOT'S BREATH, ON THE COMPOSITOR. It was a box-shadow growing 3px ->
   6px at .18 -> .06 alpha, which is a style pass and a paint on every frame for
   as long as the feed is live - i.e. forever, on a ticking chart, under the
   finger of every gesture. The same ring is a 20px disc of the same green
   scaled .7 -> 1 (14px -> 20px, the dot's 8px plus the 3px -> 6px spread) and
   faded .18 -> .06: opacity and transform only. Under the dot, as the shadow
   was: z-index -1 inside `.status`, which `isolation` makes the context, so it
   goes behind the dot and not behind the header. */
@keyframes breathe{0%,100%{transform:scale(.7); opacity:.18}50%{transform:scale(1); opacity:.06}}
@keyframes blink{50%{opacity:.35}}
@keyframes pop{from{opacity:0; transform:translateY(-4px)}to{opacity:1; transform:none}}
@keyframes drop{from{opacity:0; transform:translateY(-6px)}to{opacity:1; transform:none}}
/* The same entrance for something that is CENTRED by a transform of its own:
   `drop` ends at `transform:none`, which would drag a translateX(-50%) box off
   its own centre for the length of the animation. */
@keyframes dropx{
  from{opacity:0; transform:translate(-50%,-6px)}
  to{opacity:1; transform:translate(-50%,0)}
}
@media (prefers-reduced-motion:reduce){*{animation:none!important; transition:none!important}}

/* ---- header ---- */
header{
  position:relative; display:flex; align-items:center; gap:8px; flex-wrap:nowrap;
  height:44px; padding:0 12px; background:var(--panel);
}
/* A gold hairline under the controls, fading before it reaches either edge:
   the one rule on the page that is not slate. */
/* A piece of the header that had to give way for the row to fit; see fitHeader(). */
header .yield{display:none!important}
header::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px; pointer-events:none;
  background:linear-gradient(90deg,rgba(228,199,120,0),rgba(228,199,120,.55) 14%,rgba(228,199,120,.55) 86%,rgba(228,199,120,0));
}
.brand{
  display:flex; align-items:center; gap:9px; flex:none; margin-right:4px;
  font:600 14px/1 var(--sans); letter-spacing:.01em; white-space:nowrap;
}
.brand .mark{width:20px; height:20px; display:block}
.brand>span{
  background:var(--gold-sheen); -webkit-background-clip:text; background-clip:text;
  color:transparent; -webkit-text-fill-color:transparent;
}
/* A FIELD SITS A STEP BELOW WHAT IT IS IN (the look pass, 2026-09-27): the
   well, one hairline, and the gold only once it has the focus. */
select{
  background:var(--well); color:var(--text); border:1px solid var(--line);
  border-radius:var(--r); padding:6px 8px; font:inherit; outline:none; min-width:190px;
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
select:hover{border-color:var(--line2)}
select:focus{border-color:var(--gold-line); box-shadow:var(--ring)}

.finder{position:relative; flex:0 1 250px; min-width:150px}
.finder::before{
  content:""; position:absolute; left:12px; top:50%; width:14px; height:14px; margin-top:-7px;
  pointer-events:none; opacity:.55; z-index:1; transition:opacity var(--dur-1);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b95a5' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E") center/contain no-repeat;
}
/* Asked, the magnifier is the user's gold: the box is a search now. */
.finder:focus-within::before{
  opacity:1;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e4c778' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
}
/* THE SYMBOL IS THE TITLE OF THE PAGE, AND A PLACE TO SEARCH FROM - BOTH AT
   ONCE, AT REST (Dor, 2026-10-05: *"it was maybe not good"*). It was a bare
   title with a grey magnifier floating beside it (2026-09-28) - nothing said
   the name could be tapped, and asked, it changed size, grew a well and a
   double gold ring at once. Now: a quiet pill at rest - a hairline and a
   breath of ground, the name in the title's weight - and, asked, the same
   pill at the same size and the same type, its edge gold and its magnifier
   gold. Nothing about the text moves between the two; only the box answers. */
#q{
  width:100%; min-width:0; height:30px; padding:0 12px 0 33px;
  background:var(--well); color:var(--text); border:1px solid var(--gold-line);
  border-radius:10px; font:500 15px var(--sans); letter-spacing:-.005em; outline:none;
  box-shadow:0 0 0 3px rgba(228,199,120,.07);
  transition:border-color var(--dur-1), box-shadow var(--dur-1), background-color var(--dur-1);
}
#q::placeholder{color:var(--dim); font-weight:400}
#q::selection{background:rgba(228,199,120,.28); color:#fff}
#q:not(:focus){background:rgba(214,222,235,.03); border-color:var(--line);
  box-shadow:none; font-weight:600; cursor:pointer}
#q:not(:focus):hover{background:var(--hover); border-color:var(--line2)}

#results{
  overscroll-behavior:contain; touch-action:pan-y;
  position:absolute; top:calc(100% + 6px); left:0; z-index:20;
  width:400px; max-height:440px; overflow-y:auto; padding:6px;
  background:var(--panel2); border:1px solid var(--line2); border-radius:var(--r-lg);
  box-shadow:var(--shadow-pop); animation:pop var(--dur-2) var(--ease-out);
}
#results .grp{padding:10px 10px 5px; font:600 11.5px/1 var(--sans); letter-spacing:.01em; color:var(--muted)}
#results .row{
  display:flex; align-items:baseline; gap:10px; padding:8px 10px;
  border-radius:var(--r); cursor:pointer;
  transition:background-color var(--dur-1);
}
#results .row:hover{background:var(--hover)}
#results .row.sel{background:var(--raise)}
#results .row .sym{font:500 13px var(--num); color:var(--text)}
#results .row .sub{font:12.5px var(--sans); color:var(--dim); overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; flex:1}
/* Where a row comes from is a fact about it, not a control: a quiet chip. */
#results .row .tag{font:500 10.5px/1 var(--sans); color:var(--dim); background:var(--hover);
  border-radius:var(--r-xs); padding:4px 6px}
#results .row .tag.delayed{color:var(--warn); background:var(--warn-soft)}
#results .grp.filling{color:var(--dim); opacity:.75}
#results .empty{padding:14px; color:var(--muted); font-size:12.5px; text-align:center}

/* THE ONE SEGMENTED TRACK IN THE HEADER: a well, a hairline drawn inside it,
   and items with no edge of their own. The on-item wears the page's single
   on-treatment - gold, a soft gold fill and the one inset line a control ever
   gets - which every other segmented control on the page repeats. */
.tfs{
  flex:none; display:flex; gap:2px; height:28px; padding:2px;
  background:var(--well); border:0; border-radius:var(--r);
  box-shadow:inset 0 0 0 1px var(--line);
}
.tfs button{
  background:none; border:0; color:var(--muted); cursor:pointer;
  padding:0 9px; border-radius:var(--r-sm); font:500 12px var(--num);
  transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1);
}
.tfs button:hover{color:var(--text); background:var(--hover)}
.tfs button.on{background:var(--gold-soft); color:var(--gold); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
/* THE ACTIVE TIMEFRAME LIGHTS THE HAIRLINE (2026-09-28). On a desktop the row
   is text rather than a track of boxes - the nine read as one ruler of
   choices - and the chosen one is gold with a 2px underline standing exactly
   on the header's own gold hairline, so the one line on the page that is not
   slate brightens where the chart's clock is set. The row stretches to the
   header's height for that; nothing about its width changed, which is all
   `fitHeader()` measures. On a phone the row is the dock's key and keeps its
   own rules. */
body:not(.phone) .tfs{align-self:stretch; height:auto; padding:0; gap:0;
  background:none; box-shadow:none; border-radius:0}
body:not(.phone) .tfs button{border-radius:0}
body:not(.phone) .tfs button:hover{background:none; color:var(--text)}
body:not(.phone) .tfs button.on{background:none; color:var(--gold-hi);
  box-shadow:inset 0 -2px 0 var(--gold)}

/* A RESTING CONTROL HAS NO EDGE. The glyph sits on the panel in the muted ink;
   the pointer brings a fill, a press a deeper one, and `.on` is the only state
   that draws a line - inside the button, never around it. The border stays,
   transparent, so every box keeps the size it had and a state that colours
   one (the phone's dock) still has a border to colour. */
.ind{position:relative}
.ind>button{
  width:28px; height:28px; padding:0; display:grid; place-items:center;
  background:transparent; color:var(--muted); border:1px solid transparent;
  border-radius:var(--r-sm); cursor:pointer; font:500 13px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1), border-color var(--dur-1);
}
.ind>button svg{width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round}
.ind>button:hover{color:var(--text); background:var(--hover)}
.ind>button:active{background:var(--press)}
.ind>button.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#chatBtn.on{color:var(--agent); background:var(--agent-soft); box-shadow:inset 0 0 0 1px rgba(110,169,255,.3)}
/* THE ANALYST'S KEY, OVER ITS OWN PANEL (index.html, `.chatkey`): its ice at
   rest rather than the muted ink, because it is what the product is for, and
   its name beside the glyph. A pill and not a square, the one control in the
   row that carries a word. */
header .chatkey{margin-left:4px}
header #chatBtn{
  width:auto; padding:0 11px 0 9px; display:flex; align-items:center; gap:6px;
  color:var(--agent); font:500 12.5px/1 var(--sans); letter-spacing:.01em;
  box-shadow:inset 0 0 0 1px rgba(110,169,255,.22);
}
header #chatBtn:hover{color:var(--agent); background:var(--agent-soft)}
header #chatBtn.on{box-shadow:inset 0 0 0 1px rgba(110,169,255,.42)}
#dock #chatLbl{display:none}
/* Hidden marks are a thing you should notice you are not seeing. */
/* The two eyes share one cell and cross over - the open one shrinks away as
   the shut one arrives - so the key says what changed instead of swapping a
   picture under the thumb. */
#marksBtn svg{grid-area:1/1; transition:opacity .2s, transform .26s cubic-bezier(.3,1.6,.5,1)}
#marksBtn .eye-off{opacity:0; transform:scale(.5) rotate(-25deg)}
#marksBtn.on .eye{opacity:0; transform:scale(.5)}
#marksBtn.on .eye-off{opacity:1; transform:none}
/* The fullscreen key's four corners turn inward while it is the way OUT - the
   one picture for "leave fullscreen" every app has, reached by motion rather
   than by a second icon. Each corner turns about its own middle. */
#fsBtn path{transform-box:fill-box; transform-origin:center; transition:transform .32s cubic-bezier(.3,1.4,.5,1)}
#fsBtn.on path{transform:rotate(180deg)}
#marksBtn.on{color:var(--warn); background:var(--warn-soft); box-shadow:inset 0 0 0 1px rgba(240,160,60,.3)}
/* ...and on the rail, among the axis's own glyphs, it says so in the same
   colour without a box: the rail has no chrome of its own (see #zoom). */
#zoom #marksBtn.on{color:var(--warn); opacity:1; background:none; box-shadow:none}
/* THE SWITCHER IS A PHONE'S CONTROL (web/switcher.js). On a desktop the lists
   are a column beside the chart already; chrome.js carries it into the dock,
   and the phone block below is the only place it is drawn. */
.ind.sym{display:none}
/* THE HEADER READS IN GROUPS, BY PROXIMITY RATHER THAN BY BOXES. With the
   edges gone, thirteen controls at one even pitch are one long row; pulled
   together where they are neighbours, the lists-calendar-bell trio and the
   seven chart tools read as two groups either side of the search and the
   timeframes. A desktop's alone: the phone's header is its own layout. */
@media (min-width:701px){
  header > .ind + .ind{margin-left:-4px}
}

.spacer{flex:1}
.up{color:var(--up)} .down{color:var(--down)}
/* THE FEED'S READOUTS ARE WORDS, NOT CAPSULES. What the latency, the history
   and the source ARE is in their text and its colour; a ring round each one
   was four more boxes in a row that already had thirteen. The border stays,
   transparent, so the row keeps its measured width. */
/* DEV-SWITCH - the source pill as the TWS/Intrinio switch (paintStocksSwitch). */
.pill.switch{cursor:pointer}
.pill.switch::after{content:" ⇄"; color:var(--gold)}
.pill.switch:hover{color:var(--text); border-color:var(--gold)}
/* When the row is short the switch keeps the source and drops the contract
   code (fitHeader), and if it is still short it stands beside the search box
   rather than at the end of the row that falls off the screen. */
#src.short .sc{display:none}
header.sw-front > .finder ~ *{order:1}
header.sw-front > #src{order:0}
/* ...and in that last state the search box is the one that gives: at 320px the
   row was still 20px over, and what fell off was the bell. */
header.sw-front > .finder{min-width:36px}
.pill{
  display:inline-flex; align-items:center; height:22px; padding:0 6px; white-space:nowrap;
  font:500 11.5px/1 var(--num); font-variant-numeric:tabular-nums; color:var(--muted);
  background:transparent; border:1px solid transparent; border-radius:var(--r-sm);
}
.lat{min-width:70px; justify-content:center}
.lat.good{color:var(--up)}
.lat.mid{color:var(--warn)}
.lat.bad{color:var(--down)}
/* The two that are STATES rather than readouts keep a soft fill - the analyst
   present, and the one warning in the row - on the resting-control pattern. */
.pill.agent{color:var(--agent); background:var(--agent-soft); box-shadow:inset 0 0 0 1px rgba(110,169,255,.3)}
.pill.unsaved{color:var(--down); background:var(--down-soft); box-shadow:inset 0 0 0 1px rgba(255,93,115,.3)}
.status{display:flex; align-items:center; gap:7px; color:var(--muted); font-size:11.5px; font-weight:500; white-space:nowrap}
.dot{width:8px; height:8px; border-radius:50%; background:var(--dim); transition:background .2s}
.dot.live{background:var(--up); position:relative}
.status{isolation:isolate}
.dot.live::after{
  content:""; position:absolute; z-index:-1; left:50%; top:50%; width:20px; height:20px; margin:-10px 0 0 -10px;
  border-radius:50%; background:rgb(47,208,138); opacity:.18; transform:scale(.7);
  animation:breathe 2.4s ease-in-out infinite; pointer-events:none;
}
.dot.connecting{background:var(--warn); animation:blink 1s ease-in-out infinite}
.dot.delayed{background:var(--warn); box-shadow:0 0 0 3px var(--warn-soft)}
.dot.error{background:var(--down); box-shadow:0 0 0 3px var(--down-soft)}

/* The header holds a lot; when the window is narrow the least important
   figures step aside rather than pushing the feed status off the edge. */
@media (max-width:1440px){ #span{display:none} }
@media (max-width:1280px){
  .finder{flex-basis:180px}
  .tfs button{padding:0 7px}
  .status #statusTxt{display:none}
}
@media (max-width:1120px){ .lat{display:none} .brand span{display:none} }


/* ---- the chart ---- */
#wrap{position:relative; flex:1; min-height:0; background:var(--bg)}
#chart{position:absolute; inset:0}
#legend{
  position:absolute; top:10px; left:12px; z-index:3; pointer-events:none;
  /* IT STOPS AT THE PRICE AXIS, ON EVERY SCREEN.
     The phone got this on 2026-09-13 and the desktop did not, which left the
     desktop legend with NO right bound at all: it is positioned from the LEFT
     and grows with its content, so a long instrument name, a compared symbol
     or a wider price ran it under the axis labels AND under the rail at the
     top of the scale - the fit, copy and FULLSCREEN buttons. Reported from the
     desktop, 2026-09-13: "the price high low volume window is outside the full
     screen button".
     `--pad-right` is the scale's real width, published by repositionOverlay()
     because only it knows - the number changes with the digits in it - and the
     rail is exactly that column, so one bound keeps the legend off both. Wide
     enough and nothing changes: the legend is far short of it.

     WHAT IT LOOKED LIKE, because the mechanism is not the obvious one: `#zoom`
     is one node LATER in the markup at the SAME z-index, and its patch is
     opaque for its first 34px. The legend is at top:10px and about 30px tall,
     so the overlap hid y=10..34 and left the strip from 34..40 showing - "I
     can see only the bottom part of him". Not a clipped box: a box painted
     over. Raising the legend above the rail would be the WRONG fix - the rail
     holds the way out of fullscreen, and a control that leaves a mode must
     stay visible and hittable from inside it. They must not overlap at all.

     The fallback is 74px and NOT 0: `--pad-right` is published by the first
     repositionOverlay(), and until then `#zoom` is already 74px wide by its
     own fallback. Two fallbacks that disagree is the same overlap for the
     first few frames of every load. */
  max-width:calc(100% - var(--pad-left,0px) - var(--pad-right,74px) - 24px);
  font:12px/1.6 var(--num); font-variant-numeric:tabular-nums; color:var(--muted);
  /* The frame is transparent rather than gone, so the box keeps its size and
     `--lights-top` still lands under it. A readout lying on the pane needs a
     ground to be read against, not an edge to be noticed by. */
  background:rgba(11,14,19,.78); padding:6px 11px; border-radius:8px; border:1px solid transparent;
  box-shadow:0 1px 2px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.035);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
}

/* ---- the stage, on the pane ------------------------------------------------
   What is on the price surface and what is folded off it. Under the legend,
   sharing its two bounds for the reasons written over it: positioned from the
   LEFT and stopped at the price axis by `--pad-right`, so a long list can never
   run under the axis labels or the rail at the top of the scale.

   A CHIP, NOT A PANEL, and that is the feature taking its own advice. This
   exists because a setup drawn under a dozen support lines cannot be read; a
   permanent list of switches standing over the top-left candles is the same
   complaint in a different place. Closed it is one row, ~120px, saying what is
   NOT on screen. It opens on a press and closes on the next one.

   `pointer-events` is the one thing it does NOT inherit from the legend: the
   legend is a readout and lets every gesture through to the chart under it,
   and this is the one piece of furniture in the corner that has to take a
   press. It is bounded to its own content width so the pane either side of it
   still pans. */
/* THE TOP IS THE LEGEND'S BOTTOM, AND IT IS A VARIABLE BECAUSE THE LEGEND IS
   NOT THE SAME HEIGHT ON EVERY SCREEN. 42px is the desktop legend (one 12/1.6
   row plus `.row2`, plus its 6px padding and its own top:10px) with a few
   pixels under it. The phone clamps the legend to TWO 11/1.5 lines and hides
   `.row2`, which is 41px of box from a top of 10 - so a 42px chip sat across
   the legend's second line, over the Close and the Volume (reported from the
   phone, 2026-09-22). One name, set beside each legend rule, rather than a
   second hard-coded number that the next change to the legend will not know
   about. */
#lights{
  position:absolute; z-index:4; top:var(--lights-top,42px); left:12px;
  max-width:calc(100% - var(--pad-right,74px) - 24px);
  display:flex; flex-direction:column; align-items:flex-start; gap:4px;
  font:500 11px var(--sans);
}
#lights[hidden]{display:none}
/* THE COLUMN IS AS WIDE AS ITS WIDEST ROW, AND ITS EMPTY PART IS CHART. Open,
   the reading's name is ~650px wide on a phone and the parts under it are
   short, so the box beside them was a dead patch a finger could not pan or
   pinch through (reported from the phone, 2026-10-06). Only what is drawn
   takes a touch; the gaps fall through to the candles. */
#lights{pointer-events:none}
#lights button, #lights input, #lights select, #lights .lrow, #lights .lbox{pointer-events:auto}
/* IN FULLSCREEN `#wrap` STARTS AT y=0, UNDER THE NOTCH. The header stands in
   that band in every other layout and carries the inset for everything below
   it; take the header away and anything anchored to the top of the pane is
   anchored to the hardware. `#zoom` paid for this once and `#legend` paid for
   it again - this is the third, and it is global because `--sat` is 0 on a
   screen with no notch. `tests/phone.test.mjs` holds the general form. */
body.fs #lights{top:calc(var(--lights-top,42px) + var(--sat,0px))}
#lights button{
  display:flex; align-items:center; gap:6px; cursor:pointer;
  background:rgba(13,16,22,.84); color:var(--muted);
  border:1px solid transparent; border-radius:999px;
  box-shadow:0 1px 2px rgba(0,0,0,.3);
  padding:0 9px; height:24px; white-space:nowrap;
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
}
#lights button:hover{color:var(--text); background:rgba(26,32,43,.9)}
#lights .head svg{width:12px; height:12px; fill:none; stroke:currentColor; stroke-width:1.8}
/* The chevron turns rather than swapping glyph: one node, and the direction is
   the state. */
#lights .head .cv{
  width:0; height:0; margin-left:1px;
  border-left:3.5px solid transparent; border-right:3.5px solid transparent;
  border-top:4px solid currentColor; transition:transform .12s ease;
}
#lights.open .head .cv{transform:rotate(180deg)}
#lights .rows{display:flex; flex-direction:column; align-items:flex-start; gap:3px}
/* A ROW SAYS ITS STATE THREE WAYS, and none of them is colour alone: the dot
   is filled or hollow, the text is bright or dim, and the count is always
   there. A folded group whose only signal was a dimmer grey would be a mark
   stored and not drawn with nothing saying so (rule 1). */
#lights .lt i{
  width:7px; height:7px; border-radius:50%; flex:none;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.35);
}
#lights .lt:not(.on) i{background:none !important; box-shadow:inset 0 0 0 1.5px var(--dim)}
#lights .lt.on{color:var(--text); border-color:var(--line2)}
#lights .lt b{font:500 10px var(--num); color:var(--dim); font-variant-numeric:tabular-nums}
#lights .lt.on b{color:var(--muted)}
/* TWO LEVELS, AND THE INDENT IS WHAT SAYS SO. The outer row is the READING -
   one analysis, one name - and the inner ones are its parts. A reading's own
   dot is the agent's blue filled or a hollow ring, because the question it
   answers is "is this one on", not "what colour is it drawn in"; its parts
   carry their own ink. */
#lights .lt.an{font-weight:600; max-width:280px}
#lights .lt.an i{background:var(--agent)}
#lights .lt.an span{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
#lights .lt.sub{margin-left:16px; font-size:10.5px; height:22px; padding:0 8px}
/* THE READING AND THE WAY TO BE RID OF IT, IN ONE ROW. The cross is a sibling
   of the switch rather than a corner of it - two questions, two controls - and
   it is the width of one glyph until it is ARMED, at which point it says what
   it is about to do and how much of it. Dim until the row is under the pointer
   on a desktop; on a phone there is no hover, so it is simply always visible
   and the arming is what stands in for the hesitation a hover buys. */
#lights .anrow{display:flex; align-items:center; gap:3px}
#lights .del{
  width:24px; padding:0; justify-content:center; flex:none;
  color:var(--dim); font-size:11px; line-height:1; opacity:.55;
  transition:opacity .12s ease;
}
#lights .anrow:hover .del{opacity:1}
#lights .del:hover{color:var(--stop); border-color:var(--stop)}
/* ARMED IT IS THE ONE ORANGE THING ON THE CHIP, and it is wider because it now
   carries words: a second press on something that looks identical to the first
   would be the confirmation saying nothing. */
#lights .del.armed{
  width:auto; padding:0 9px; opacity:1;
  color:var(--stop); border-color:var(--stop); font-weight:600;
}
/* The reading's name can be a whole sentence, so it is the one thing here
   allowed to be elided - and `dir:auto` because half of what the analyst
   writes is Hebrew and a name measured left-to-right reads backwards. */
#lights .lt.an span, #lights .head .nm{direction:ltr; unicode-bidi:plaintext}
#lights .head .nm{max-width:210px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
#lights .head b{font:500 10px var(--num); color:var(--warn); font-variant-numeric:tabular-nums}
#lights .all{display:flex; gap:3px; margin-top:1px}
#lights .all button{height:20px; padding:0 7px; font-size:10px; color:var(--dim)}
#lights .all button:disabled{opacity:.4; cursor:default}
/* THE LIST OF INDICATORS: what the I panel has on the chart, a row each, each
   line in its own ink - the key to the colours and the list of what is on
   (paintLayRow()). A head that folds it, then rows: the NAME with its bulb
   (picture out and back), the line numbers (a press lights one), a gear (the
   layer's settings, inside the list) and a cross (off). Quiet by default: the
   word muted, the numbers in their ink, the gear and the cross dim until the
   row is under a pointer. */
/* BOUNDED BY THE PANE, AND IT SCROLLS INSIDE IT. On a phone on its side the
   pane is ~220px, and Pearson's settings opened under their row ran past its
   foot - two buttons nobody could reach (measured in WebKit, 2026-10-05). The
   stage chip's column is capped at the room under its own top, and the list is
   the part that gives way, so the readings' head under it keeps its size. */
#lights{max-height:calc(100% - var(--lights-top,42px) - 8px)}
#lights .layrow{display:flex; flex-direction:column; align-items:flex-start; gap:3px; max-width:100%;
  flex:0 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; scrollbar-width:none;
  padding-bottom:2px}
#lights .layrow::-webkit-scrollbar{display:none}
#lights .layrow .lhead .cv{
  width:6px; height:6px; border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor;
  transform:rotate(45deg); margin:-3px 1px 0 2px; opacity:.7; transition:transform .15s}
#lights .layrow .lhead[aria-expanded="true"] .cv{transform:rotate(225deg); margin-top:3px}
#lights .layrow .lhead svg{width:12px; height:12px; fill:none; stroke:currentColor; stroke-width:1.8}
#lights .layrow .lrows{display:flex; flex-direction:column; align-items:flex-start; gap:3px}
#lights .layrow .lrow{
  display:flex; align-items:center; gap:1px; height:24px; padding:0 3px 0 2px; border-radius:999px;
  background:rgba(13,16,22,.84); box-shadow:0 1px 2px rgba(0,0,0,.3);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  font:500 11px var(--num); font-variant-numeric:tabular-nums; white-space:nowrap;
}
#lights .layrow .lrow button{
  height:20px; padding:0 5px; border-radius:999px; border:0; box-shadow:none;
  background:none; -webkit-backdrop-filter:none; backdrop-filter:none; font:inherit;
}
#lights .layrow .lrow button.lname{gap:6px; padding:0 7px 0 6px; color:var(--text); font:500 11px var(--sans)}
#lights .layrow .lrow .bulb{
  width:7px; height:7px; border-radius:50%; background:var(--gold);
  box-shadow:0 0 6px rgba(228,199,120,.55); transition:background .15s, box-shadow .15s}
#lights .layrow .lrow.dark .lname{color:var(--dim)}
#lights .layrow .lrow.dark .bulb{background:none; box-shadow:inset 0 0 0 1.5px var(--dim)}
#lights .layrow .lrow em{font:500 10.5px var(--sans); font-style:normal; color:var(--dim); margin:0 3px 0 6px}
#lights .layrow .lrow em.idle{opacity:.6; margin:0 4px 0 2px}
#lights .layrow button.ll{color:var(--c); opacity:.9; padding:0 4px; transition:background .15s, opacity .15s}
#lights .layrow button.ll.sw i{width:14px; height:2px; border-radius:1px; background:var(--c)}
#lights .layrow button.ll:hover{background:rgba(255,255,255,.06); color:var(--c); opacity:1}
#lights .layrow button.ll.on{
  opacity:1; font-weight:600;
  background:color-mix(in srgb, var(--c) 24%, transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--c) 55%, transparent);
}
#lights .layrow button.ll.off{opacity:.35}
#lights .layrow .lv{font:500 10.5px var(--num); color:var(--text); margin:0 3px 0 4px}
#lights .layrow .lrow button.ledit,#lights .layrow .lrow button.ldel{
  width:22px; padding:0; justify-content:center; color:var(--dim); opacity:0; transition:opacity .15s, color .15s}
#lights .layrow .lrow:hover button.ledit,#lights .layrow .lrow:hover button.ldel,
#lights .layrow .lrow button.ledit.on{opacity:1}
#lights .layrow .lrow button.ledit{margin-left:4px}
#lights .layrow .lrow button.ledit svg{width:13px; height:13px; fill:none; stroke:currentColor; stroke-width:1.8}
#lights .layrow .lrow button.ledit:hover,#lights .layrow .lrow button.ledit.on{color:var(--gold)}
#lights .layrow .lrow button.ldel{font-size:10px}
#lights .layrow .lrow button.ldel:hover{color:var(--stop)}
/* The settings, inside the list, under their row: the I panel's own controls
   on the I panel's ground - a box that grows with them, never a window. */
#lights .layrow .lbox{
  margin:0 0 2px 10px; padding:5px 8px; border-radius:10px; min-width:240px; max-width:340px;
  background:rgba(16,20,27,.95); box-shadow:0 4px 14px rgba(0,0,0,.45), inset 0 0 0 1px var(--line);
  display:flex; flex-direction:column; gap:2px;
}
#lights .layrow .lbox .ctl{
  display:flex; align-items:center; flex-wrap:wrap; gap:4px 6px; padding:2px 0;
  font:11px var(--sans); color:var(--muted);
}
#lights .layrow .lbox .ctl .lbl{min-width:64px}
#lights .layrow .lbox .ctl button{
  height:22px; padding:0 8px; border-radius:var(--r-sm); border:1px solid transparent; box-shadow:none;
  background:var(--hover); color:var(--muted); font:500 11px var(--sans);
  -webkit-backdrop-filter:none; backdrop-filter:none;
}
#lights .layrow .lbox .ctl button:hover{color:var(--text); background:var(--press)}
#lights .layrow .lbox .ctl button.armed{color:var(--warn); background:var(--warn-soft)}
#lights .layrow .lbox .ctl .chips{display:flex; flex-wrap:wrap; gap:4px}
#lights .layrow .lbox .ctl .chips button.pick{
  /* `.pick` is also the drawing-pick overlay's class (absolute, inset 0, no
     pointer) - undone here, or the chips lie stacked and take no press.
     RELATIVE and never static: on a finger every `#lights button` carries an
     `::after` grown 5px round it, and a static button hands that box to the
     nearest positioned ancestor - so the LAST chip's grew over the whole list
     and every tap in it landed on that one chip (Dor, on the iPhone: the chips
     took no press, the lines changed anyway). */
  position:relative; inset:auto; z-index:auto; pointer-events:auto; overflow:visible; color:var(--dim); background:var(--raise); font:500 11px var(--num)}
#lights .layrow .lbox .ctl .chips button.pick.on{
  color:var(--gold); background:linear-gradient(var(--gold-soft),var(--gold-soft)),var(--raise);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#lights .layrow .lbox .ctl input[type=number]{
  height:22px; width:66px; padding:0 6px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r-sm); font:11.5px var(--num)}
#lights .layrow .lbox .ctl input[type=number]:focus{border-color:var(--gold-line); box-shadow:var(--ring); outline:none}
#lights .layrow .lbox .ctl select{
  height:22px; min-width:84px; padding:0 4px; line-height:20px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r-sm); font:11px var(--sans)}
#lights .layrow .lbox .ctl .cur{color:var(--dim); font:10.5px var(--num); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%}

/* THE STAGE ROWS IN THE D PANEL wear the marks ladder's own shape, because
   they are read in the same glance as it - `.mk` is that row, and only the
   state word and the dot differ. */
#drawPanel .marks.stage .mk em{color:var(--dim); text-transform:uppercase; letter-spacing:.04em}
#drawPanel .marks.stage .mk.on em{color:var(--agent)}
#drawPanel .marks.stage .mk:not(.on){opacity:.62}
#drawPanel .marks.stage .mk:not(.on) i{background:none !important; box-shadow:inset 0 0 0 1.5px var(--dim)}

#legend b{color:var(--text); font-weight:500}
#legend .lid>b{font:600 13px var(--sans); color:#fff}
/* The company's mark before its name - the rail's own face, a step smaller
   than the name's cap height allows to look heavy. Inline, so the legend's
   two clamped lines on a phone still clamp; its letter keeps its own colour
   rather than the legend's `b`. */
#legend .lmark:empty{display:none}
/* CENTRED ON THE NAME'S CAPITALS, MEASURED (2026-10-05, Dor: "a little
   below the word"). An inline grid sits on the baseline of the letter INSIDE
   it, so its own offset moved nothing and the mark's middle sat on the name's
   baseline, 4.5px under the middle of the word. The wrapper is an
   inline-block with overflow hidden, whose baseline is its BOTTOM edge - so
   the offset is exact: 16px tall, 13px name, cap height 0.7em, middle of the
   capitals 4.5px over the baseline -> bottom 3.5px under it. */
#legend .lmark{display:inline-block; overflow:hidden; width:16px; height:16px;
               vertical-align:-3.5px; margin-right:6px}
#legend .wlogo{width:16px; height:16px}
#legend .wlogo b{font:600 8.5px/1 var(--sans); color:hsl(var(--lh,210) 45% 82%)}
#legend b.up{color:var(--up)} #legend b.down{color:var(--down)}
#legend .avg{font-style:normal; color:var(--dim); font-size:11px}
#legend .sess{font:500 10px var(--sans); font-style:normal; color:var(--dim); border:1px solid var(--line); border-radius:999px; padding:1px 6px; margin-left:4px; vertical-align:middle}
#legend .sess.pre{color:rgba(56,189,248,.9); border-color:rgba(56,189,248,.35)}
#legend .sess.after{color:rgba(248,231,176,.95); border-color:rgba(248,231,176,.4)}
#legend .sess.overnight{color:rgba(148,163,184,.9); border-color:rgba(148,163,184,.35)}
/* a continuous futures bar from before its contract was the front month */
#legend .sess.deferred{color:rgba(148,163,184,.9); border-color:rgba(148,163,184,.35)}
#legend .cl{display:inline-flex; align-items:center; gap:5px; margin-left:12px}
#legend .cl:first-of-type{margin-left:0}
#legend .row2{display:block; margin-top:2px}
/* THE CHEVRON THAT PUTS BOTH ROWS UNDER THE LEGEND AWAY (paintTuck). It is
   the FIRST thing on the legend's own line and costs no row: in the flow, so
   it is as wide as its count and pushes the numbers along rather than sitting
   on them, and at the start, so neither a clamp nor an ellipsis can take it
   and the distance tags at the line's right end never cover it. The legend
   lets every gesture through to the chart; this one button takes a press. */
#legend .tuck{
  display:inline-flex; align-items:center; justify-content:center; gap:4px;
  vertical-align:middle; height:20px; min-width:24px; padding:0 6px; margin-right:8px;
  pointer-events:auto; cursor:pointer; color:var(--muted);
  background:rgba(255,255,255,.05); border:1px solid var(--line); border-radius:999px;
}
#legend .tuck[hidden]{display:none}
#legend .tuck:hover{color:var(--text); background:rgba(255,255,255,.09)}
/* `middle` sits a box on the baseline plus half an x-height, a hair under the
   middle of the figures (which are capitals' height), so it is lifted by that
   hair - measured, so the chevron is centred on the text beside it. */
#legend .tuck{position:relative; top:-1.9px}
#legend .tuck .cv{
  width:6px; height:6px; border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor;
  transform:rotate(225deg); margin-top:3px; transition:transform .15s}
#legend .tuck[aria-expanded="false"] .cv{transform:rotate(45deg); margin-top:-3px}
.sw{display:inline-block; width:8px; height:8px; border-radius:50%; vertical-align:middle; margin-right:5px}
#msg{
  position:absolute; inset:0; display:none; place-items:center; z-index:4; padding:24px;
  background:rgba(11,14,19,.86); backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
}
#msg .box{max-width:520px; text-align:center; color:var(--muted); line-height:1.7}
/* A NOTE IS NOT A CURTAIN. The scrim, the blur and the spinner belong to a
   chart that cannot be looked at yet; a remark about the gesture you just made
   must leave the chart visible and must not eat the next tap - the whole point
   of it is that you carry on drawing. It sits under the strip rather than in
   the middle of the pane, and it takes itself down (`NOTE_MS`). */
#msg.note{
  background:none; backdrop-filter:none; -webkit-backdrop-filter:none;
  place-items:start center; padding:12px; pointer-events:none;
}
#msg.note .box{
  max-width:min(340px,100%); padding:8px 12px; border-radius:var(--r-lg);
  background:rgba(11,14,19,.94); border:1px solid var(--gold-line);
  color:var(--text); font-size:12.5px; line-height:1.5;
  /* `drop`, not `dropx`: this box is centred by the grid rather than by a
     transform of its own, so the -50% one would shove it off its own centre. */
  box-shadow:0 6px 18px rgba(0,0,0,.5); animation:drop .16s var(--ease);
}
#msg.loading .box::before{
  content:""; display:block; width:22px; height:22px; margin:0 auto 14px; border-radius:50%;
  border:2px solid var(--line2); border-top-color:var(--accent); animation:spin .8s linear infinite;
}
#msg .t{color:var(--text); font-weight:600; font-size:14px}
#msg .acts{display:flex; gap:8px; justify-content:center; margin-top:16px}
#msg .acts a,#msg .acts button{
  display:inline-flex; align-items:center; height:28px; padding:0 12px; border-radius:var(--r-sm);
  font:500 12.5px var(--sans); cursor:pointer; text-decoration:none;
  background:var(--gold-soft); color:var(--gold); border:1px solid transparent;
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
}
#msg .acts button{background:var(--hover); color:var(--text); box-shadow:none}
#msg .acts button.strong{background:var(--gold-sheen); color:#1a1408; border-color:transparent; font-weight:600}
#msg .hint{margin-top:10px; font-size:12px; color:var(--dim)}
#msg .acts a:hover,#msg .acts button:hover{filter:brightness(1.15)}
#msg code{
  display:inline-block; background:var(--panel2); border:1px solid var(--line2);
  padding:6px 10px; border-radius:5px; color:var(--text);
  font:12px var(--mono); margin-top:8px; user-select:all;
}

/* ---- the strip: the quote bar ----
   ONE LINE, and that is the design. The strip sits between the header and the
   candles, so every pixel of it is a pixel off the chart: the caption, the
   figure and the basis it was measured from run along a single 28px row, the
   caption INLINE before its number rather than stacked over it. Stacked, each
   tile was three lines tall and a chart carrying the session, the volatility,
   a gap and a chain stood two rows deep - eighty pixels of quote bar over a
   chart that wanted them.

   The price and the day's change lead it, the market cap follows, then one
   tile per layer that has something to say, in a fixed order, so the same
   number is in the same place on every chart. Tiles are ruled off from each
   other; the rule at the start of the row is clipped away, because a rule to
   nothing is noise.

   The row never wraps and the page never scrolls sideways: when the tiles run
   past the edge, fitStrip() hides them from the right - the least important
   end, by stripRank - and counts them into the `+n` chip, exactly as
   fitHeader() makes the header give way. `S` puts everything back: `.full` is
   the one state ON SCREEN allowed to be more than one line deep, and it wraps
   into thin rows rather than growing tall ones. Flat backgrounds only:
   snapshot.js paints this row into the exported picture. */
#strip{
  display:flex; align-items:center; flex:none; height:28px;
  padding:0 12px; background:var(--panel); border-bottom:1px solid var(--line);
  font:11.5px/1.2 var(--num); font-variant-numeric:tabular-nums; color:var(--muted);
}
/* Two states may be more than one line deep, and only two: `.full`, which is
   the user asking for every number (S), and `.wrapped`, the copy snapshot.js
   lays out at the picture's width - a picture has no `+n` to click, so what is
   in it wraps rather than being cut off at the edge. */
#strip.full,#strip.wrapped{
  height:auto; min-height:28px; padding-top:2px; padding-bottom:2px;
  /* the price belongs on the first of the wrapped rows, not floating in the
     middle of them */
  align-items:flex-start;
}
#layers{
  display:flex; flex:1; align-items:center; flex-wrap:nowrap; min-width:0;
  overflow:hidden; margin-left:-1px; clip-path:inset(0 0 0 1px);
}
#strip.full #layers,#strip.wrapped #layers{flex-wrap:wrap; overflow:visible; row-gap:1px}
/* A tile is one line: the caption, the figure, then the quieter half of it -
   the percentile, the points, the close the move was measured from - inline
   after the figure rather than under it. `flex:none` is load-bearing: a tile
   that shrinks spills its own text instead of overflowing the row, and
   fitStrip() would then never see that it does not fit. */
#strip .t{
  display:flex; align-items:baseline; gap:6px; flex:none;
  padding:3px 10px; white-space:nowrap;
}
/* The class rule above outranks the browser's own [hidden], so say it again. */
#strip .t[hidden]{display:none}
/* THE RULES BETWEEN TILES ARE SHORT, NOT FULL HEIGHT (the look pass): a rule
   the height of the row is a column of boxes; a short one centred on the type
   is punctuation. The border keeps its width - fitStrip() measures tiles with
   it - and only its ink is drawn shorter. snapshot.js paints a border only
   from its TOP edge, so a left or right rule was never in the picture and
   still is not. */
#strip .t.vital{padding-left:0; padding-right:11px; border-right:1px solid var(--line);
  border-image:linear-gradient(to bottom, transparent 24%, var(--line2) 24%, var(--line2) 76%, transparent 76%) 1}
#strip .t.vital + .t.vital{padding-left:10px}
#layers .t{border-left:1px solid var(--line);
  border-image:linear-gradient(to bottom, transparent 24%, var(--line2) 24%, var(--line2) 76%, transparent 76%) 1}
#strip .t em{
  font:500 10.5px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase;
  color:var(--dim); font-style:normal;
}
#strip .t .v{display:flex; align-items:baseline; gap:8px; line-height:1.2}
#strip .t small{font:10.5px/1.2 var(--num); color:var(--dim)}
/* An empty second half is not a gap: the tile's own `gap` would still hold
   space open for it. */
#strip .t small:empty{display:none}
#strip .t small.up{color:var(--up)} #strip .t small.down{color:var(--down)}
/* The price leads: the one figure in the row set larger than the rest. */
#strip .t.px .v b{font:600 17px/1 var(--num); color:var(--text); letter-spacing:-.015em}
#strip .t.px .v b.up{color:var(--up)} #strip .t.px .v b.down{color:var(--down)}
/* THE BLINK A TICK GETS (`tickClass()` in index.html): a wash of the move's own
   colour under the price, held for a third of a second so the eye can catch
   it and then settled to nothing - the rail's tick flash told at the size of
   the one number the page exists to show. Padding paid back by
   margin, so the figure's width - which fitStrip() measures - does not move. */
/* ...AND THE WASH IS ITS OWN LAYER, FADED, not the figure's background
   recoloured: a tick arrives every quarter second on a live coin, so the
   background-color animation never stopped and cost a style pass and a paint
   every frame. A colour fading to transparent IS that colour fading in
   opacity, so the look is the same; `isolation` keeps the wash behind the
   digits and in front of nothing else.
   ⚠ ONE PICTURE DOES CHANGE: snapshot.js clones the strip and paints each
   node's own background, so with the wash on `background-color` every
   exported picture froze a fresh flash under the price (the clone restarts the
   animation at its 0% - measured, rgba .34 on the clone with nothing on the
   screen). A pseudo-element is not painted there, so the picture now shows
   the price as the screen does at rest. */
#last{border-radius:4px; padding:0 3px; margin:0 -3px; position:relative; isolation:isolate}
#last::before{
  content:""; position:absolute; inset:0; z-index:-1; border-radius:inherit;
  background:var(--tick, transparent); opacity:0; pointer-events:none;
}
@keyframes tickA{0%,30%{opacity:1}100%{opacity:0}}
@keyframes tickB{0%,30%{opacity:1}100%{opacity:0}}
#last.tick-up-a,#last.tick-up-b{--tick:rgba(47,208,138,.34)}
#last.tick-down-a,#last.tick-down-b{--tick:rgba(255,107,124,.34)}
#last.tick-up-a::before,#last.tick-down-a::before{animation:tickA 1.1s ease-out}
#last.tick-up-b::before,#last.tick-down-b::before{animation:tickB 1.1s ease-out}
.chg{
  white-space:nowrap; padding:3px 6px; border-radius:var(--r-xs);
  font:500 11.5px/1 var(--num); font-variant-numeric:tabular-nums; color:var(--muted);
}
.chg.up{background:var(--up-soft); color:var(--up)} .chg.down{background:var(--down-soft); color:var(--down)}
#strip .t.cap .v b{font-weight:500; color:var(--text)}
/* The share count and the filing it came off fold on the one line, and only
   here: the day's move changes every tick and the close it is measured from is
   what makes it checkable, while a share count moves once a quarter and its
   filing date is a thing you go and look at when you care. The tile's own
   tooltip carries both, and `S` puts them back on the row. */
#strip:not(.full) .t.cap small{display:none}
/* The hours around the session ride on the price tile's own line, small and
   after the day's move rather than under it: the figure is honestly +0.00% on
   a stock that had no overnight prints, and a number that is usually zero must
   not be set as loud as the one that never is. The word is dim, the percent
   takes its own sign's colour - which is not always the day's. */
#strip .t small i.sess{font-style:normal; color:var(--dim)}
#strip .t small b{font-weight:500}
#strip .t small b.up{color:var(--up)} #strip .t small b.down{color:var(--down)}
#strip .t small b.xpx{color:var(--text)}
#strip .t small .sw{display:none}
/* Exactly zero is neither, on this figure only: most stocks do not trade after
   the bell, so it is 0.00% most evenings and a nightly green would stop the
   colour meaning anything. */
#strip .t small b.flat{color:var(--dim)}
#strip .s{white-space:nowrap}
#strip .s b{font-weight:500; color:var(--text); font-size:12.5px}
#strip .s.up b{color:var(--up)} #strip .s.down b{color:var(--down)}
#strip .s.warn b{color:var(--warn)}
/* A triggered formation is neither up nor down - the direction is in the label
   - it is "this one is live", which is what the agent's own colour says. */
#strip .s.good b{color:var(--agent)}
#strip .s i{font-style:normal; color:var(--dim); margin-left:3px; font-size:10.5px}
#strip .s .sw{margin-right:4px; position:relative; top:-1px}
/* Warnings and hints, never folded - so they are bounded rather than dropped:
   a third of the row at most, cut with an ellipsis, and the whole sentence in
   the tooltip. Bounded rather than shrinking to nothing, or a long warning
   would squeeze every tile out of the row and never look like it overflowed. */
#strip .note{
  flex:none; max-width:34%; align-self:center; padding:0 0 0 12px;
  color:var(--dim); font:11px/1.3 var(--sans);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
#strip.full .note,#strip.wrapped .note{max-width:100%; white-space:normal}
/* The one control in the row: how much is folded, and the way in. */
#strip .fold{
  flex:none; align-self:center; margin:0 0 0 auto; height:17px; padding:0 7px; min-width:30px;
  border-radius:var(--r-xs); cursor:pointer;
  background:var(--hover); border:1px solid transparent; color:var(--muted);
  font:500 10.5px/1 var(--num); letter-spacing:.02em;
  transition:color var(--dur-1), background-color var(--dur-1);
}
#strip .fold:hover{color:var(--text); background:var(--press)}
#strip .fold.less{font-family:var(--sans)}

/* ---- a glossary term, and the definition behind it ----
   The app defines and the model interprets (rule 7 extended,
   docs/פרופיל-הקורא.md §7): these are decorations the page adds over text
   nothing has changed, in the strip's captions and in the analyst's answer.

   A DOTTED UNDERLINE AND NOTHING ELSE. Colour is spoken for on this page - the
   two temperatures say WHOSE a mark is, and the roles say what it claims - so
   a term takes the colour of whatever it is sitting in and says "there is more
   here" with a line that is dimmer than the words it is under. It is also why
   this never reaches the chart surface: nothing the code adds may compete with
   the candles (root CLAUDE.md, The skin), and the strip is chrome. */
.gl-term{
  cursor:pointer;
  /* THE WORD ITSELF IS THE MARK - gold letters, a little heavier, and nothing
     behind them (Dor, 2026-09-19, twice: first "the dotted line underneath
     does not look good", then "make the letters of the word gold and bold").
     A dotted underline is a footnote's manner - it says there is more about
     this somewhere else - and a wash behind the word is a marker pen through
     a paragraph nobody asked to have marked up. Gold because opening a
     definition is something the USER does (the two temperatures), and the
     weight is what carries it at 13px where colour alone would not. */
  color:var(--gl-term); font-weight:600;
  transition:color .12s ease;
}
.gl-term:hover,.gl-term.on{color:var(--gl-term-on)}
.gl-term:focus-visible{outline:1px solid var(--accent-line); outline-offset:1px; border-radius:3px}

/* The definition itself: one box for the whole page, fixed to the VIEWPORT so
   nothing it was opened from can clip it - the windows' own lesson. Above the
   spine (the dock is 13, a window 8000) and under #signin's 9000.
   A POPOVER, AND IT READS AS A PAGE OF A GLOSSARY: the word in the user's gold
   at title weight, a hairline, and the definition in the body face at the
   page's own white, a step down - the reader opened this to read it. */
#glpop{
  position:fixed; z-index:8500; width:min(340px, calc(100vw - 16px));
  padding:14px 16px 16px; border-radius:14px;
  /* A warmth at the head that fades by the first line of words, and an edge
     touched with the same gold: opening a definition is the USER's act, the
     same temperature the word that opened it is written in. */
  background:linear-gradient(180deg, color-mix(in oklab, var(--panel2) 92%, var(--gold) 8%) 0, var(--panel2) 64px);
  border:1px solid color-mix(in oklab, var(--line2) 70%, var(--gold) 30%);
  box-shadow:0 18px 50px -14px rgba(0,0,0,.75), 0 0 0 1px rgba(0,0,0,.35);
  font:13px/1.65 var(--sans); color:var(--text);
  animation:pop var(--dur-2) var(--ease-out);
}
#glpop[hidden]{display:none}
/* A gold rule across the top that opens from the middle each time the card
   does - the drawing's own manner, one line long. */
#glpop::before{content:""; position:absolute; top:0; left:12%; right:12%; height:1.5px; pointer-events:none;
  background:linear-gradient(90deg, transparent, var(--gold) 50%, transparent); opacity:.85;
  animation:glLine .7s var(--ease-out) both}
@keyframes glLine{from{transform:scaleX(0); opacity:0}to{transform:scaleX(1); opacity:.85}}
#glpop .gl-head{display:flex; align-items:flex-start; gap:10px; margin-bottom:10px}
#glpop .gl-title{flex:1; min-width:0}
#glpop .gname{display:block; font-size:16px; font-weight:650; line-height:1.25; letter-spacing:.005em; color:var(--gold)}
#glpop .gl-alt{display:block; margin-top:3px; font:500 11px/1.2 var(--sans); letter-spacing:.04em; color:var(--dim)}
#glpop .gl-alt[hidden]{display:none}
#glpop[dir=rtl] .gl-alt{text-align:right}
#glpop .gl-tag{flex:none; margin-top:2px; padding:3px 8px; border-radius:999px; border:1px solid var(--line2);
  background:rgba(255,255,255,.02); font:600 10px/1.2 var(--sans); letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted)}
#glpop .gl-tag:empty{display:none}
#glpop .gl-x{flex:none; width:24px; height:24px; margin:-3px -6px 0 0; padding:0; border:0; border-radius:7px;
  background:none; color:var(--dim); font:16px/24px var(--sans); cursor:pointer}
#glpop[dir=rtl] .gl-x{margin:-3px 0 0 -6px}
#glpop .gl-x:hover{color:var(--text); background:rgba(255,255,255,.05)}
/* 24px to the eye and 36px to a finger: the target grows, the mark does not. */
#glpop .gl-x{position:relative}
#glpop .gl-x::after{content:""; position:absolute; inset:-6px}
#glpop .gdef{margin:0; color:color-mix(in oklab, var(--text) 74%, var(--muted))}
#glpop .gl-lead{display:block; font-weight:500; color:var(--text)}
#glpop .gl-rest{display:block; margin-top:6px}
#glpop .gl-rest:empty{display:none}
/* A FORMATION, DRAWN SMALL, between its name and its words (web/glossary-shape.js).
   The chart's two inks - the price in the page's white, the formation's lines in
   `--shape` cyan - so the card teaches the picture the chart will paint. It
   draws itself once, price first and the lines after it, the way a hand would;
   reduced motion takes the animation off and leaves the finished drawing.
   And it is drawn IN LIGHT, the board's own look (docs/מצב-לימוד.md §34): on a
   dark plate ruled in the analyst's blue, every line glowing (`gs-glow`, an SVG
   filter - WebKit ignores a CSS drop-shadow inside an SVG), and a spark riding
   the price as it is drawn. */
#glpop .gshape{display:flex; gap:8px; margin:0 0 12px; padding:8px 8px 6px;
  border:1px solid rgba(110,169,255,.28); border-radius:10px;
  background:
    radial-gradient(120% 95% at 50% 0, rgba(110,169,255,.16), transparent 70%),
    repeating-linear-gradient(90deg, rgba(110,169,255,.07) 0 1px, transparent 1px 14px),
    repeating-linear-gradient(0deg, rgba(110,169,255,.05) 0 1px, transparent 1px 14px),
    #070a10}
#glpop .gshape figure{flex:1 1 0; min-width:0; margin:0}
#glpop .gs-pic{position:relative; width:100%; max-width:calc(var(--gs-max, 136px) * 120 / 52);
  aspect-ratio:120 / 52; margin:0 auto}
#glpop .gs-pic svg{display:block; width:100%; height:100%}
#glpop .gshape figcaption{margin-top:2px; text-align:center; font:500 11px/1.3 var(--sans); color:var(--muted)}
#glpop .gs-price{fill:none; stroke:#eef5ff; stroke-width:.95; stroke-linejoin:round; stroke-linecap:round;
  stroke-dasharray:1; animation:gsDraw .9s var(--ease-out) both; filter:url(#gs-glow)}
#glpop .gs-line{fill:none; stroke:var(--shape); stroke-width:.8; stroke-linecap:round;
  stroke-dasharray:1; animation:gsDraw .5s .75s var(--ease-out) both; filter:url(#gs-glow)}
#glpop .gs-label{position:absolute; transform:translate(-50%, -100%); white-space:nowrap;
  font:500 11px/1 var(--sans); letter-spacing:.02em; color:#e4eeff; pointer-events:none;
  text-shadow:0 0 6px rgba(110,169,255,.9), 0 0 2px #000;
  animation:gsFade .3s 1.1s both}
/* The spark is SMIL, which the reduced-motion switch above does not reach - so
   it goes with the rest of the motion, and the finished drawing stays. */
#glpop .gs-spark{color:#ffffff; filter:url(#gs-glow)}
@media (prefers-reduced-motion: reduce){ #glpop .gs-spark{display:none} }
/* Two side by side are each drawn at half the width, so the same pixels need
   twice the units - or the lines go hairline and the names unreadable. */
#glpop .gshape.two .gs-price{stroke-width:1.3}
#glpop .gshape.two .gs-line{stroke-width:1.05}
#glpop .gshape.two .gs-label{font-size:10px}
@keyframes gsDraw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
@keyframes gsFade{from{opacity:0}to{opacity:1}}

/* ---- the agent layer ----
   Text, vertical rules and shaded spans are DOM because v4 has no primitive
   for any of them. Nothing here takes the mouse: a note must never be the
   thing you grab when you meant to drag the chart. */
.ovl{position:absolute; inset:0; z-index:2; pointer-events:none; overflow:hidden}
.ovl>div{position:absolute}
.o-note,.o-label{
  max-width:250px; padding:4px 8px; border:1px solid; border-radius:6px;
  background:var(--ovl-box); font:11.5px/1.45 var(--sans);
  white-space:pre-wrap; box-shadow:0 4px 14px rgba(0,0,0,.45);
}
/* A label that is not a sentence. It loses the border, the shadow and most of
   the padding, and keeps just enough ground to stay legible over a candle.
   Mono and tabular, because a tag is almost always a number. Never wrapped: a
   tag is short by definition, and one that wraps has become a box again. */
.o-note.tag,.o-label.tag{
  /* A CAPTION IS NEVER WIDER THAN THE COLUMN IT LIVES IN.
     This was `max-width:none`, and the note said so on purpose: capped at
     180px the ground stopped where the box did and the text ran on past it
     over the candles, while offsetWidth still measured 180 so the tag was
     centred on the wrong bar. That bug was a cap WITHOUT a clip - the pair
     below is what makes a cap safe, and the whole of what was missing.
     `--cap-tag` is computed by capTagPx() and published by
     repositionOverlay(): the caption column applyGutter() buys, or a share of
     the pane on a phone where that column is narrower than the words are
     worth - AND SLID BY THE ZOOM, down to about two words once a bar is
     getting less than a couple of pixels, because a zoomed-out chart is a
     texture with no quiet part left for a word to sit on. Measured on SOFI's
     daily: a 344px context caption, the only thing left sitting on the candles
     once every other rule had done its work. */
  max-width:var(--cap-tag, 9999px);
  overflow:hidden; text-overflow:ellipsis;
  padding:1px 4px; border:0; border-radius:3px;
  background:var(--ovl-tag); font:500 10px/1.3 var(--mono);
  font-variant-numeric:tabular-nums; white-space:nowrap; box-shadow:none;
}
/* A CAPTION THE READER OPENED - a click on a clipped one, wired in
   index.html. It is the one caption here that is deliberately IN FRONT of
   whatever is under it: the placement routes every other one around its
   neighbours, and this one was asked for, so it takes the box's own ground
   back and sits above them. A sentence read through another sentence is not
   read. It lasts until the chart moves. */
.o-note.tag.open,.o-label.tag.open{
  z-index:4; background:var(--ovl-box); box-shadow:0 4px 14px rgba(0,0,0,.45);
}
/* THE HAIRLINE FROM A CAPTION BACK TO ITS OWN LINE.
   It exists to buy off the constraint every placement rule here is fighting -
   a caption may only move so far before it stops saying which line it belongs
   to - so that the words can go where the candles are not. 1px, its mark's own
   colour, half strength: it has to be followable and it must not read as
   another drawing. Two of them make an elbow; both are axis-aligned, because
   snapshot.js paints this layer from bounding boxes and a rotated hairline
   would export as a filled rectangle. */
.o-lead{ opacity:.5; border-radius:0 }

/* ---- a mark's words, as a symbol ----
   The words left the chart (web/pinread.js, asPins() in index.html): what
   stands in for them is the mark's ROLE as a glyph, ringed in the role's own
   colour, on the mark's own line. Always the same size - nothing here slides
   with the zoom, which is the whole point - and a press opens the sentence in
   the card below.

   `div.pin` and `.ovl>` are specificity, not decoration: the phone block
   restyles `.o-note.tag` and `.o-board` further down this file, and a symbol
   must not inherit a caption's ground, padding or width from it. Background,
   border, radius and a text glyph are all `snapshot.js` can paint, so a symbol
   is made of nothing else and exports as itself. */
.ovl>div.pin{
  box-sizing:border-box; width:20px; height:20px; min-width:0; max-width:none;
  max-height:none; padding:0;
  display:flex; align-items:center; justify-content:center;
  border:1.5px solid; border-radius:50%;
  background:var(--ovl-box); box-shadow:0 1px 5px rgba(0,0,0,.55);
  font:600 11px/1 var(--sans); font-variant-numeric:normal;
  white-space:nowrap; overflow:hidden; text-overflow:clip;
  -webkit-line-clamp:unset;
}
/* The scaffolding is read AGAINST the claims, so its symbol is a size down -
   the same ranking the slate colour already makes. */
.ovl>div.pin[data-role="structure"]{width:16px; height:16px; font-size:9px; border-width:1px}
/* The board is the one symbol that takes the pointer itself (it always has -
   see `.o-board`); it keeps that and gives up everything that made it a box.
   A rounded SQUARE and not a circle: it is a page about the whole chart, not a
   bead on one line, and it must not be read as belonging to whatever line it
   happens to have been parked on. */
.ovl>div.o-board.pin{cursor:pointer; overflow:hidden; padding:0; border-radius:5px}
/* `▸` is drawn small in every face that has it; a size up so it reads as a
   triangle at a glance rather than as a dot. */
.ovl>div.pin[data-role="trigger"]{font-size:14px}
/* A finger's symbols are a size up: in the coarse-pointer block, with every
   other target this page grows for a fingertip. */

/* ---- the card a symbol opens ----
   The sentence, big: the half of the pane the symbol is NOT in, the role's
   colour along its top edge so it is plain which kind of line it is about, and
   up/down to walk the rest. No scrim - the line it is about has to stay lit
   beside it, and the chart is never out of play (the analyst's sheet makes the
   same choice). Inside `#wrap`, so it sits over the candles and under every
   surface that is not the chart. */
.pinread{
  position:absolute; z-index:6;
  left:12px; right:calc(var(--pad-right, 60px) + 12px);
  margin:0 auto; max-width:540px; max-height:min(62%, 460px);
  display:flex; flex-direction:column;
  background:var(--panel2); color:var(--text);
  border:1px solid var(--line2); border-top:3px solid var(--c);
  border-radius:var(--r-lg); box-shadow:var(--shadow);
  pointer-events:auto; touch-action:pan-y;
  animation:prIn .16s var(--ease);
}
.pinread[hidden]{display:none}
.pinread[data-side="top"]{top:12px}
.pinread[data-side="bottom"]{bottom:calc(max(var(--pad-bottom, 28px), var(--pr-floor, 0px)) + 12px)}
/* Never taller than what is in view, whatever the share says. */
.pinread{max-height:min(62%, 460px, calc(var(--pr-room, 100vh) - 24px))}
.pinread.stepped{animation:none}
@keyframes prIn{from{opacity:0; transform:translateY(8px)} to{opacity:1; transform:none}}
.pr-head{display:flex; align-items:center; gap:10px; padding:10px 8px 4px 14px}
.pinread[dir="rtl"] .pr-head{padding:10px 14px 4px 8px}
.pr-pin{
  flex:none; box-sizing:border-box; width:26px; height:26px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  border:1.5px solid var(--c); color:var(--c); background:var(--ovl-box);
  font:600 14px/1 var(--sans);
}
.pr-role{flex:1; min-width:0; font:600 13px/1.2 var(--sans); color:var(--c); letter-spacing:.02em}
.pr-who{color:var(--muted); font-weight:500}
.pr-meta{flex:none; font:500 12px/1 var(--num); color:var(--muted); font-variant-numeric:tabular-nums}
.pr-x{
  flex:none; width:34px; height:34px; border:0; border-radius:8px;
  background:transparent; color:var(--muted); font:400 22px/1 var(--sans); cursor:pointer;
}
.pr-x:hover{color:var(--text); background:var(--hover)}
.pr-body{
  padding:4px 16px 14px; font:400 16px/1.6 var(--sans);
  white-space:pre-wrap; overflow-wrap:anywhere;
  overflow:auto; overscroll-behavior:contain;
  user-select:text; -webkit-user-select:text;
}
.pr-nav{
  display:flex; align-items:center; justify-content:center; gap:16px;
  padding:8px 10px 10px; border-top:1px solid var(--line);
}
.pr-nav button{
  width:48px; height:34px; border:1px solid transparent; border-radius:var(--r);
  background:var(--hover); color:var(--text); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  transition:background-color var(--dur-1), box-shadow var(--dur-1);
}
.pr-nav button:hover{background:var(--press); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--c) 45%, transparent)}
.pr-count{font:500 12px/1 var(--num); color:var(--muted); min-width:44px; text-align:center}
.pr-steps{display:flex; align-items:center; gap:16px}
.pr-nav.has-rm{justify-content:space-between}
.pr-nav .pr-rm{width:auto; padding:0 14px; color:var(--warn); font:500 13px/1 var(--sans)}

/* THE PICTURE'S KEY - the marks' words under an exported chart (snapshot.js),
   laid out offscreen at the picture's width and painted from these styles, so
   it is made of what that painter reads: a ground, a border, a radius, text. */
.snapkey{padding:8px 14px 12px; background:var(--bg); border-top:1px solid var(--line)}
.sk-row{display:flex; align-items:flex-start; gap:10px; padding:4px 0}
.sk-pin{
  flex:none; box-sizing:border-box; width:20px; height:20px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  border:1.5px solid; background:var(--ovl-box); font:600 11px/1 var(--sans);
}
.sk-text{flex:1; min-width:0; font:400 13px/1.5 var(--sans); color:var(--text); white-space:pre-wrap}

/* The pinned board: a paragraph ABOUT the chart rather than at a point on it.
   The one thing in this layer that takes the mouse, because it is the one
   thing here you are meant to pick up and put somewhere else - .ovl stays
   pointer-events:none and this opts back in.

   Capped in width and height, and that is not decoration: the whole request
   was a box that does not hide the chart, so it may not grow to fill it. The
   text cap in normalize() is what keeps the content inside these bounds, so
   overflow:hidden here is a backstop and never the normal case. Flat ground
   and no backdrop-filter - snapshot.js reads backgroundColor and nothing
   else, and a board missing from the export is the argument missing from it. */
.o-board{
  max-width:min(300px, 34%); max-height:38%; overflow:hidden;
  padding:7px 10px; border:1px solid; border-radius:8px;
  background:var(--ovl-box); color:var(--text);
  font:12px/1.55 var(--sans); white-space:pre-wrap;
  box-shadow:0 6px 18px rgba(0,0,0,.5);
  pointer-events:auto; cursor:grab; user-select:none;
  /* A DRAGGABLE THING HAS TO OWN ITS TOUCHES. `touch-action` computed to `auto`
     here - `#chart,#chart canvas{touch-action:none}` does not reach `.ovl` -
     and `body` IS a scroll container on a phone (setting `overflow-x:hidden`
     makes the other axis compute to `auto`, the same trap `#watch` hit from the
     other side). So a vertical drag on the board panned the page instead, the
     browser took the pointer back with a `pointercancel`, and the box snapped
     home: THE BOARD COULD NOT BE MOVED ON A PHONE AT ALL. preventDefault on
     pointerdown does not stand in for this - scrolling is not that event's
     default action. `-webkit-touch-callout` stops iOS offering to copy the text
     under a long press, which is now the gesture that holds it. */
  touch-action:none; -webkit-touch-callout:none;
}
.o-board.drag{cursor:grabbing; box-shadow:0 10px 26px rgba(0,0,0,.6); will-change:transform}

/* ---- the board, folded ----
   Ten lines of prose is a box 165px tall. On a phone that is a fifth of the
   chart - "the text is almost the entire screen", from the device - which is
   why the phone has opened it folded since that layout was built. It is a
   SWITCH ON EVERY SCREEN now (Dor, 2026-09-13): 165px is less of a 1,100px
   pane and it is still the largest thing the analysis draws, and it is prose
   you have already read. `Full board` in the D panel, or B.

   It is a FOLD and not a truncation, which is the whole difference: the chip
   says how it is folded and the words are one gesture away, so nothing is
   stored and not drawn (rule 1). The same trade the strip's `+n` makes.

   TWO WHOLE LINES, AND `max-height` CANNOT SAY THAT. The first attempt was a
   bare `3.5em`, which `*{box-sizing:border-box}` spends on padding before the
   text sees it; the second was `calc(3.1em + 21px)`, arithmetic for two lines
   plus the padding - and it still showed two and a half, because
   `padding-bottom` does not stop content PAINTING into it. It reserves space
   AFTER the content; content taller than the box runs straight through it and
   `overflow:hidden` clips wherever the border box happens to end, which is
   mid-letter. `-webkit-line-clamp` is the property that means what this rule
   wants: TWO LINES, an ellipsis, and the 14px the chip needs left under them.
   Same tool the phone's legend uses, for the same reason.

   OUTSIDE THE MEDIA QUERIES ON PURPOSE - it is true of the STATE, not of the
   screen, and the state is the page's now rather than chrome.js's. */
body.boardfold .o-board{
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  /* ZERO, and it is the whole fix. `overflow:hidden` clips at the PADDING box,
     so every pixel of padding-bottom is a pixel of the NEXT line that gets
     shown: at 14px (what the `more` chip was reserved) three-quarters of line
     three was drawn through it and out over the board's own bottom border; at
     the base 7px a sliver of it still was. The clamp was never the problem -
     it stops the text and places the ellipsis correctly - the clip was in the
     wrong place. The gap under the last line is the line-height's own
     half-leading, which is what was doing most of that work anyway. */
  overflow:hidden; padding-bottom:0;
}
/* THE `more` CHIP IS GONE, AND THE ELLIPSIS IS THE FOLD'S OWN VOICE.
   The chip was reserved its room with `padding-bottom:14px`, and that could
   never have worked: `overflow:hidden` clips at the PADDING box, and padding
   is INSIDE it - so `-webkit-line-clamp` stopped the text at two lines and
   put its ellipsis there, and the third line went on painting straight through
   the reserved strip and out over the board's own bottom border, with the chip
   sitting on top of the middle of it. It read as two lines and a chip; it was
   two lines, a chip, and half a sentence underneath. Shipping on the phone
   since the fold was built, and only unmistakable on a desktop where the box
   is 295px wide.

   With the padding gone the clamp is exact, and there is then nowhere in the
   box for a chip to stand that is not on top of a word - the bottom is line
   two, whose right end IS the ellipsis, and the top is line one. So it goes.
   `…` is what an ellipsis means and it is already how every folded caption on
   this chart says it (`.o-note.tag`); the ways to the rest are the tap, `B`,
   the `Full board` row and the Marks list, and three of those exist at a
   keyboard where a click on the board means select rather than open. */
.o-vline{top:0; bottom:0; width:0}
.o-vline i{position:absolute; top:0; bottom:0; width:1px; background:var(--c); opacity:.65}
.o-vline b{
  position:absolute; top:6px; left:6px; white-space:nowrap; font:500 10px var(--mono);
  color:var(--c); background:rgba(11,14,19,.85); padding:2px 5px; border-radius:4px;
}
/* A rule near the right edge would hang its caption off the chart - 80px of a
   390px pane is the price axis, and the caption is wider than what is left.
   overlay.js measures once and flips it to the other side of its own rule,
   which is the one place there is always room. */
.o-vline.flip b{left:auto; right:6px}
/* One side of one strike: a bar in the right margin, growing leftward, at the
   strike's own price. Flat colours - snapshot.js reads backgroundColor and
   nothing else. */
.o-oibar{height:4px}
.o-oibar i{
  position:absolute; inset:0; background:var(--c); border-radius:1px;
}
.o-span{top:0; bottom:0}
.o-span b{
  position:absolute; top:6px; left:6px; white-space:nowrap; font:500 10px var(--mono);
  color:var(--agent); background:rgba(11,14,19,.8); padding:2px 5px; border-radius:4px;
}
/* ---- the board, and the pen (docs/מצב-לימוד.md §7) ----
   Ink that lives three seconds and goes: not a mark, not stored, no author, no
   budget - the ruler's standing, in a canvas rather than in divs, because this
   one draws at a hand's pace and a hundred trembling strokes as DOM nodes is
   the animation the chart is not allowed to cost (rule 5).

   ABOVE EVERYTHING ELSE IN THE PANE, and that is the spotlight's doing: "כל
   השאר מתעמעם" has to include the marks and the readings, so a dim that the
   overlay drew over would dim the candles and leave the words at full voice.
   `pointer-events:none` is what makes that safe - it is over the zoom rail and
   the legend and takes no press off either. The ONE exception is the student's
   pen (§8), which is an explicit mode and has to have the pointer while it is
   armed; index.html turns the mode on, and web/sketch.js is what sets this
   inline while it lasts.

   No transition and no animation property: every frame of this layer is drawn
   in JS, so `prefers-reduced-motion` is read there (matchMedia) rather than
   here - the global rule at the top of this file cannot reach a canvas. */
.sketch{position:absolute; inset:0; z-index:6; pointer-events:none}
/* The pen's crosshair, on the chart under the board: the board's canvas never
 * takes a press (sketch.js armPen), and the library writes its own cursor
 * inline, hence the weight. */
.sketch-armed #chart, .sketch-armed #chart *{cursor:crosshair !important}

/* THE STUDENT'S PACE (§7, "שלבים"): two controls and where you are, in the
   analyst's blue, at the foot of the pane and clear of the date row - which is
   the page's own `--pad-bottom`, published on the wrap by index.html, rather
   than a guess at how tall an axis is.
   A FLOATING CONTROL WEARS THE FLOATING SURFACE: the popover ground, a
   neutral hairline and the popover's shadow, with the analyst's ice in the
   glyphs and the timeline rather than round the edge - the performance is
   the analyst's, the pill it is played from is the page's. Flat colours
   only: it sits on the pane. */
.sketch-pace{
  position:absolute; z-index:7; left:50%; transform:translateX(-50%);
  bottom:calc(var(--pad-bottom, 0px) + 12px);
  display:flex; align-items:center; gap:6px; padding:4px 6px;
  border:1px solid var(--line2); border-radius:999px;
  background:var(--panel2); box-shadow:var(--shadow-pop);
}
.sketch-pace button{
  width:30px; height:30px; padding:0; cursor:pointer;
  display:grid; place-items:center; border-radius:999px;
  border:1px solid transparent; background:transparent;
  color:var(--agent); font:600 17px/1 var(--sans);
  transition:background var(--dur-1), color var(--dur-1);
}
.sketch-pace button:hover:not(:disabled){background:var(--agent-soft); color:var(--agent-hi)}
.sketch-pace button:active:not(:disabled){background:rgba(110,169,255,.2)}
.sketch-pace button:disabled{color:var(--dim); cursor:default}
/* THE WAY OUT, on every performance and not only a paced one. Its own colour
   because it is the one control here that is not the lesson's - the steppers
   move the board on, this ends it - and a separator so it reads as a second
   thing rather than a third arrow. `--warn`, not `--stop`: nothing was wrong,
   the reader has simply had enough. */
.sketch-pace button.stop{
  color:var(--warn); font-size:10px;
  border-left:1px solid var(--line2); border-radius:0 999px 999px 0;
}
.sketch-pace button.stop:hover{background:var(--warn-soft); color:var(--warn)}
.sketch-pace.solo button.stop{border-left:0; border-radius:999px}
.sketch-pace span{
  min-width:38px; text-align:center; color:var(--muted);
  font:500 var(--fs-meta)/1 var(--num); font-variant-numeric:tabular-nums;
}
/* A PERFORMANCE THAT RUNS IS A VIDEO (Dor, 2026-09-23): pause, and the
   timeline under the finger, between it and the stop. The pill takes the
   width the bar needs and never more than the pane has. The two icons are
   drawn with borders rather than glyphs - a pause sign is an emoji on iOS. */
.sketch-pace.video{width:min(560px, calc(100% - 32px))}
.sketch-pace button.play::before{
  content:''; box-sizing:border-box; width:10px; height:12px;
  border-left:3.5px solid currentColor; border-right:3.5px solid currentColor;
}
.sketch-pace button.play.paused::before{
  width:0; height:0; margin-left:3px; border-right:0;
  border-left:11px solid currentColor;
  border-top:7px solid transparent; border-bottom:7px solid transparent;
}
.sketch-pace .scrub{
  position:relative; flex:1; min-width:0; height:30px;
  cursor:pointer; touch-action:none;
}
.sketch-pace .scrub::before,
.sketch-pace .scrub .fill{
  content:''; position:absolute; left:0; top:50%; height:4px; margin-top:-2px;
  border-radius:2px; pointer-events:none;
}
.sketch-pace .scrub::before{right:0; background:rgba(110,169,255,.2)}
.sketch-pace .scrub .fill{width:0; background:var(--agent)}
.sketch-pace .scrub .knob{
  position:absolute; left:0; top:50%; width:12px; height:12px; margin:-6px 0 0 -6px;
  border-radius:50%; background:var(--agent-hi); pointer-events:none;
  box-shadow:0 0 0 3px var(--panel2), 0 0 0 4px rgba(110,169,255,.35);
}
/* ⚠ UNDER THE CHART, NOT ON IT (docs/מצב-לימוד.md §29, Dor 2026-09-24). The
   foot of the pane is the volume band, and the pill above hid it for every
   performance. So the board docks its controls over chrome that is already
   there (`dockPace` in web/sketch.js; the page says where): the status row on
   a desktop, the dock's row of buttons on a phone - left to right under the
   PANE only, never over the licence credit - and the chart is not one pixel
   smaller. The rules above are the look they had on the pane; these undo the
   floating half of it: it lies flat in the row it covers, the row's own
   ground, with a hairline at each end where it meets the rest of the row. */
#foot{position:relative}
.sketch-pace.docked{
  top:0; bottom:0; transform:none; border-radius:0; box-shadow:none;
  border:0; border-left:1px solid var(--line); border-right:1px solid var(--line);
  background:var(--panel); padding:0 8px; gap:4px;
}
.sketch-pace.docked button.stop{border-radius:0}
.sketch-pace.docked:not(.video){justify-content:center}
#foot > .sketch-pace.docked button{width:24px; height:24px; font-size:14px}
#foot > .sketch-pace.docked button.stop{font-size:9px}
#foot > .sketch-pace.docked span{min-width:34px; font-size:var(--fs-cap)}
#foot > .sketch-pace.docked .scrub{height:26px}
#foot > .sketch-pace.docked .scrub::before,
#foot > .sketch-pace.docked .scrub .fill{height:3px; margin-top:-1.5px}
#foot > .sketch-pace.docked .scrub .knob{width:10px; height:10px; margin:-5px 0 0 -5px;
  box-shadow:0 0 0 2px var(--panel), 0 0 0 3px rgba(110,169,255,.35)}
/* the row the controls are laid over takes no tap while they are up */
.under-pace > :not(.sketch-pace){pointer-events:none}
#dock > .sketch-pace.docked{top:1px; bottom:auto; height:41px; padding:0 4px}
#dock > .sketch-pace.docked button{width:40px; height:40px; font-size:18px}
#dock > .sketch-pace.docked .scrub{height:40px}
#dock > .sketch-pace.docked .scrub .knob{box-shadow:0 0 0 3px var(--panel), 0 0 0 4px rgba(110,169,255,.35)}
/* ...and with nowhere to go - a desktop in fullscreen has no status row - a
   THIN line over the date row, not over the volume: a pointer or a tap opens
   the controls there, and they fold again (`openThin`). Folded, everything but
   the line keeps its place and is simply not seen, so the line does not move
   when they open. */
.sketch-pace.thin{
  bottom:0; height:var(--pad-bottom, 26px); padding:0 6px; gap:4px;
  border:0; border-radius:0; box-shadow:none; background:transparent;
}
.sketch-pace.thin.open{background:rgba(13,16,22,.94); border-top:1px solid var(--line2)}
.sketch-pace.thin button{width:22px; height:22px; font-size:13px}
.sketch-pace.thin button.stop{font-size:9px}
.sketch-pace.thin .scrub{height:100%}
.sketch-pace.thin .scrub .knob{box-shadow:0 0 0 2px var(--panel), 0 0 0 3px rgba(110,169,255,.35)}
.sketch-pace.thin:not(.open) > :not(.scrub),
.sketch-pace.thin:not(.open) .scrub .knob{visibility:hidden}
.sketch-pace.thin:not(.open) .scrub::before,
.sketch-pace.thin:not(.open) .scrub .fill{top:auto; bottom:2px; height:3px; margin-top:0}
/* THE SPEED (Dor, 2026-10-06): four chips beside the pause, the one playing
   lit. Numbers, so the number face, and small enough to sit in the status row. */
.sketch-pace .speeds{display:flex; align-items:center; gap:2px; flex:none}
.sketch-pace .speeds[hidden]{display:none}
.sketch-pace button.speed{
  width:auto; min-width:34px; height:24px; padding:0 6px; border-radius:6px;
  color:var(--muted); font:600 11px/1 var(--num); font-variant-numeric:tabular-nums;
}
.sketch-pace button.speed.on{
  color:var(--agent-hi); background:var(--agent-soft);
  border-color:rgba(110,169,255,.45);
}
/* ⚠ THE BAR HAS TO BE SEEN (Dor, 2026-10-06: on a desktop "the bar is not
   clear and you do not see play and pause"). In the 27px status row it was a
   3px line at a fifth of its colour beside a 24px glyph with no ground. Still
   the same row and not one pixel of chart: a hairline of the analyst's ice
   along its top says the row has become the player, the pause is a FILLED
   disc, and the track is twice as thick and twice as bright. */
#foot > .sketch-pace.docked.video{
  background:var(--panel2); box-shadow:inset 0 1px 0 rgba(110,169,255,.55);
  gap:8px; padding:0 10px;
}
#foot > .sketch-pace.docked.video button.play{
  width:22px; height:22px; background:var(--agent); color:var(--panel);
}
#foot > .sketch-pace.docked.video button.play:hover{background:var(--agent-hi); color:var(--panel)}
#foot > .sketch-pace.docked.video button.play::before{width:8px; height:9px;
  border-left-width:2.5px; border-right-width:2.5px}
#foot > .sketch-pace.docked.video button.play.paused::before{width:0; height:0; margin-left:2px;
  border-left-width:8px; border-top-width:5px; border-bottom-width:5px}
#foot > .sketch-pace.docked.video .scrub::before,
#foot > .sketch-pace.docked.video .scrub .fill{height:5px; margin-top:-2.5px; border-radius:3px}
#foot > .sketch-pace.docked.video .scrub::before{background:rgba(110,169,255,.32)}
#foot > .sketch-pace.docked.video .scrub .knob{width:13px; height:13px; margin:-6.5px 0 0 -6.5px}
#foot > .sketch-pace.docked button.speed{width:auto; height:20px; font-size:11px}
#dock > .sketch-pace.docked.video button.play{
  width:34px; height:34px; margin:0 3px; background:var(--agent); color:var(--panel);
}
#dock > .sketch-pace.docked.video button.play::before{width:10px; height:12px;
  border-left-width:3.5px; border-right-width:3.5px}
#dock > .sketch-pace.docked.video button.play.paused::before{width:0; height:0; margin-left:3px;
  border-left-width:11px; border-top-width:7px; border-bottom-width:7px}
#dock > .sketch-pace.docked.video .scrub::before{background:rgba(110,169,255,.32)}
#dock > .sketch-pace.docked button.speed{width:auto; height:32px; min-width:48px; font-size:13px}

/* WHERE EACH STOCK BEGINS, on a lesson that walks several (`acts`): a notch
   across the bar, so the reader can put a finger on the next stock. */
.sketch-pace .scrub .act{
  position:absolute; top:50%; width:2px; height:12px; margin:-6px 0 0 -1px;
  border-radius:1px; background:var(--agent-hi); opacity:.55; pointer-events:none;
}

/* ---- the lesson's sentence ----
   docs/מצב-לימוד.md §21, decision 8. ONE live sentence at a time, written ON
   the board over the ink it belongs to - `placeCaption()` in web/sketch.js
   puts it there and nothing here has an opinion about the corner. It began as
   a strip owning its own row; a strip is what Dor kept reporting
   ("עדיין יש כתב שנכתב בתוך חלון למטה, ולא הכל על הגרף"), and one sentence to
   place is arithmetic rather than the solver docs/caption-placement.md is
   about. The rule below is the LOOK; where it goes is the board's. */
.sketch-say{
  /* The board places it (`placeCaption`); this is only where it sits for the
     frame before that, and it is ON the board rather than along its top rim
     for the same reason the placement is. */
  position:absolute; z-index:7; top:26%; left:50%; transform:translateX(-50%);
  max-width:min(92%, 620px); padding:0;
  color:var(--say, #ffffff); text-align:center; text-wrap:balance;
  /* WRITING ON A BOARD, NOT A CAPTION BAR (Dor, 2026-09-20: "אני רוצה שכל
     הגרף יהיה כמו לוח, שהוא יכתב כמו בכתב יד"). No panel, no border, no
     ground - a box under the words is the thing that made it read as a strip
     bolted under the chart. The HAND face, and the same DARK HALO the board
     paints behind a numeral, because the rule that made handwriting legible
     over candles is the one that makes a whole sentence legible over them:
     the background under one glyph is not one colour, so no colour fixes it
     (CLAUDE.md, "The skin"; docs/skin.md). */
  font:700 17px/1.42 var(--hand, var(--sans));
  /* ⚠ AND THE HALO IS THE BRIGHTNESS, NOT THE COLOUR. The white is already at
     the top of the scale, so "יותר זוהר ובהיר" is bought where it is actually
     lost: a sentence written over candles reads against a ground that changes
     colour under every glyph (docs/skin.md). A tight dark ring separates it,
     and the analyst's blue glowing outside it (`--say-glow`) is what makes it
     read as LIT - the board draws with light (§34) - rather than as pasted on. */
  text-shadow:
     0 0 2px var(--bg-deep), 0 0 5px var(--bg-deep),
     1px 1px 0 var(--bg-deep), -1px 1px 0 var(--bg-deep),
     1px -1px 0 var(--bg-deep), -1px -1px 0 var(--bg-deep),
     0 0 9px var(--say-glow), 0 0 22px var(--say-glow), 0 0 44px rgba(110,169,255,.28);
  opacity:0; transition:opacity .18s ease;
  pointer-events:none;
}
/* ...AND IT IS WRITTEN IN LIGHT, in the direction it is read: a soft edge
   sweeps across it as it arrives - the window slides along a mask whose far
   side is clear, so a Hebrew line comes up from its right edge and a Latin one
   from its left. */
.sketch-say.in{opacity:1;
  -webkit-mask-image:linear-gradient(to left, #000 42%, transparent 58%);
          mask-image:linear-gradient(to left, #000 42%, transparent 58%);
  -webkit-mask-size:260% 100%; mask-size:260% 100%;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  animation:sayWrite .75s cubic-bezier(.3,.7,.3,1) both;
}
.sketch-say.in:dir(ltr){
  -webkit-mask-image:linear-gradient(to right, #000 42%, transparent 58%);
          mask-image:linear-gradient(to right, #000 42%, transparent 58%);
  animation-name:sayWriteL;
}
@keyframes sayWrite{
  from{-webkit-mask-position:0 0; mask-position:0 0}
  to{-webkit-mask-position:100% 0; mask-position:100% 0}
}
@keyframes sayWriteL{
  from{-webkit-mask-position:100% 0; mask-position:100% 0}
  to{-webkit-mask-position:0 0; mask-position:0 0}
}
/* It is placed by the board now, over the ink of the step it belongs to, so
   there is no corner for the paced controls to collide with. */
.sketch-say.over-pace{}
/* With the motion off, the words are simply there: a mask the animation never
   moved would hide a Hebrew line whole. */
@media (prefers-reduced-motion: reduce){
  .sketch-say{transition:none}
  .sketch-say.in, .sketch-say.in:dir(ltr){animation:none; -webkit-mask-image:none; mask-image:none}
}
/* THE CHAPTER'S NAME AT THE TOP OF THE PANE (Dor, 2026-10-02): when each
   chapter of a lesson IS - "נגיעה 2/3 · 11.11.25 · שעה". The sentence's own
   look (the hand, the dark halo and the blue glow, no panel), one size up and
   heavier, on one line. The board places it under the legend (`placeHead`). */
.sketch-head{
  position:absolute; z-index:7; top:8px; left:50%; transform:translateX(-50%);
  white-space:nowrap; padding:0;
  color:var(--say, #ffffff);
  font:800 23px/1.25 var(--hand, var(--sans));
  text-shadow:
     0 0 2px var(--bg-deep), 0 0 5px var(--bg-deep),
     1px 1px 0 var(--bg-deep), -1px 1px 0 var(--bg-deep),
     1px -1px 0 var(--bg-deep), -1px -1px 0 var(--bg-deep),
     0 0 9px var(--say-glow), 0 0 22px var(--say-glow), 0 0 44px rgba(110,169,255,.28);
  opacity:0; transition:opacity .2s ease;
  pointer-events:none;
}
.sketch-head.in{opacity:1}
@media (prefers-reduced-motion: reduce){ .sketch-head{transition:none} }

/* THE LIGHT UNDER THE INK (web/sketch.js, `bloom`): the board's own picture,
   small, blurred by the compositor and SCREENED onto the chart, so the glow
   lands on the candles as light and never as paint. Off the page while the
   board is empty. */
.sketch-glow{position:absolute; inset:0; z-index:6; pointer-events:none;
  filter:blur(7px) brightness(1.55) saturate(1.35);
  mix-blend-mode:screen; opacity:.92}
.sketch-glow[hidden]{display:none}

/* ---- the ruler ----
   Not a mark and not stored: two points the user is asking about, answered
   while the mouse is still moving. Divs rather than SVG, because snapshot.js
   paints backgrounds, borders and text and would drop anything else. The
   frame is the user's gold; the fill is the direction the move went. */
.rule{position:absolute; inset:0; z-index:2; pointer-events:none; overflow:hidden}
.rule>div{position:absolute}
.r-box{border:1px solid var(--gold-line); background:var(--gold-soft); border-radius:2px}
.r-box.up{border-color:rgba(47,208,138,.5); background:var(--up-soft)}
.r-box.down{border-color:rgba(255,93,115,.5); background:var(--down-soft)}
.r-tag{
  padding:5px 9px; border:1px solid var(--gold-line); border-radius:6px;
  background:rgba(11,14,19,.92); box-shadow:0 4px 14px rgba(0,0,0,.45);
  white-space:nowrap; text-align:center;
}
.r-tag b{
  display:block; color:var(--text);
  font:600 12.5px/1.25 var(--mono); font-variant-numeric:tabular-nums;
}
.r-tag.up b{color:var(--up)} .r-tag.down b{color:var(--down)}
/* The two prices it ran between, then how long - stepping down in weight, so
   the percent is what you read first and the rest is there to check it. */
.r-tag span{
  display:block; margin-top:3px; color:var(--text);
  font:500 11px/1.25 var(--mono); font-variant-numeric:tabular-nums;
}
.r-tag i{
  display:block; margin-top:2px; color:var(--muted); font-style:normal;
  font:500 10.5px/1.25 var(--mono); font-variant-numeric:tabular-nums;
}
/* THE START, WAITING TO BE PUT WHERE THE MOVE BEGAN. The phone's stage before
   the measurement: not a box of zero width but a ring, because nothing has been
   measured yet and a box with a label on it says something has. A ring rather
   than a disc so the candle it is standing on can be seen THROUGH it - the
   whole gesture is aiming at a bar, and a solid dot under a fingertip hides
   exactly the thing being aimed at. NINE PIXELS, not thirteen: it is a point
   on a chart where 1px is several cents, so it has to be small enough to say
   WHICH bar and which price - at 13 it was a marker rather than a point
   (Dor, 2026-09-22: *"make the dot smaller and nicer, it is a bit too big"*).
   It gives up no reach by being small: the handle is the whole pane, not the
   ring, so what you aim with is the drag and never the dot. The user's own gold, like everything else
   the hand does; `translate(-50%,-50%)` is what makes the point it claims the
   point it is drawn at. It is never exported - the aim is gone by the time
   there is anything worth a picture. */
.r-dot{
  width:9px; height:9px; margin:0; border-radius:50%;
  border:1.5px solid var(--gold); background:rgba(11,14,19,.55);
  box-shadow:0 0 0 1px rgba(11,14,19,.75), 0 0 6px rgba(0,0,0,.5);
  transform:translate(-50%,-50%);
}
/* ...AND IT IS FILLED ONCE THE START IS SET. Two stages draw a dot and neither
   draws a reading, so the dot is the only thing that can say which one the
   hand is in: hollow while it still moves with the finger, solid once a tap
   has committed it and the next drag is the measurement. */
.r-dot.set{background:var(--gold); border-color:var(--gold-hi)}
/* THE RING IS 9px AND THE HANDLE IS THE WHOLE PANE, which is why it can be
   this small: the dot is never a target to hit - a drag anywhere moves it by
   what the finger did (`rulerPressMeans` in `index.html`). `.rule` takes no
   pointer events at all, and none of this is exported: the aim is gone by the
   time there is anything worth a picture. */
/* THE CROSSHAIR A FINGER MEASURES WITH, and the two readings it points at.
   A crosshair is a hover and a finger has none, so this is the phone's only way
   to be told which price and which bar it is standing on - and it is ours
   rather than the library's because `setCrosshairPosition` takes a time and v4
   resolves it to a bar it HOLDS, while a measurement runs on past the last
   candle (`web/measure.js`). Dashed and in the user's own gold, like the held
   reading: it is the same hand asking the same question, and a solid gold line
   would read as a level somebody drew. Borders rather than a background because
   a dash is the point; `snapshot.js` reads `backgroundColor` and the top
   border, so the vertical would be missing from an export - which cannot
   happen, since the cross exists only while a finger is down on the glass and
   the camera is a second gesture. */
.r-vl{width:0; border-left:1px dashed var(--read)}
.r-hl{height:0; border-top:1px dashed var(--read)}
/* ON the axis, where the scale's own numbers are: the question is which price
   and which bar, and an answer floating in the pane is one more thing over the
   candles. The library's crosshair labels are this colour, so the two inputs
   read the same. */
.r-xt, .r-yt{
  height:15px; line-height:15px; padding:0 3px; border-radius:2px;
  background:var(--cross-label); color:var(--bg-deep); text-align:center;
  white-space:nowrap; font:600 10.5px var(--mono); font-variant-numeric:tabular-nums;
}

/* A HELD READING, KEPT AFTER THE LIFT: the vertical on the bar it locked onto,
   that bar's date on the time axis, and a + beside the price label that arms an
   alert or draws a level there. The layer takes no pointer; the + and its menu
   do, and are named in PANE_CHROME_BASE. Same gold and the same tag as the
   ruler's crosshair (its own two classes) - one hand asking one question. */
.readpin{position:absolute; inset:0; pointer-events:none; z-index:5}
.readpin[hidden], .readpin [hidden]{display:none}
.readpin .r-vl{position:absolute; top:0}
.readpin .r-xt{position:absolute}
.rp-plus{position:absolute; width:26px; height:26px; padding:0; border-radius:50%;
  pointer-events:auto; touch-action:manipulation; -webkit-tap-highlight-color:transparent;
  background:var(--bg); border:1px solid var(--gold-line); color:var(--gold);
  font:600 18px/24px var(--mono); text-align:center}
.rp-plus:active{background:var(--ovl-tag)}
.rp-menu{position:absolute; display:flex; flex-direction:column; gap:4px; padding:5px;
  pointer-events:auto; border-radius:var(--r-lg); background:rgba(11,14,19,.93);
  border:1px solid var(--gold-line); box-shadow:0 6px 18px rgba(0,0,0,.5)}
.rp-menu button{min-height:36px; padding:0 12px; white-space:nowrap; text-align:left;
  border-radius:var(--r); background:var(--hover); border:1px solid transparent;
  color:var(--text); font:500 13px var(--sans); cursor:pointer;
  touch-action:manipulation; -webkit-tap-highlight-color:transparent}
.rp-menu button:active{background:var(--gold-soft); color:var(--gold)}
.rp-menu b{font-variant-numeric:tabular-nums}

/* THE BAR THAT SAYS A DRAWING IS BEING MADE.
   A mode has to be visible from inside it, and it has to carry the way OUT of
   itself on a device with no Esc key - the same rule the selection halo's `×`
   follows. At the TOP of the pane on purpose: the bottom half of a phone screen
   is where the thumb draws, and a control there is a control you press by
   accident mid-gesture. It is named in PANE_CHROME_BASE so the pick, the ruler
   and the grabs all know a press on it is not a press on the candles. */
.drawbar{
  position:absolute; top:8px; left:50%; transform:translateX(-50%); z-index:6;
  display:flex; align-items:center; gap:8px; max-width:calc(100% - 24px);
  padding:5px 6px 5px 11px; border-radius:var(--r-lg);
  background:rgba(11,14,19,.93); border:1px solid var(--gold-line);
  box-shadow:0 6px 18px rgba(0,0,0,.5); animation:dropx .16s var(--ease);
  pointer-events:auto; touch-action:none; -webkit-tap-highlight-color:transparent;
}
.drawbar .nm{
  color:var(--gold); font:600 12px var(--sans);
  letter-spacing:.01em; white-space:nowrap;
}
.drawbar .step{color:var(--muted); font:11.5px var(--sans); white-space:nowrap}
.drawbar button{
  min-width:28px; height:28px; padding:0 8px; border-radius:var(--r-sm);
  background:var(--hover); border:1px solid transparent; color:var(--text);
  font:500 12px var(--sans); cursor:pointer;
  transition:color var(--dur-1), background-color var(--dur-1);
}
.drawbar button:hover{background:var(--press); color:var(--gold)}
.drawbar button:active{background:var(--gold-soft)}
/* The magnet's switch - here and in the D panel's Draw heading. Lit is gold,
   the hand's colour, because it changes where the hand's points land. */
button.magnet{display:inline-grid; place-items:center; padding:0 5px}
button.magnet svg{width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:1.6;
  stroke-linejoin:round; stroke-linecap:round}
button.magnet.on{color:var(--gold); border-color:var(--gold-line); background:var(--gold-soft)}
.panel .grp button.magnet{margin-left:6px; width:26px; height:22px; border-radius:var(--r-sm);
  border:1px solid var(--line); background:var(--hover); color:var(--muted); cursor:pointer}
.panel .grp button.magnet.on{color:var(--gold); border-color:var(--gold-line); background:var(--gold-soft)}
.drawbar input[type=text]{
  width:190px; height:28px; padding:0 8px; border-radius:var(--r);
  background:var(--well); border:1px solid var(--line); color:var(--text);
  font:12.5px var(--sans);
}
.drawbar input[type=text]:focus{outline:none; border-color:var(--gold-line); box-shadow:var(--ring)}

/* While the ruler is waiting for a point, the pointer says so everywhere in
   the pane - the canvas sets its own cursor, so it is named too. */
#wrap.measuring, #wrap.measuring canvas{cursor:crosshair}

/* ---- the lists, down the left ----
   The chart is the message, so the rail is narrow and every row is one line
   with a number on the end. Gold, because these are the user's own lists and
   their own holdings - an agent-made group is marked, not recoloured. */
#watch{
  position:relative; flex:0 0 var(--rail-w); display:flex; flex-direction:column; min-width:0;
  background:var(--panel); overflow-y:auto;
  /* ⚠ `none`, NOT `contain` — THEY STOP DIFFERENT THINGS. `contain` stops the
     scroll CHAINING to whatever is behind the rail, which was the reason it
     was here and is still wanted. It does not stop the BOUNCE: pulling a short
     list down still drags the whole column with it and lets it spring back,
     which on a list is the pull-to-refresh gesture and this rail refreshes
     nothing. Reported from the device inside a folder of three lists: *"I can
     pull down and the whole list goes down, as if I am loading something — and
     there is no reason for it, and not from the bottom either when there is
     nowhere to scroll."* `none` is `contain` plus the bounce, so nothing that
     was wanted is lost. */
  overscroll-behavior:none;
}
/* A hairline, not a slab. The rail is a narrow column and the page's default 4px
   thumb in --raise reads as a third column beside the numbers; here it is a
   faint rule that only asserts itself while the pointer is in the rail. */
#watch{scrollbar-width:thin; scrollbar-color:rgba(148,163,184,.13) transparent}
#watch::-webkit-scrollbar{width:6px}
#watch::-webkit-scrollbar-thumb{
  background:rgba(148,163,184,.13); border:1px solid transparent;
  background-clip:padding-box; border-radius:3px;
  transition:background .18s var(--ease);
}
#watch:hover{scrollbar-color:rgba(148,163,184,.3) transparent}
#watch:hover::-webkit-scrollbar-thumb{background:rgba(148,163,184,.3); background-clip:padding-box}
#watch::-webkit-scrollbar-track{background:transparent}
/* ONE SCROLLBAR, NOT TWO. Chrome ignores every ::-webkit-scrollbar rule the
   moment scrollbar-width or scrollbar-color is set, and on a Mac whose
   scrollbars are "when scrolling" it then draws the SYSTEM overlay bar - a
   wide grey track with a white thumb - which came and went over the hairline
   above (Dor, 2026-10-05, off the installed app). Where the pseudo-elements
   exist they are the whole styling; the standard pair stays for Firefox. */
@supports selector(::-webkit-scrollbar){
  #watch, #watch:hover{scrollbar-width:auto; scrollbar-color:auto}
}
#watch[hidden]{display:none}
/* The rail's edge is the header's hairline stood on end, mirrored from the
   analyst panel's so the two read as a pair. */
#watch::after{
  content:""; position:absolute; top:0; bottom:0; right:0; width:1px; pointer-events:none;
  background:linear-gradient(180deg,rgba(228,199,120,.5),rgba(228,199,120,.16) 55%,rgba(228,199,120,0));
}
#watchBtn.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}

/* =======================================================================
   THE LISTS: FOUR SCREENS, ONE LEVEL EACH
   =======================================================================

   This was an accordion until 2026-09-18 and the accordion is why it is not
   one now. Measured on the store it is actually used against: FOURTEEN lists,
   131 rows, 115 unique symbols, twelve of the fourteen made by the analyst.
   Folded, that is fourteen headers you scroll to read; opened, one of them is
   twenty-two rows and the other thirteen are gone off the bottom. Reported as
   "it is just long, and it is hard to manage".

   So: folders over lists over symbols, one level on screen at a time, and a
   fourth screen that cuts across all of it - every symbol once, sorted by what
   moved. `web/folders.js` is the model; this is the surface.

   ⚠ EVERYTHING THAT MOVES HERE MOVES ON `transform` OR `opacity`, AND NOTHING
   ELSE. The rail repaints about once a second all day with the price poll
   behind it, so anything that can make the browser lay the row out again is a
   cost on a path the eye is already on (rule 5). That is why the tick flash is
   a child with a colour rather than a background on the row, why the breadth
   bar is a gradient behind the row rather than a sized element, and why
   `will-change` is worn by a screen only while it is sliding.  */

/* ---- the head every screen wears ----
   A way back, where you are, and what this place is. The crumb names the
   FOLDER above a list, because "SOXX" on its own does not say which of
   fourteen lists you walked into. */
.whead{
  display:flex; align-items:center; gap:2px; min-height:38px; padding:6px 6px 6px 4px;
  position:sticky; top:0; z-index:6; background:var(--panel);
  border-bottom:1px solid var(--line);
}
/* The hairline under the head is gold at the leading edge and gone by the
   far one - the same fade the rail's own edge and the analyst panel's use,
   turned on its side, so the three read as one piece of furniture. */
.whead::after{
  content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:linear-gradient(90deg,var(--gold-line),rgba(228,199,120,.10) 60%,transparent);
  pointer-events:none;
}
.wback{
  flex:none; width:26px; height:26px; display:grid; place-items:center;
  background:none; border:0; border-radius:var(--r-sm); cursor:pointer; color:var(--muted);
  transition:color var(--dur-1), background-color var(--dur-1), transform .16s var(--ease);
}
.wback svg{width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:1.9;
           stroke-linecap:round; stroke-linejoin:round}
.wback:hover{color:var(--text); background:var(--hover); transform:translateX(-2px)}
.wback:active{transform:translateX(-3px)}
.wcrumb{display:flex; flex-direction:column; gap:1px; min-width:0; flex:1; padding-left:2px}
.wtitle{
  font:600 13px var(--sans); color:var(--text); letter-spacing:.01em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.wsub{
  font:400 11px var(--sans); color:var(--dim);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.wact{
  flex:none; min-width:24px; height:24px; padding:0 5px;
  background:none; border:0; border-radius:var(--r-sm); cursor:pointer;
  color:var(--muted); font:400 13px var(--sans); line-height:1;
  transition:color var(--dur-1), background-color var(--dur-1);
}
.wact:hover{color:var(--text); background:var(--hover)}
.wact.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
/* THE WAY BACK TO THE CHART IS THE PHONE'S ALONE (it replaced the ✕, which
   was too). On a desktop the rail is a column with a button in the header and
   a `w` key on top of it; a third way to close one thing is two ways too many.
   It is turned back on in the coarse-pointer block, where the drawer is the
   whole screen and the chart behind it cannot be tapped. */
.wact.tochart{display:none}
/* The way back to the chart (watchlist.js, `chartBtnHtml`): a small gold line
   chart in a gold frame - the user's colour, and the one framed key in the head, so
   it reads as the door rather than as one more action on the list. */
.whead .wact.tochart{
  place-items:center; flex:none; width:34px; height:28px; margin-left:4px; padding:0;
  color:var(--gold); background:var(--gold-soft);
  border:1px solid var(--gold-line); border-radius:var(--r);
  box-shadow:inset 0 1px 0 var(--hi), 0 0 10px rgba(228,199,120,.10);
}
/* THE PLUS IN A HEAD IS THE SIZE OF THE KEY BESIDE IT (Dor, 2026-10-06: *the
   plus is very small*): a 13px glyph in a 24px box was the smallest target in
   the head, and it is the one that makes things. Framed in slate, so the gold
   frame still says which key is the door. */
.whead .wact.gnew,.whead .wact.add{
  display:grid; place-items:center; flex:none; width:34px; height:28px; padding:0;
  font:300 22px/1 var(--sans); color:var(--text);
  background:rgba(255,255,255,.045); border:1px solid rgba(148,163,184,.22);
  border-radius:var(--r);
}
.whead .wact.gnew:active,.whead .wact.add:active{background:var(--raise)}
/* A list being made on Glance: its title IS the field (16px, or iOS zooms). */
.gphead.gdraft{cursor:default}
.gdraft input{
  grid-area:1/1/2/3; width:100%; min-width:0; padding:2px 0 3px; margin:0;
  font:600 16px var(--sans); color:var(--text); background:transparent;
  border:0; border-bottom:1px dashed var(--gold-line); border-radius:0; outline:none;
}
.gdraft input::placeholder{color:var(--dim); font-weight:500}
.gdraft input:focus{border-bottom-style:solid; border-bottom-color:var(--gold)}
/* The add box on Glance, with its Done beside it and the hits under both. */
.wadd.withdone{display:flex; flex-wrap:wrap; gap:6px; align-items:center}
.wadd.withdone input{flex:1; min-width:0}
.wadd.withdone .whits{flex-basis:100%}
.gpin .wadd{padding:8px 8px 4px}
.whead .wact.tochart svg{width:18px; height:16px; fill:none; stroke:currentColor;
                         stroke-linecap:round; stroke-linejoin:round}
.whead .wact.tochart svg .ax{stroke-width:1.2; opacity:.45}
.whead .wact.tochart svg .ln{stroke-width:1.9}
.whead .wact.tochart:active{color:var(--gold-hi); background:var(--raise)}

/* ---- the two tabs: Glance and Folders ----
   A segmented control on its own line under the title, the whole width of the
   rail, so both are a thumb's width wide on a phone. The lit pill is ONE
   element that glides from the tab you left to the one you pressed (glideTab)
   while the head holds still and only the body slides underneath. */
/* ⚠ `height:auto`, because the page's own `header{height:44px}` reaches this
   one on a desktop (a phone's block already lifts it) and the tab row would
   hang out of a 44px head over the first list. */
.whead.tabbed{flex-wrap:wrap; row-gap:8px; padding-bottom:8px; height:auto}
.wtabs{
  flex:1 0 100%; display:flex; gap:2px; padding:3px; margin:0 2px;
  background:rgba(255,255,255,.035); border-radius:var(--r-lg);
  box-shadow:inset 0 0 0 1px rgba(148,163,184,.07);
}
.wtab{
  position:relative; flex:1; height:30px; display:flex; align-items:center;
  justify-content:center; gap:6px; background:none; border:0; cursor:pointer;
  border-radius:calc(var(--r-lg) - 3px); color:var(--muted);
  font:600 12.5px var(--sans); letter-spacing:.01em;
  transition:color .2s var(--ease);
  -webkit-tap-highlight-color:transparent;
}
.wtab .tl,.wtab .tn{position:relative; z-index:1}
.wtab .tn{
  font:500 10.5px var(--num); font-variant-numeric:tabular-nums; color:var(--dim);
  min-width:16px; height:16px; padding:0 4px; border-radius:8px; display:grid; place-items:center;
  background:rgba(255,255,255,.05); transition:color .2s var(--ease), background-color .2s var(--ease);
}
.wtab:hover{color:var(--text)}
.wtab:active .tl{transform:scale(.97)}
.wtab.on{color:var(--gold-hi)}
.wtab.on .tn{color:var(--gold); background:rgba(228,199,120,.14)}
.wtab .ind{
  position:absolute; inset:0; z-index:0; border-radius:inherit;
  background:linear-gradient(180deg,rgba(228,199,120,.16),rgba(228,199,120,.08));
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.30), 0 1px 10px -3px rgba(228,199,120,.45);
  transition:transform 240ms var(--ease);
}

/* ---- Glance: the pinned lists, open and live ---- */
.wbody[data-screen="glance"]{padding-bottom:10px}
.gpin{
  margin:10px 6px 0; border-radius:var(--r-lg);
  background:rgba(255,255,255,.022); box-shadow:inset 0 0 0 1px rgba(148,163,184,.075);
  overflow:hidden;
}
.gphead{
  width:100%; display:grid; grid-template-columns:1fr auto auto; grid-template-rows:auto auto;
  align-items:center; gap:0 6px; padding:9px 8px 6px 11px; text-align:left;
  background:none; border:0; cursor:pointer; color:inherit;
  border-bottom:1px solid rgba(148,163,184,.07);
  transition:background-color .14s;
}
.gphead:hover{background:rgba(255,255,255,.03)}
/* A list's name is a card too, so the trail lights it the way it lights every
   other: gold to the frame in 70ms, back over 700ms. It is inside a section
   that clips, so the glow is drawn INWARD. */
.gphead{border-radius:var(--r-lg) var(--r-lg) 0 0;
        transition:background-color .14s, box-shadow 700ms cubic-bezier(.22,.61,.36,1)}
.gphead.lit{
  box-shadow:inset 0 0 0 1px var(--gold), inset 0 0 14px -7px var(--gold);
  transition:background-color .14s, box-shadow 70ms linear;
}
.gphead:active{background:rgba(255,255,255,.05)}
.gpname{grid-area:1/1/2/2; font:600 12.5px var(--sans); color:var(--text);
        overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.gpmeta{grid-area:2/1/3/2; font:400 10.5px var(--num); color:var(--dim)}
.gppct{grid-area:1/2/3/3; font:500 12px var(--num); font-variant-numeric:tabular-nums}
.gphead .wchev{grid-area:1/3/3/4}
.gpin .gbody{padding:2px 4px 4px; gap:0}
/* ---- MINIMISE: the arrow in a pinned list's head ----
   The arrow points down while the list is open and turns to point right when
   it is folded; the press sends out the eye's ring. The fold is animated in
   watchlist.js on transforms (see the comment over `.gfold`'s handler). */
.gpin{position:relative}
.gphead{padding-right:46px}
.gfold{
  position:absolute; top:9px; right:6px; width:34px; height:34px; z-index:2;
  display:grid; place-items:center; border:0; border-radius:10px; cursor:pointer;
  background:rgba(255,255,255,.04); color:var(--muted);
  box-shadow:inset 0 0 0 1px rgba(148,163,184,.1);
  transition:background-color .14s, color .14s;
  -webkit-tap-highlight-color:transparent;
}
.gfold:hover{color:var(--text); background:rgba(255,255,255,.07)}
.gfold svg{width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2;
           stroke-linecap:round; stroke-linejoin:round;
           transition:transform .34s cubic-bezier(.2,.7,.2,1)}
.gpin.folded .gfold svg,.gfold.turned svg{transform:rotate(-90deg)}
.gpin.folded .gfold{color:var(--gold-hi)}
/* No pop on the arrow: it fired in the same frame as the fold began and read
   as a jolt on top of the motion. The ring is the press; the turn is the answer. */
.gfold:active{transform:scale(.94)}
.gfold.pop::after{
  content:""; position:absolute; inset:-1px; border-radius:inherit; pointer-events:none;
  animation:eyering 650ms ease-out forwards;
}
/* The fold itself is a film in watchlist.js (transforms and a clip, layout
   once at the end); folded, the body is simply not laid out. */
/* The head and its arrow, as one card top: it clips the blocks a swipe uncovers
   (watchlist.js, `.gtop`) to the list's own top corners. */
.gtop{position:relative; overflow:hidden; border-radius:var(--r-xl) var(--r-xl) 0 0}
.gfoldable{overflow:hidden}
.gpin.folded .gfoldable{display:none}
.gpin.folded .gphead{border-bottom-color:transparent}
@media (prefers-reduced-motion:reduce){
  .gfold svg{transition:none}
  .gfold.pop::after{animation:none}
}
/* A GLANCE ROW IS ONE LINE: the name, then the price and the day side by side.
   The list screen's two-line row is a target you work with; this is a column
   you read down, so the rows are hairlines in one card rather than cards. */
/* ⚠ THE BORDER STAYS, TRANSPARENT - the trail lights a card by its FRAME
   (`.lit` turns `border-color` gold), so a row with no border is a row the
   finger cannot light. The hairline between rows is a pseudo-element for the
   same reason: as a `box-shadow` it outranked `.wrow.lit`'s glow. */
.rows1 .wrow{border-color:transparent; border-radius:var(--r); padding:7px 8px 7px 9px}
.rows1 .wrow + .wrow::before{
  content:""; position:absolute; left:9px; right:8px; top:-2px; height:1px;
  background:rgba(148,163,184,.06); pointer-events:none;
}
.rows1 .wrow.lit + .wrow::before,.rows1 .wrow.lit::before{opacity:0}
.rows1 .wrow .r{flex-direction:row; align-items:center; gap:10px}
/* ONE ROW, EVERYWHERE A SYMBOL IS LISTED (Dor, 2026-10-05: *"the cards in
   Glance look different from the ones inside a folder - should they match?"*).
   A row is one line: the name, then the price and the day side by side. It
   takes a second line ONLY for what it carries beyond that - a company name or
   a position under the symbol, and on the right a position's worth and P&L, an
   option's own move or the hours around the session - and then the figures
   stack as they always did, so nothing is squeezed and nothing is dropped.
   `.wx` is written by the rail on exactly the rows holding a `.val`, `.xch` or
   `.och` - it was `:has(.val, .xch, .och)`, which every styling pass re-asked
   of every row's subtree. */
.rows1 .wrow.wx .r{flex-direction:column; align-items:flex-end; gap:2px}
.rows1 .wrow .fx{top:50%; bottom:auto; margin-top:-8.5px}
.rows1 .wrow.wx .fx{top:auto; margin-top:0; bottom:7px}
/* The list screen's rows sit in ONE card, as Glance's do - a column you read
   down, hairlines between - rather than a stack of separate frames. */
.wbody[data-screen="list"] .gbody.rows1{
  margin:8px 6px 10px; padding:2px 4px 4px; gap:0; border-radius:var(--r-xl);
  background:linear-gradient(180deg, rgba(255,255,255,.034), rgba(255,255,255,.014) 40%, rgba(255,255,255,.01));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.055), inset 0 0 0 1px rgba(148,163,184,.075),
             0 12px 28px -20px rgba(0,0,0,.9);
}
.wbody[data-screen="list"] .gbody.rows1.over{box-shadow:inset 0 0 0 1px var(--gold-line)}
/* What the list is NOT showing, and the two ways to change it: the stepper
   (how many of its first rows are live here) and the rest of it, one tap. */
.gmore{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding:4px 8px 7px 9px; font:400 11px var(--num); color:var(--dim);
}
.gpin.bare .gmore{padding-top:7px}
.gstep{
  display:inline-flex; align-items:center; gap:2px; height:24px; padding:0 2px;
  border-radius:12px; background:rgba(255,255,255,.035);
  box-shadow:inset 0 0 0 1px rgba(148,163,184,.08);
}
.gstep b{min-width:18px; text-align:center; font:500 11.5px var(--num);
         font-variant-numeric:tabular-nums; color:var(--text)}
.gstep button{
  width:24px; height:20px; border:0; border-radius:10px; background:none; cursor:pointer;
  color:var(--muted); font:500 14px/1 var(--sans);
  transition:color .14s, background-color .14s, transform .12s var(--ease);
  -webkit-tap-highlight-color:transparent;
}
.gstep button:hover:not(:disabled){color:var(--text); background:var(--hover)}
.gstep button:active:not(:disabled){transform:scale(.88); color:var(--gold-hi)}
.gstep button:disabled{opacity:.3; cursor:default}
.grest{
  background:none; border:0; cursor:pointer; padding:4px 2px 4px 8px;
  font:500 11.5px var(--sans); color:var(--muted); transition:color .14s;
}
.grest:hover{color:var(--gold-hi)}
.gall{font:400 11px var(--sans); color:var(--dim)}
.gnote{
  margin:0 9px 9px; padding:7px 9px; border-radius:var(--r);
  font:400 11px/1.45 var(--sans); color:var(--muted);
  background:rgba(228,199,120,.05); box-shadow:inset 2px 0 0 rgba(228,199,120,.45);
}
/* The hold: the held list lifts and follows the finger; the others step
   aside on a transition so the place is already open under it. */
#watch.pinlift{user-select:none; -webkit-user-select:none; touch-action:none}
#watch.pinlift .gpin{transition:transform .18s var(--ease)}
#watch.pinlift .gpin.held{
  transition:none; position:relative; z-index:5;
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.35), 0 14px 32px -10px rgba(0,0,0,.7);
}
.ghint{margin-left:auto; color:var(--dim); opacity:.8}
.gphead{-webkit-touch-callout:none; -webkit-user-select:none; user-select:none}
.gfoot{
  display:flex; align-items:center; gap:8px; margin:14px 12px 4px;
  font:400 10.5px var(--num); color:var(--dim);
}
.gmeter{position:relative; width:46px; height:3px; border-radius:2px; background:rgba(255,255,255,.07); overflow:hidden}
.gmeter i{position:absolute; inset:0 auto 0 0; width:calc(var(--used) * 100%);
          background:var(--gold); border-radius:2px; box-shadow:0 0 6px var(--gold-line)}
.gempty{
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  margin:18px 12px; font:400 12px var(--sans); color:var(--muted); line-height:1.45;
}
.gempty b{color:var(--text); font-weight:600}
.cpinned{
  display:inline-block; width:5px; height:5px; margin-left:6px; vertical-align:middle;
  border-radius:50%; background:var(--gold); box-shadow:0 0 6px var(--gold-line);
}
.mact.mpin.on{color:var(--gold-hi); box-shadow:inset 0 0 0 1px rgba(228,199,120,.35)}
@media (prefers-reduced-motion:reduce){ .wtab .ind{transition:none} }

/* ---- the company's mark, beside its ticker ----
   A round mark the way TradingView draws one: the logo where `logos.py` has
   one, the ticker's first letter on a colour of its own where it has not (a
   future, an index, a yield, a logo that failed). The letter is ALWAYS drawn
   and the image sits over it, so a failed load leaves the letter rather than
   a hole, and nothing jumps when the image arrives. The colour is the
   ticker's hash, held dark and desaturated so twelve of them down a list do
   not outshout the green and red they sit beside. */
.wlogo{
  position:relative; flex:none; width:20px; height:20px; border-radius:50%;
  display:grid; place-items:center; overflow:hidden; isolation:isolate;
  background:hsl(var(--lh,210) 28% 26%);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.08);
}
.wlogo b{font:600 10px/1 var(--sans); color:hsl(var(--lh,210) 45% 82%); letter-spacing:0}
.wlogo img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  border-radius:50%; background:#fff;
}
.wrow .wlogo{margin-right:2px}

/* ---- GLANCE, FINISHED: the surface a person opens to read and close ----
   Restraint over decoration, and every touch is one the rail already speaks:
   - a section is a card lifted off the panel by a hairline of light along
     its top edge and a soft shadow under it, not a frame drawn round it;
   - each list's name carries the breadth baseline the folders carry - how
     much of it is green and how much red, at a glance, before a number;
   - the list's median is a quiet outlined chip, so the eye reads the rows'
     solid pills as the figures and the header's as the summary;
   - the folder it lives in is a small-caps label rather than a second name;
   - the pills get an inner edge in their own colour, which is the
     difference between a tint and a lozenge;
   - "live" earns a breathing dot, and only while the tape is open. */
.whead.tab-glance .wtitle{font-size:15px; letter-spacing:.005em}
/* ⚠ THE BREATH IS TWO COMPOSITED LAYERS, NOT A BOX-SHADOW. It was one
   `::before` animating `box-shadow` (0 → 4px of rgba(47,208,138,.12)) beside
   its opacity, for ever - and a box-shadow cannot be composited, so while the
   lists were open the main thread restyled, repainted and committed the head
   EVERY frame: measured at 390x844 under x4 CPU, 359 paints and commits in 3s
   and 484ms of main-thread tasks, against 22 and 157ms this way. Same picture:
   the dot (`.gdot`, the old pseudo's box exactly) breathes its opacity, and
   the ring is its `::before` - inset by the 4px the shadow spread to, so a
   scale of 6/14 is a spread of 0 - growing and fading on the same clock, under
   the dot's opacity as the shadow was, and clipped by `.wsub` as it was.
   `::after` is the dot's own disc, painted after the ring. */
.whead.live .wsub .gdot{
  position:relative; display:inline-block; width:6px; height:6px; margin:0 6px 1px 1px;
  border-radius:50%; vertical-align:middle;
  opacity:.75; animation:gbreathe 2.4s ease-in-out infinite;
}
.whead.live .wsub .gdot::before,.whead.live .wsub .gdot::after{
  content:""; position:absolute; border-radius:50%;
}
.whead.live .wsub .gdot::before{
  inset:-4px; background:rgba(47,208,138,.12);
  opacity:0; transform:scale(.4286); animation:gring 2.4s ease-in-out infinite;
}
.whead.live .wsub .gdot::after{inset:0; background:var(--up)}
@keyframes gbreathe{0%,100%{opacity:.75} 50%{opacity:1}}
@keyframes gring{
  0%,100%{opacity:0; transform:scale(.4286)}
  50%{opacity:1; transform:scale(1)}
}
.wbody[data-screen="glance"]{
  background:radial-gradient(120% 40% at 50% 0, rgba(228,199,120,.035), transparent 70%);
}
.gpin{
  border-radius:var(--r-xl);
  background:linear-gradient(180deg, rgba(255,255,255,.034), rgba(255,255,255,.014) 40%, rgba(255,255,255,.01));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.055), inset 0 0 0 1px rgba(148,163,184,.075),
             0 12px 28px -20px rgba(0,0,0,.9);
}
.gphead{border-radius:var(--r-xl) var(--r-xl) 0 0; border-bottom-color:rgba(148,163,184,.06)}
.gphead::before{inset:auto 13px 3px 13px; opacity:.75}
.gpmeta{text-transform:uppercase; letter-spacing:.08em; font-size:9.5px; font-weight:500}
.gppct{
  justify-self:end; padding:2px 8px; border-radius:var(--r-sm);
  box-shadow:inset 0 0 0 1px currentColor; background:transparent;
}
.gppct.up{color:var(--up); box-shadow:inset 0 0 0 1px rgba(47,208,138,.35)}
.gppct.down{color:var(--down); box-shadow:inset 0 0 0 1px rgba(255,107,124,.35)}
.gppct.flat,.gppct.none{color:var(--dim); box-shadow:inset 0 0 0 1px rgba(148,163,184,.2)}
.rows1 .wrow .ch.up{box-shadow:inset 0 0 0 1px rgba(47,208,138,.16)}
.rows1 .wrow .ch.down{box-shadow:inset 0 0 0 1px rgba(255,107,124,.16)}
.rows1 .wrow .px{font-weight:500; letter-spacing:.01em}
.rows1 .wrow .sym{font-weight:600}
@media (prefers-reduced-motion:reduce){
  .whead.live .wsub .gdot{animation:none; opacity:1}
  .whead.live .wsub .gdot::before{animation:none; opacity:0}
}

/* ---- the screen itself ----
   `--dir` is +1 walking deeper and -1 coming back, written by render(). A
   repaint that did not change screen gets neither and does not move - an
   account refresh every thirty seconds must not re-play the journey. */
.wbody{display:flex; flex-direction:column; min-width:0}
/* ---- the back-drag: two screens, and the one behind is real ----

   The screen behind is DRAWN and dragged in rather than merely arrived at. The
   first version nudged the current screen 28px and jumped, and a gesture that
   shows you where you are going while you decide is the difference between a
   shortcut and a transition you can change your mind in the middle of.

   Both layers move on `transform` alone; the one behind travels at 35% of the
   finger, which is the parallax that makes the eye read it as UNDER rather
   than beside. No transition while a finger is on it — a transition racing a
   thumb is what "not smooth" means (rule 5) — and one on release, which is
   what finishes the journey. */
/* ⚠ PROMOTED WHILE THE RAIL IS OPEN, not on the frame it starts moving.
   Building the composited layer is a first-run cost paid on the frame the eye
   is most on - reported as a jump on the very first navigation and on no
   other. `.wscreen` exists only while the drawer is open, and it is exactly
   the thing that moves on every navigation, so it costs nothing the rest of
   the time. The same argument `#watch`'s own `will-change` already makes one
   level up. */
.wscreen{transition:transform 200ms var(--ease), opacity 200ms var(--ease);
         will-change:transform}
.wscreen.backing{transition:none; will-change:transform}
.wunder{
  position:absolute; left:0; right:0; z-index:2; overflow:hidden;
  background:var(--panel); pointer-events:none;
  transition:transform 200ms var(--ease), opacity 200ms var(--ease);
}
.wunder.backing{transition:none; will-change:transform, opacity}
/* The one you are on rides above it, and casts an edge so the two do not read
   as one long sheet sliding. */
#watch .wscreen{position:relative; z-index:3; background:var(--panel)}
/* ...and a screen `slide()` turned into the layer behind, in place: under
   the one arriving and clipped to the band on screen - what the `.wunder`
   wrapper was, without the wrapper - and `.wblock` over it is what makes it
   untouchable, because `pointer-events` would restyle all of it. */
#watch .wscreen.leaving{z-index:2; overflow:hidden}
#watch .wblock{position:absolute; left:0; right:0; z-index:2}
.wscreen.backing{box-shadow:-8px 0 24px rgba(0,0,0,.45)}
/* ⚠ THERE IS NO ENTRY ANIMATION, AND THAT WAS THE BLINK. The body used to
   come in from `opacity:0` with every card and row rising 7px behind it on a
   stagger - right when a navigation simply swapped the markup, and wrong the
   moment `slide()` existed: both ran on every navigation, so the screen came
   in from the side WHILE everything on it faded up from transparent. Two
   animations over one event, and what the eye reads is not two movements, it
   is a flash. The screen travelling IS the transition; what is on it has
   already arrived.

   `wrise` survives for the two things that genuinely APPEAR - the ⋯ menu and
   the new-list form - because a popover opening is one object arriving, not a
   whole screen of them. */
@keyframes wrise{
  from{opacity:0; transform:translate3d(0,7px,0)}
  to{opacity:1; transform:none}
}

/* ---- THE BREADTH BAR ----
   How much of a folder is green, as a shape rather than as a number.

   The specification this was built from asked for a ghost SPARKLINE behind
   each folder row. It was costed and dropped: a folder's sparkline is an
   intraday series for every symbol in it, aggregated - one fetch per symbol
   per folder, which is exactly the traffic the redesign exists to stop, and on
   a cold rail it draws nothing for ten seconds.

   This says the thing the sparkline was wanted for and is free: it is the
   quotes already on the row. `--gup` is the green share and `--gdn` the point
   where red stops; what is left is unpriced, and stays dark on purpose - a
   folder nobody has quoted must not look like a flat one (rule 6).

   IT IS A 3px BAND ALONG THE FOOT, NOT A WASH BEHIND THE ROW, and the shape
   was decided the first time anybody SAW it (2026-09-18 - it had never painted
   once, see below). Full height it was a hard vertical seam across the middle
   of every card with the red half sitting directly under the figures - the one
   thing this block's own note said it must never do, which it had been getting
   away with by being invisible. A band touches no text, so it can be read at
   0.6 instead of hidden at 0.085, and a hard edge is CORRECT for a bar where
   it was an artefact in a wash.

   ⚠ THE SHARES ARE `--gup`/`--gdn` AND MAY NEVER GO BACK TO `--up`/`--down`,
   WHICH IS WHAT THEY WERE UNTIL 2026-09-18. The palette's two tokens, set
   inline on the very card the figures sit in: a custom property INHERITS, so
   every `color:var(--up)` underneath resolved to `0.688` - invalid at
   computed-value time, which for an inherited property means `color` takes its
   PARENT's value, the UA button colour. Black here, iOS blue on the phone it
   was reported from, on every figure of the home screen at once. And the bar
   itself never painted: the name is read below as a COLOUR and as a POSITION,
   so whichever meaning arrived, the other one made the whole declaration
   invalid and `background` fell back to none. One collision, four symptoms,
   and it looked like four unrelated bugs. */
.wuniverse,.wpin,.wfolder,.wcard,.wsum,.gphead{position:relative; isolation:isolate}
.wuniverse::before,.wpin::before,.wfolder::before,.wcard::before,.wsum::before,.gphead::before{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  border-radius:inherit; opacity:.6;
  background-image:linear-gradient(90deg,
    var(--up) 0, var(--up) calc(var(--gup,0) * 100%),
    var(--down) calc(var(--gup,0) * 100%), var(--down) calc(var(--gdn,0) * 100%),
    transparent calc(var(--gdn,0) * 100%));
  background-repeat:no-repeat;
  background-position:bottom left;
  background-size:100% var(--breadth-h,3px);
  transition:opacity .3s var(--ease);
}
.wfolder:hover::before,.wcard:hover::before,.wuniverse:hover::before,.wpin:hover::before{
  opacity:.9;
}
/* ...AND IT IS A BASELINE RATHER THAN THE CARD'S FLOOR (the look pass,
   2026-09-27). The same shares on the same host, drawn as a 2px rule with
   rounded ends, inset from both edges and lifted off the corner radius, over
   a faint track - so the unpriced remainder still reads as dark rather than
   as a flat day (rule 6). The rule above is left exactly as it was, because
   `BAR_HOSTS` is asserted against its selector text. */
.wuniverse::before,.wpin::before,.wfolder::before,.wcard::before,.wsum::before,.gphead::before{
  inset:auto 12px 4px 12px; height:2px; border-radius:2px;
  background-color:rgba(255,255,255,.035); background-size:100% 100%;
}

/* ---- screen 1: the folders ----

   TWO LINES, AND THE SECOND ONE IS WHY. The rail is 216px on a desktop - the
   chart is the message, so the column is narrow and that is not negotiable -
   and a folder row wants a name, a count, its loudest mover, a percentage and
   a chevron. On one line the leader squeezed the percentage off the end:
   measured at 216px, "Indices & breadth 3 lists · 25 +2…" with the figure
   clipped mid-digit, which is the one thing on the row that must never be
   half-read.

   So the row is a grid: what it IS on top, what it is DOING underneath. The
   name and the percentage lead, because those are the two things a glance is
   for; the count and the loudest name are the second read. It also gives the
   breadth bar a body tall enough to be a shape rather than a stripe.

   ⚠ EVERY CHILD IS PLACED EXPLICITLY, AND THAT IS NOT TIDINESS. Grid places
   items with a definite ROW and an indefinite COLUMN in a pass of their own,
   after the auto-flow cursor has already moved - so `.uname` (auto) and
   `.upct` (`grid-row:1`) came out in the opposite order and the whole rail
   rendered right-to-left: the percentage first, then the name, then the
   chevron. It looks exactly like a direction bug and is not one. Naming both
   axes on every child removes the pass. */
.wuniverse,.wpin,.fgo{
  display:grid; align-items:center; gap:0 6px; text-align:left;
  grid-template-columns:auto 1fr auto auto;
  grid-template-rows:auto auto;
}
.wuniverse,.wpin{
  width:calc(100% - 12px); margin:8px 6px 0;
  padding:calc(8px * var(--card-scale)) calc(8px * var(--card-scale))
          calc(8px * var(--card-scale)) calc(10px * var(--card-scale));
  border-radius:var(--r-lg); cursor:pointer;
  background:rgba(255,255,255,.025); border:1px solid transparent;
  transition:border-color .14s, transform .14s var(--ease);
}
/* A TILE, NOT A FRAME: a faint lift off the panel and no edge at rest - the
   edge is the trail's to light, and a hover lifts the fill instead. */
.wuniverse:hover,.wpin:hover{background:rgba(255,255,255,.045)}
/* The glow owns the lift; `:hover` owns nothing but the press, or the two
   transforms take turns overriding each other on a desktop. */
.wuniverse:active,.wpin:active{transform:translateY(0) scale(.995)}
/* col 1 is the dot's, and only the account has one - everything else starts at
   col 2 so the three cards line up down their names. */
.pdot{grid-area:1/1/3/2; width:5px; height:5px; border-radius:50%;
      background:var(--gold); box-shadow:0 0 8px var(--gold-line)}
.uname,.pname,.fname{
  grid-area:1/2/2/3; font:600 calc(12.5px * var(--card-scale)) var(--sans); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.uname,.fname{color:var(--text)}
.pname{color:var(--gold-hi)}
.upct,.ppct,.fpct{
  grid-area:1/3/2/4; justify-self:end;
  font:500 calc(11.5px * var(--card-scale)) var(--num);
  font-variant-numeric:tabular-nums;
}
.umeta,.pmeta,.fmeta{
  grid-area:2/2/3/3; font:400 calc(10.5px * var(--card-scale)) var(--num);
  color:var(--dim); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* The account is the one list you did not choose to watch, so it is gold - the
   rail's whole vocabulary for "this is yours". The dot is a pin without a
   glyph: a pin points somewhere and this is not pointing, it is saying first. */
/* ...AND THE GOLD IS A LEADING EDGE AND A TINT, NOT A FRAME: one thin line
   down the left of the tile and a whisper of gold under it (the look pass). */
.wpin{background:rgba(228,199,120,.055); box-shadow:inset 2px 0 0 var(--gold)}
.wpin:hover{background:rgba(228,199,120,.085)}

/* ---- the two sections, and their verbs ----
   The home screen holds folders AND loose lists, and until it said so the
   model was unreadable: *"I do not understand how I make a list only of
   stocks and not a list of lists"*. A hairline said they were different and
   did not say HOW. Two words say it, and the `+` sits ON the heading, which
   puts the verb exactly where the thing it makes will appear. */
/* A heading in the words it is written in, with its count as a quiet number
   and no rule running to the edge: the spacer that held the rule still pushes
   the `+` to the end of the line, and now draws nothing. */
.whead2{
  display:flex; align-items:center; gap:6px; padding:14px 10px 5px;
  font:600 11.5px var(--sans); letter-spacing:.01em;
  color:var(--muted);
}
.whead2 span{font:500 11.5px var(--num); color:var(--dim)}
.whead2::after{content:""; flex:1; height:1px; background:none; order:1}
.wadd2{
  order:2; width:22px; height:22px; flex:none; display:grid; place-items:center;
  background:none; border:1px solid transparent; border-radius:var(--r-sm);
  color:var(--muted); cursor:pointer; font:400 16px/1 var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1);
}
.wadd2:hover{color:var(--text); background:var(--hover)}
.wadd2:active{background:var(--press)}
/* One line, once, when there is nothing in the section to explain it. It is
   the only prose on the rail and it earns its place by being the answer to
   the question that produced the section. */
.whint{
  margin:2px 8px 4px; padding:8px 10px; border-radius:var(--r);
  background:rgba(255,255,255,.02); border:1px solid transparent;
  font:400 11px/1.5 var(--sans); color:var(--dim);
}
.whint b{color:var(--muted); font-weight:600}
/* ---- the card in the hand ----
   A copy of the carried list, on `body` at `position:fixed` — the only place
   immune to BOTH the rail's scrolling and the screen being replaced under it,
   and being replaced is the point: taking the card to the leading edge turns
   the screen while the hand keeps holding it. `pointer-events:none`, so the
   hit test for what is UNDERNEATH keeps working through it.

   It is lifted off the surface rather than merely moved — a real shadow and
   the gold edge — and NOTHING about it transitions, because it is under a
   finger (rule 5). */
/* ⚠ `.wcard.wfloat`, NOT `.wfloat`, AND THAT ONE SELECTOR COST AN EVENING.
   The float carries `.wcard` too, for the look - and `.wcard{position:relative}`
   (added to anchor the ⋯) sits LATER in this file at equal specificity, so it
   won: the card computed to `relative`, flowed to the bottom of `<body>` at
   offsetTop 774, and the per-frame translate moved it from THERE. It tracked
   the finger horizontally and sat seven hundred pixels below it, which is
   exactly what *"I do not understand where the list is"* looked like from the
   other side. Nothing about the maths was ever wrong. */
.wcard.wfloat{
  position:fixed; left:0; top:0; z-index:60; pointer-events:none;
  will-change:transform; opacity:.97;
  /* IT IS SEEN TO LEAVE THE SURFACE. The copy is drawn exactly over the card
     it came from, so without this nothing visibly happens on the press at all
     - the only cue was a ghost the float was sitting on top of. */
  animation:wpick 150ms var(--ease) both;
  border-radius:var(--r-lg); background:var(--panel2);
  border:1px solid var(--gold); overflow:hidden;
  box-shadow:0 14px 34px rgba(0,0,0,.55), 0 0 0 1px var(--gold-line);
}
.wfloat .cmore,.wfloat .x{display:none}
/* ...and at the leading edge, saying the screen is about to give way. The rule
   grows over exactly the wait — the promise `GRAB_HOLD_MS` already makes over
   the chart, because a wait that does not say how long it is feels broken. */
.wfloat.atedge::after{
  content:""; position:absolute; left:0; top:0; bottom:0; width:3px;
  background:var(--gold); animation:wspring 240ms linear both;
}
@keyframes wspring{from{transform:scaleY(0)} to{transform:scaleY(1)}}
/* ⚠ SCALE ONLY, NEVER `transform`. The float's own transform is written per
   frame by `moveFloat`, so an animation on the same property would fight it -
   the card would twitch back to where the keyframe says it is, once a frame.
   `scale` is its own property and composes with it. */
/* ⚠ NO SCALE IN THE KEYFRAMES. The offset that keeps the grab point under the
   finger is computed FOR `PICK_SCALE`, so a scale that animates from 1 means
   the card is the wrong size for its own offset for the first 150ms - it
   starts somewhere else and slides into place, under a finger that has not
   moved. The shadow alone says it has been picked up. */
@keyframes wpick{
  from{box-shadow:0 0 0 rgba(0,0,0,0)}
  to{box-shadow:0 14px 34px rgba(0,0,0,.55), 0 0 0 1px var(--gold-line)}
}
/* ⚠ NO `scale` PROPERTY HERE. It is applied AFTER `transform`, so it would
   multiply the per-frame translate as well - the card moving 0.62px for every
   pixel of finger, and starting a hundred and nineteen away from it. The scale
   is written inside the transform by `paintFloat`, after the translate. */
/* ...and it is PUT DOWN on what took it, rather than vanishing at the moment
   the list rebuilds underneath. */
.wfloat.settling{
  animation:none;
  transition:transform 160ms var(--ease), opacity 160ms var(--ease), scale 160ms var(--ease);
}

/* JUST LANDED. One beat of the gold it was carried in, so the eye finds it
   without anything moving - the card is already where it belongs and this only
   says which one it is. */
.wcard.landed{animation:wland 900ms var(--ease) both}
@keyframes wland{
  0%{border-color:var(--gold); box-shadow:0 0 0 1px var(--gold), 0 0 22px -4px var(--gold)}
  60%{border-color:var(--gold); box-shadow:0 0 0 1px var(--gold-line), 0 0 14px -6px var(--gold)}
  100%{border-color:transparent; box-shadow:none}
}

/* WHERE IT CAME FROM, left in place and dimmed. A gap that simply closed would
   say the card had already gone; the ghost says it is still yours until you
   put it down, and it is where Escape puts it back. */
/* ⚠ FADED, NOT SNAPPED. Both of these changed in a single frame at the moment
   of pick-up - the card to a quarter and its chips to nothing - which is one
   more thing flashing on a surface that already has a card leaving it. */
.wcard.ghost{opacity:.25}
.wcard.ghost .cchips{opacity:0}
.wcard .cchips{transition:opacity 140ms var(--ease)}

/* ...and once the screen has turned, every folder says it will take it. This
   is the one time the rail shows more than one gold edge, and it is right:
   they are not a selection, they are a set of answers. */
/* ⚠ AND THE TARGETS COME UP, THEY DO NOT SWITCH ON. Every folder gaining a
   dashed outline and a gold name in the same frame is five or six cards
   changing at once, which is the blink the entry animation was - the same
   mistake one layer along. `outline-color` from transparent rather than
   `outline` from none, because a width appearing is a paint either way. */
#watch .wfolder{outline:1px dashed transparent; outline-offset:-1px;
                transition:outline-color 160ms var(--ease)}
#watch.filing .wfolder{outline-color:var(--gold-line)}
#watch .wfolder .fname{transition:color 160ms var(--ease)}
#watch.filing .wfolder .fname{color:var(--gold-hi)}

/* Where a dragged list will land. Gold, because filing is the user's own — and
   a folder is the only thing that can be landed ON: putting a list down
   anywhere else means out here, on the main screen. */
.wfolder.over{
  border-color:var(--gold); background:var(--gold-soft);
  box-shadow:0 0 0 1px var(--gold);
}
.wcard.drag{opacity:.4}

/* A FOLDER HAS TO LOOK LIKE ONE, now that a plain list sits beside it. Both
   are cards on the same screen, and the only thing saying which is which was
   the meta line — "4 lists · 53" against "21". A 9px outline of a folder in
   front of the name is the whole distinction, drawn in the dim ink so it reads
   as a KIND rather than as an ornament. */
.wfolders{display:flex; flex-direction:column; padding:8px 6px 0}
.wfolder .fname::before{
  content:""; display:inline-block; width:11px; height:9px; margin-right:6px;
  vertical-align:baseline; border:1.2px solid var(--dim); border-radius:2px;
  border-top-width:3px; border-top-color:transparent;
  box-shadow:inset 0 2px 0 -1px var(--dim);
}
.wfolder.lit .fname::before,.wfolder:hover .fname::before{border-color:var(--gold)}
/* ...and the loose lists get a hairline above them, so the home screen reads
   as two kinds of thing rather than one long column. */
.wcards.loose{margin-top:0}
/* THE LISTS STEP ASIDE to open a place for something being put down among
   them, the same answer the rows give one level down. `SETTLE_MS` is the same
   160ms the row gap uses, so the two read as one behaviour at two sizes. */
#watch.filing .wcards.loose .wcard{transition:transform 160ms var(--ease)}
.wfolders{gap:4px}
/* The row is the container - it carries the border, the breadth bar and the
   red block - and `.fgo` inside it is the button that opens the folder. Same
   arrangement as `.wcard`/`.cgo`, for the same reason: a <button> may not
   contain a <button>. */
.wfolder{
  position:relative; overflow:hidden; border-radius:var(--r-lg); cursor:pointer;
  background:rgba(255,255,255,.025); border:1px solid var(--line);
}
.fgo{
  width:100%; background:none; border:0;
  padding:calc(8px * var(--card-scale)) calc(8px * var(--card-scale))
          calc(8px * var(--card-scale)) calc(10px * var(--card-scale));
  cursor:pointer; border-radius:inherit;
}
.wfolder:hover{background:rgba(255,255,255,.045)}
.wfolder:active{transform:translateY(0) scale(.995)}
/* A FOLDER'S ✕ IS A PEEL BLOCK ON A FINGER AND A HOVER CONTROL ON A MOUSE
   (the fine-pointer block beside the pencil, below). Neither: hidden. */
.wfolder>.x{display:none}
.wfolder:hover .fname{color:var(--text)}
/* ---- THE PENCIL, AND IT IS THE ONLY CONTROL A FOLDER ROW HAS ----
   Renaming was the one thing you could not do to a folder (Dor, 2026-09-18).
   It is at the RIGHT-hand edge because that is where this rail puts "act on
   it" - a card's ⋯ and a row's ✕ are both there - and the chevron's own corner
   is "go in".

   IT RESERVES ITS ROOM IN `.fgo`'s PADDING rather than floating over the
   figures: a control that lands on top of a percentage when the pointer
   arrives is a percentage you cannot read while you are reaching for it. The
   column that gives the 30px is `1fr`, which is the NAME, and a folder name is
   elided anyway. */
.wfolder>.ren{
  position:absolute; right:4px; top:50%; transform:translateY(-50%); z-index:2;
  display:grid; place-items:center; width:22px; height:22px; padding:0;
  background:none; border:0; cursor:pointer;
  font-size:11px; line-height:1; color:var(--dim); opacity:0;
  transition:opacity .16s var(--ease), color .12s;
}
.wfolder .fgo{padding-right:32px}
/* A LIST CARD HAS NO PENCIL ON A DESKTOP, and that is not an omission: its ⋯
   already holds Rename beside Move, Merge and Delete, and a second control for
   one of the four verbs is a second place for it to drift. The card's block
   exists for the FINGER, which has no menu under its thumb. */
.wcard>.ren,.gtop>.ren{display:none}
/* The swipe's eye is the finger's; the one in a list's head serves both. */
.wcard>.eye,.gtop>.eye{display:none}
/* The eye beside `+` in a list's head: the head's own small control, lit gold
   (`.wact.on`) while the list is on Glance. */
.whead .wact.eye{display:grid; place-items:center; padding:0 4px; position:relative}
/* PINNED IS ORANGE, AND LOUD ENOUGH TO READ AT ARM'S LENGTH (Dor, 2026-10-05:
   gold on a 13% gold ground did not say "pressed" on the phone). `--warn` is
   the page's own orange for a control that is ON (#marksBtn wears it) - not
   `--stop`, which is a level's colour on the chart. */
.whead .wact.eye.on{
  color:var(--warn); background:rgba(240,160,60,.2);
  box-shadow:inset 0 0 0 1.5px rgba(240,160,60,.75), 0 0 14px -3px rgba(240,160,60,.7);
}
.whead .wact.eye svg{transition:transform .12s var(--ease)}
.whead .wact.eye:active svg{transform:scale(.8)}
/* ...and the press itself: the eye pops and a ring goes out from it, once, on
   the node the write re-rendered (`popped` in togglePin). */
.whead .wact.eye.pop{animation:eyepop 440ms cubic-bezier(.2,.9,.3,1.35)}
.whead .wact.eye.pop::after{
  content:""; position:absolute; inset:-1px; border-radius:inherit; pointer-events:none;
  animation:eyering 650ms ease-out forwards;
}
@keyframes eyepop{0%{transform:scale(.72)} 55%{transform:scale(1.2)} 100%{transform:scale(1)}}
@keyframes eyering{
  0%{box-shadow:0 0 0 0 rgba(240,160,60,.75)}
  100%{box-shadow:0 0 0 12px rgba(240,160,60,0)}
}
.cpinned.pop{animation:dotpop 700ms cubic-bezier(.2,.9,.3,1.4)}
@keyframes dotpop{0%{transform:scale(0); opacity:0} 60%{transform:scale(1.35); opacity:1} 100%{transform:scale(1)}}
@media (prefers-reduced-motion:reduce){
  .whead .wact.eye.pop,.whead .wact.eye.pop::after,.cpinned.pop,
  .wcard>.eye.pop svg,.gtop>.eye.pop svg{animation:none}
}
.whead .wact.eye svg{width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:1.8;
                     stroke-linecap:round; stroke-linejoin:round}
/* It arrives with the glow, like the card's ⋯: a permanent control on every
   folder row is five of them competing with the figures. A finger has no
   hover, so the coarse block leaves it visible - and unlike the ✕ it is not
   destructive, which is what makes that safe here and not there. */
.wfolder:hover .ren,.wfolder.lit .ren{opacity:1}
.wfolder>.ren:hover{color:var(--gold-hi)}
/* ⚠ AND ON A MOUSE THE ✕ STANDS BESIDE IT (Dor, 2026-10-06). Taking a folder
   apart was the phone's alone - its peel's far block - and on a desktop the
   only way was to file each list out of it one by one. It is the same verb and
   the same confirm ("Nothing is deleted"), so it gets the row's own hover
   control: arriving with the glow like the pencil, left of it, its room
   reserved in `.fgo`'s padding for the pencil's reason. Scoped to a FINE
   pointer because the coarse block places this node as a peel block (left:0,
   top:0, bottom:0) and a height written here would beat its stretch. */
@media (hover:hover) and (pointer:fine){
  .wfolder>.x{
    display:grid; place-items:center; position:absolute; z-index:2;
    right:28px; top:50%; transform:translateY(-50%);
    width:22px; height:22px; padding:0; border:0; border-radius:var(--r-xs);
    background:none; color:var(--dim); cursor:pointer; opacity:0;
    font-size:10px; line-height:1;
    transition:opacity .16s var(--ease), color .12s, background .12s;
  }
  .wfolder>.x::before{content:"\2715"}
  .wfolder:hover>.x,.wfolder.lit>.x{opacity:1}
  .wfolder>.x:hover{color:var(--down); background:var(--down-soft)}
  .wfolder .fgo{padding-right:54px}
}
/* The loudest name in the folder. It is what the eye wants first when the
   median says "quiet" and one name inside did not read the memo - and it is
   the reason a folder row is worth more than a number. On the second line,
   right-aligned under its own percentage, so the two read as a pair. */
.flead{
  grid-area:2/3/3/4; justify-self:end; min-width:0;
  font:400 calc(10.5px * var(--card-scale)) var(--num); color:var(--dim);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.flead.up{color:var(--up)} .flead.down{color:var(--down)}
/* Held back from the figure above it rather than from the row: it is the
   SECOND read, and the median is the first. */
.flead{opacity:.9}
.wchev{
  grid-area:1/4/3/5; align-self:center; justify-self:end;
  font-size:calc(12px * var(--card-scale)); color:var(--dim); line-height:1;
  transition:transform .16s var(--ease), color .12s;
}
.wfolder:hover .wchev,.wuniverse:hover .wchev,.wpin:hover .wchev,.cgo:hover .wchev{
  transform:translateX(3px); color:var(--gold);
}

/* WHEN THESE PRICES WERE TAKEN, on the row's own basis line.
   The home screen stopped quoting (2026-09-18: 105 lines wanted across three
   accounts against the bridge's 90), so most figures on it are DELIBERATELY
   not from this second - and a figure that is old and does not say so is a
   stale price presented as live, which the conventions forbid outright. It is
   empty when the figure IS current, so a live rail looks exactly as it always
   did and only a held one carries a clock. Mono, so it reads as a reading. */
.wage:not(:empty){
  margin-inline-start:6px; font:400 9.5px var(--num); color:var(--muted);
  font-variant-numeric:tabular-nums;
}
.wage:not(:empty)::before{content:"· "; color:var(--dim)}
/* ...but not when it LEADS the line, where a bullet has nothing before it. */
.wsess+.wage:not(:empty){margin-inline-start:0}
.wsess+.wage:not(:empty)::before{content:""}

/* WHICH SESSION A FOLDER'S FIGURES ARE, WHEN IT IS NOT THE OPEN MARKET.
   `stampSession` writes the rail's own short word - `pre`, `after`, `o/n`,
   `closed` - at the FRONT of the meta line, and leaves it empty while the
   regular session runs. So for most of the day the row is byte-for-byte what
   it always was, and the word appearing is itself the signal. It leads the
   line rather than trailing it like the clock, because it qualifies
   everything on the row and the clock only qualifies itself. */
.wsess:not(:empty){
  margin-inline-end:6px; padding:0 4px; border-radius:var(--r-xs);
  font:500 9.5px var(--num); letter-spacing:.02em; text-transform:uppercase;
  color:var(--warn); border:1px solid transparent;
  background:color-mix(in srgb, var(--warn) 13%, transparent);
}


/* every figure that is a move, wherever it sits */
.upct.up,.ppct.up,.fpct.up,.spct.up,.cpct.up,.sortpct.up{color:var(--up)}
.upct.down,.ppct.down,.fpct.down,.spct.down,.cpct.down,.sortpct.down{color:var(--down)}
.upct.flat,.ppct.flat,.fpct.flat,.spct.flat,.cpct.flat,.sortpct.flat{color:var(--muted)}
.upct.none,.ppct.none,.fpct.none,.spct.none,.cpct.none,.sortpct.none{color:var(--dim)}
/* THE TILE'S OWN FIGURE IS A PILL (the look pass) - the same soft pill the
   rows wear, so a folder's median and a symbol's move are read the same way.
   Nobody-priced is `none` and stays bare: a dash in a coloured pill would be a
   reading dressed up as one (rule 6). */
.upct,.ppct,.fpct,.cpct{
  padding:calc(1px * var(--card-scale)) calc(6px * var(--card-scale));
  border-radius:var(--r-sm); line-height:1.4;
}
.upct.up,.ppct.up,.fpct.up,.cpct.up{background:var(--up-soft)}
.upct.down,.ppct.down,.fpct.down,.cpct.down{background:var(--down-soft)}
.upct.flat,.ppct.flat,.fpct.flat,.cpct.flat{background:var(--raise)}

/* ---- screen 2: inside a folder ---- */
.wsum{
  display:flex; align-items:baseline; gap:7px; flex-wrap:wrap;
  margin:8px 6px; padding:8px 10px 12px; border-radius:var(--r-lg);
  background:rgba(255,255,255,.025); border:1px solid transparent;
}
.spct{font:600 15px var(--num); font-variant-numeric:tabular-nums}
/* THE BASIS, BESIDE THE FIGURE AND NOT IN A TOOLTIP (rule 3). A median over
   nine of twenty-one rows and a median over twenty-one are different claims,
   and the second one is the one a reader assumes unless told. */
.snote{font:400 10.5px var(--sans); color:var(--dim); flex:1 1 100%; line-height:1.4}
.slead{font:400 11px var(--num); color:var(--muted)}
.slead.up{color:var(--up)} .slead.down{color:var(--down)}

.wcards{display:flex; flex-direction:column; gap:4px; padding:0 6px}
.wcard{
  border-radius:var(--r-lg); background:rgba(255,255,255,.025); border:1px solid transparent;
  overflow:hidden; transition:border-color .14s, transform .14s var(--ease);
}
.wcard:hover{background:rgba(255,255,255,.045)}
/* Two lines, for the reason the folder row has two: a card carries a name, up
   to two tags, a percentage, a count and a chevron, and at 216px that is one
   line too many. The name and the figure lead; the tags and the count are the
   second read, which is also the order you want them in. */
/* Two lines, for the reason the folder row has two: a card carries a name, up
   to two tags, a percentage, a count and a chevron, and at 216px that is one
   line too many. The name and the figure lead; the tags and the count are the
   second read, which is also the order you want them in. Every child placed
   explicitly, for the reason set out above the folder rows. */
.cgo{
  display:grid; grid-template-columns:1fr auto auto; grid-template-rows:auto auto;
  align-items:center; gap:0 6px; width:100%;
  padding:calc(8px * var(--card-scale)) calc(8px * var(--card-scale))
          calc(5px * var(--card-scale)) calc(10px * var(--card-scale));
  background:none; border:0; cursor:pointer; text-align:left;
}
.cname{
  grid-area:1/1/2/2; font:600 calc(12.5px * var(--card-scale)) var(--sans);
  color:var(--text); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.cgo:hover .cname{color:var(--text)}
.cpct{grid-area:1/2/2/3; justify-self:end;
      font:500 calc(11.5px * var(--card-scale)) var(--num);
      font-variant-numeric:tabular-nums}
.ctags{grid-area:2/1/3/2; display:flex; align-items:center; gap:3px; min-width:0}
.cmeta{grid-area:2/2/3/3; justify-self:end;
       font:400 calc(10.5px * var(--card-scale)) var(--num); color:var(--dim)}
.cgo .wchev{grid-area:1/3/3/4}
.wcard .tag{
  font:500 calc(9.5px * var(--card-scale))/1 var(--sans); border-radius:var(--r-xs);
  padding:calc(2.5px * var(--card-scale)) calc(5px * var(--card-scale)); flex:none;
  color:var(--agent); border:1px solid transparent; background:var(--agent-soft);
}
/* HOW MANY OF THESE ARE ALSO SOMEWHERE ELSE IN THIS FOLDER. A fact, in the
   colour of a warning and never a button that tidies: Dor's own "Index
   futures" shares five rows with the built-in one and carries three it has
   not got, so the obvious cleanup would have quietly cost him three
   instruments. Merge copies; nothing here deletes. */
.wcard .tag.dup{color:var(--warn); background:var(--warn-soft)}
/* THE THREE THAT MOVED, not the three that happen to be first - a peek is for
   deciding whether to walk in, and the same three tickers every day decide
   nothing. Tap one and it charts without opening the list at all, which is the
   fastest path on the rail. */
.cchips{display:flex; gap:3px; flex-wrap:wrap;
        padding:0 calc(8px * var(--card-scale)) calc(7px * var(--card-scale))
                  calc(10px * var(--card-scale))}
.cchip{
  display:inline-flex; align-items:baseline; gap:4px; min-width:0;
  padding:calc(2px * var(--card-scale)) calc(6px * var(--card-scale));
  background:var(--raise); border:1px solid transparent; border-radius:var(--r-xs);
  cursor:pointer; font:500 calc(11px * var(--card-scale)) var(--num); color:var(--dim);
  transition:background-color .12s, transform .12s var(--ease), color .12s;
}
.cchip i{max-width:84px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.cchip i{font-style:normal; color:var(--text); font-weight:500}
.cchip:hover{background:rgba(255,255,255,.09); transform:translateY(-1px)}
.cchip:hover i{color:var(--gold-hi)}
.cchip.up{color:var(--up)} .cchip.down{color:var(--down)} .cchip.none{color:var(--dim)}

/* ---- screen 3: the sort bar over one list ----
   Sticky, under the head, because the order a list is read in is a thing you
   change while you are reading it. The choice is kept PER LIST: "biggest
   mover first" is right for a sector basket and wrong for a list whose order
   is the argument - which is what Manual is, and why it is the default. */
.wsort{
  display:flex; align-items:center; gap:3px; padding:5px 8px;
  position:sticky; top:38px; z-index:5; background:var(--panel);
  border-bottom:1px solid var(--line);
}
.wsortb{
  padding:3px 8px; background:none; border:1px solid transparent; border-radius:var(--r-sm);
  color:var(--muted); cursor:pointer; font:500 11px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1);
}
.wsortb:hover{color:var(--text); background:var(--hover)}
.wsortb.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
/* A HOLD ON A SORTED LIST SAYS WHY NOTHING LIFTED, ON THE BAR THAT IS THE WAY
   OUT (`sortNote` in watchlist.js): a line under the buttons, sticky with them,
   and Manual breathes gold once. In the rail and not over the chart, which a
   phone's drawer covers. */
.wsort.noting{flex-wrap:wrap}
.wsortnote{
  flex-basis:100%; padding:4px 2px 1px; font:400 11px/1.35 var(--sans); color:var(--muted);
  animation:wsnIn .16s var(--ease);
}
@keyframes wsnIn{from{opacity:0; transform:translateY(-3px)}}
.wsortb.ask{animation:wsnAsk 1.2s var(--ease) 2}
@keyframes wsnAsk{50%{color:var(--gold-hi); box-shadow:inset 0 0 0 1px var(--gold)}}
@media (prefers-reduced-motion:reduce){ .wsortnote,.wsortb.ask{animation:none} }
/* On the sub-line rather than on the sort bar - see listHtml. It is the one
   figure in a `.wsub`, so it takes the mono face back off the sentence around
   it and nothing else. */
.wsub .sortpct{font-family:var(--num); font-weight:500; font-variant-numeric:tabular-nums}

/* ---- the one destructive menu, and the verb this redesign is for ---- */
.wmove{
  display:flex; flex-direction:column; gap:6px; margin:8px 6px; padding:9px 10px;
  background:var(--panel2); border:1px solid transparent; border-radius:var(--r-lg);
  box-shadow:inset 0 1px 0 var(--hi);
  animation:wrise .18s var(--ease) both;
}
.mrow{display:flex; align-items:center; gap:4px; flex-wrap:wrap}
.mlbl{font:500 11px var(--sans); color:var(--dim); flex:none; width:44px}
.mfold,.mact{
  padding:4px 8px; background:var(--hover); border:1px solid transparent;
  border-radius:var(--r-sm); color:var(--muted); cursor:pointer; font:500 11px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1);
}
.mfold:hover,.mact:hover{color:var(--text); background:var(--press)}
.mfold.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
.mact.del:hover{color:var(--down); background:var(--down-soft)}
.mrow.end{justify-content:flex-end}
.mact.nok{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
/* Inside a card the menu is not a floating box - it is the bottom of the card
   it belongs to, so it loses its own border and keeps only the rule above it.
   A second outline inside an outline reads as two objects. */
.wcard .wmove{margin:0 8px 8px 10px; border-color:transparent; background:rgba(0,0,0,.22); box-shadow:none}
/* ⚠ `⋯` IS A SIBLING OF THE CARD'S GRID, NOT A CHILD OF IT, so `grid-area`
   does nothing to it — it laid out in flow and came out as three tiny dots
   under the tag, in the middle of the card. It is anchored to the card's own
   bottom-right corner instead, level with the chips, which is also where it
   belongs: the chevron's corner is "go in" and this one is "act on it". */
/* ...and it STEPS FORWARD on the card the pointer is on. A permanent ⋯ on
   every card is five of them competing with the figures; one that arrives with
   the glow is the actions belonging to the thing you are already looking at.
   A finger has no hover, so the coarse block leaves it visible there. */
.wact.cmore{
  position:absolute; right:4px; bottom:3px; z-index:2;
  display:grid; place-items:center; width:26px; height:24px; font-size:15px;
  line-height:1; color:var(--dim); opacity:.35;
  transition:opacity .16s var(--ease), color .12s;
}
.wcard.lit .cmore,.wact.cmore.on{opacity:1; color:var(--gold)}
.wcard{position:relative}
/* ...and the chips stop short of it, or the last one runs underneath. */
.wcard .cchips{padding-right:34px}

/* MAKING A LIST IS A FORM, NOT A `prompt()`. A browser prompt asks for a name
   and says nothing about the fact that a list LIVES somewhere — so the
   hierarchy was a thing you could only learn by getting it wrong. The folder
   chips are here even when the folder is already decided, because the point of
   them is not the choice, it is the sentence: this list, in that folder. */
.wnewform{
  display:flex; flex-direction:column; gap:6px; margin:8px 6px; padding:9px 10px;
  background:var(--panel2); border:1px solid transparent; border-radius:var(--r-lg);
  box-shadow:inset 0 1px 0 var(--hi);
  animation:wrise .18s var(--ease) both;
}
.wnewform input{
  width:100%; height:28px; padding:0 8px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r);
  font:400 12.5px var(--sans); outline:none;
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
.wnewform input:focus{border-color:var(--gold-line); box-shadow:var(--ring)}

/* ---- screen 4: every symbol, once ---- */
.wchips{
  display:flex; align-items:center; gap:3px; padding:5px 8px;
  position:sticky; top:38px; z-index:5; background:var(--panel);
  border-bottom:1px solid var(--line);
}
.wchip{
  padding:3px 8px; background:none; border:1px solid transparent; border-radius:var(--r-sm);
  color:var(--muted); cursor:pointer; font:500 11px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1);
}
.wchip:hover{color:var(--text); background:var(--hover)}
.wchip.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}

/* The two ends, named. A sorted list of 115 rows is the accordion's problem
   with the headers taken off, so the default is what moved - and what is NOT
   on screen says so, in `.wmore`, rather than being quietly dropped. */
/* The two ends keep their direction's colour, taken down toward the muted
   ink: it says which end this is without competing with the figures under it. */
.wgap{
  display:flex; align-items:center; gap:6px; padding:12px 10px 5px;
  font:600 11.5px var(--sans); letter-spacing:.01em;
  color:var(--muted);
}
.wgap span{font:500 11.5px var(--num); color:var(--dim)}
.wgap.up{color:color-mix(in srgb, var(--up) 70%, var(--muted))}
.wgap.down{color:color-mix(in srgb, var(--down) 70%, var(--muted))}
.wgap::after{content:""; flex:1; height:1px; background:none}
.wmore{
  margin:10px 8px; padding:7px; background:rgba(255,255,255,.025); border:1px solid transparent;
  border-radius:var(--r); color:var(--muted); cursor:pointer; font:400 11px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1);
}
.wmore:hover{color:var(--text); background:var(--hover)}
/* The lists a symbol is in, on its own row - membership as a TAG, which is
   what it always was in the store. This is the line that stops a stock in
   three lists being three rows you scroll past. */
.wbody[data-screen="all"]>.wrow{margin:0 6px 3px}
.wbody[data-screen="all"]>.wrow:first-of-type{margin-top:2px}
.wrow.flat .nm.in{color:var(--muted); opacity:.85}
.wrow .tag.held{
  font:500 9.5px/1 var(--sans); color:var(--gold); border:1px solid transparent;
  background:var(--gold-soft); border-radius:var(--r-xs); padding:2px 4px; flex:none;
}
.wtick{
  flex:none; width:13px; height:13px; border-radius:4px; border:1.5px solid var(--line2);
  transition:background .12s, border-color .12s;
}
.wtick.on{background:var(--gold); border-color:var(--gold); box-shadow:0 0 8px var(--gold-line)}
.wpick-bar{
  display:flex; align-items:center; gap:6px; padding:7px 10px;
  background:var(--panel2); border-bottom:1px solid var(--gold-line);
  position:sticky; top:68px; z-index:5;
}
.plbl{font:500 10.5px var(--sans); color:var(--gold-hi); flex:1}
.pact{
  padding:4px 9px; background:var(--hover); border:1px solid transparent;
  border-radius:var(--r-sm); color:var(--muted); cursor:pointer; font:500 11px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1);
}
.pact:hover:not(:disabled){color:var(--text); background:var(--press)}
.pact:disabled{opacity:.4; cursor:default}

/* ---- the heat map ----
   A hundred and fifteen tiles are scanned in a second; a hundred and fifteen
   rows are not. Five steps rather than a continuous ramp, because at tile size
   a smooth alpha reads as a smudge and the eye sorts steps far faster than it
   sorts shades. Step 0 is "not priced" and paints as the panel - which is the
   honest picture of not known (rule 6). */
.wheat{
  display:grid; grid-template-columns:repeat(auto-fill,minmax(62px,1fr));
  gap:2px; padding:8px 6px;
}
.wtile{
  display:flex; flex-direction:column; align-items:flex-start; justify-content:center;
  gap:1px; min-height:40px; padding:5px 6px; cursor:pointer; overflow:hidden;
  border:1px solid transparent; border-radius:var(--r-xs); background:rgba(255,255,255,.03);
  transition:transform .12s var(--ease), border-color .12s, box-shadow .14s;
}
.wtile i{font:500 10px var(--num); font-style:normal; color:var(--text);
         max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.wtile b{font:400 9.5px var(--num); font-weight:400; color:var(--dim);
         font-variant-numeric:tabular-nums}
.wtile:hover{transform:scale(1.045); border-color:var(--gold-line); z-index:2;
             box-shadow:0 6px 18px rgba(0,0,0,.45)}
.wtile.up.s1{background:rgba(47,208,138,.10)} .wtile.up.s2{background:rgba(47,208,138,.20)}
.wtile.up.s3{background:rgba(47,208,138,.32)} .wtile.up.s4{background:rgba(47,208,138,.46)}
.wtile.up.s5{background:rgba(47,208,138,.62)}
.wtile.down.s1{background:rgba(255,93,115,.10)} .wtile.down.s2{background:rgba(255,93,115,.20)}
.wtile.down.s3{background:rgba(255,93,115,.32)} .wtile.down.s4{background:rgba(255,93,115,.46)}
.wtile.down.s5{background:rgba(255,93,115,.62)}
/* Past the third step the ink under the label is bright enough that a dim
   figure stops being readable - so the text comes up to meet it rather than
   the tile coming down. */
.wtile.s4 b,.wtile.s5 b{color:var(--text)}
.wtile.s5 i{color:#fff}

/* ---- THE TRAIL ----

   Everything on the rail is a card, and a pointer drawn down it lights the
   card FRAMES one after another: the one under the hand is gold, the one it
   just left is on its way back, the one before that is nearly home. On a
   desktop the same thing happens under a mouse moving without pressing — one
   handler in `watchlist.js`, so a finger and a pointer cannot drift apart.

   THREE VERSIONS WERE WRONG BEFORE THIS ONE AND EACH WAS WRONG IN ITS OWN WAY:

     1. A band that followed the pointer. It lit the SPACE rather than the
        object — it says where your hand is, which you already know, and says
        nothing about which card it is on.
     2. A steady glow on the card under the pointer. Right object, no memory:
        it snaps from one card to the next, which reads as a light being
        switched rather than as something travelling.
     3. A sheen and a halo laid over the card. Right idea, too much of it —
        Dor's correction was *"there should be no extra light; the card itself,
        its frame, does the lighting"*. A second luminous layer over a row of
        numbers is a film over the thing you are reading, and this rail already
        spends its brightness on the figures.

   SO IT IS THE BORDER AND NOTHING ELSE — the edge the card already has,
   travelling from `--line` to gold and back. One property, no extra layer, no
   gradient, no per-frame arithmetic.

   WHAT MAKES IT PLAY IN SEQUENCE IS THE ASYMMETRY, AND THE BROWSER DOES ALL OF
   IT. `border-color` reaches gold in 70ms and returns over 700ms, so every
   card the pointer crossed is mid-fade at once, each having started the moment
   the pointer left it. That is a correctly staggered chase computed by the
   compositor from a single class toggle per card — no rAF loop, no timers, no
   list of decaying intensities to walk (rule 5), which is the only reason a
   twenty-row list can do this at all. The ease is `ease-out` on the way back:
   gold, a fast drop, then a long faint remainder, which is how light actually
   leaves a surface.

   NOTHING MOVES AND NOTHING FILLS. A trail that also lifted or tinted its
   cards would be a second thing happening in the same frames, and the list
   would read as unsettled rather than as lit.  */
.wrow,.wcard,.wfolder,.wpin,.wuniverse,.wtile{
  transition:border-color 700ms cubic-bezier(.22,.61,.36,1),
             box-shadow 700ms cubic-bezier(.22,.61,.36,1),
             opacity 140ms var(--ease),
             background .14s;
}
.wrow.lit,.wcard.lit,.wfolder.lit,.wpin.lit,.wuniverse.lit,.wtile.lit{
  border-color:var(--gold);
  /* THE FRAME LIT, NOT A LIGHT OVER THE CARD. One pixel of gold at 216px is a
     change you can measure and not one you can feel - the head read at alpha
     0.96 against a 0.13 base and still looked flat in a screenshot. So the
     edge carries its own short glow: a half-pixel of extra weight on the line
     and a 12px bloom hugging it, both TIGHT to the border and neither of them
     reaching the middle of the card. Nothing is laid over the numbers, which
     is the whole distinction - `-5px` spread is what keeps the bloom on the
     edge rather than under the row. */
  box-shadow:0 0 0 .5px rgba(228,199,120,.45), 0 0 12px -5px var(--gold);
  transition-duration:70ms, 70ms, .14s;
  transition-timing-function:linear, linear, var(--ease);
}
/* Two things draw with `box-shadow` on a tile that the trail also lights - the
   account's leading edge and the row on the chart - so the lit state carries
   them rather than wiping them for the length of the glow. */
.wpin.lit{box-shadow:inset 2px 0 0 var(--gold), 0 0 0 .5px rgba(228,199,120,.45), 0 0 12px -5px var(--gold)}
.wrow.on.lit{box-shadow:inset 2px 0 0 var(--gold), 0 0 0 .5px rgba(228,199,120,.45), 0 0 12px -5px var(--gold)}

/* ---- THE TICK FLASH ----
   A layer with nothing in it but a colour, animated on `opacity` alone, so the
   row underneath is never laid out or repainted. `background-color` on the row
   itself was the obvious version and it is the expensive one: the row is text,
   a price and two percentages, and sixty of them blinking once a second is the
   shape of "the website feels laggy".

   Only on a REAL change of price - a poll that answered with the identical
   number is not a tick, and a rail that blinks every second whatever the
   market is doing has taught the eye to stop reading it. */
.wrow .fx{
  position:absolute; inset:0; pointer-events:none; opacity:0;
  border-radius:inherit;
}
/* ...AND IT PULSES THE PILL RATHER THAN THE ROW (the look pass): the same
   layer, laid over the change pill in the row's bottom-right corner - the
   row's own padding from its padding box - so a tick is a breath in the
   column of pills rather than a flash across the name. */
.wrow .fx{inset:auto 8px 4px auto; width:58px; height:17px; border-radius:var(--r-sm)}
.wrow .fx.up{background:var(--up-soft); animation:wflash .3s ease-out}
.wrow .fx.down{background:var(--down-soft); animation:wflash .3s ease-out}
@keyframes wflash{from{opacity:1} to{opacity:0}}

/* ---- the whole of it, for somebody who asked for less of it ----
   The flashes and the journeys go; the COLOUR stays. Every one of them is an
   ornament on a fact that is also written down - the flash says a price
   changed and the price is right there, the slide says you went deeper and the
   head says where you are - so taking them away costs nothing that is only
   said that way. */
@media (prefers-reduced-motion:reduce){
  .wmove{animation:none}
  .wrow .fx{display:none}
  /* THE CHASE GOES AND THE HEAD STAYS. Which card the pointer is on is a fact
     worth saying and the border says it with nothing moving; the sequence is
     the part that is motion, so the change is instant both ways and only the
     card under the pointer is ever gold. */
  .wrow,.wcard,.wfolder,.wpin,.wuniverse,.wtile{transition:none}
  .wrow.lit,.wcard.lit,.wfolder.lit,.wpin.lit,.wuniverse.lit,.wtile.lit{
    transition:none; box-shadow:none;
  }
  .wtile.lit{transform:none}
  .wfolder:hover,.wuniverse:hover,.wpin:hover,.wtile:hover,.cchip:hover,.wback:hover{
    transform:none;
  }
  .wfolder:hover .wchev,.wuniverse:hover .wchev,.wpin:hover .wchev,.cgo:hover .wchev{
    transform:none;
  }
}

/* A LIST IS A COLUMN OF CARDS, like everything else on the rail. It was a
   flush list of flush rows, which is the right shape for an accordion — every
   pixel of the rail was list — and the wrong one for a surface where the thing
   under your finger is meant to answer: a flush row has no edge to light.
   The gap is 3px and the padding is 5/8, so 21 rows still fit the way they
   did; what changed is that each one is now an OBJECT. */
.gbody{display:flex; flex-direction:column; gap:3px; padding:6px 6px 8px}
.gbody.over{background:var(--gold-soft); box-shadow:inset 0 0 0 1px var(--gold-line)}

/* ---- carrying a LIST, inside its folder ----
   The gesture the group headers used to carry, one level up: a 380ms hold on a
   card and it lifts. While one is up the cards shed their chips, so the whole
   folder is on screen at once and the reorder never needs a scroll halfway
   through - the same reason the groups used to collapse to their headers.

   ⚠ IT REORDERS WITHIN ONE FOLDER, and that is a fact about `endLift` rather
   than about this stylesheet: `from` and `to` are indices in the cards on
   screen, and they are translated back to `groups` by id. A card carried in
   Macro cannot move anything in Themes. */
#watch.dragging{user-select:none; touch-action:none}
/* ⚠ AND IT NO LONGER COLLAPSES THE LIST. Every card losing its chip row and
   the summary block vanishing was right for the ACCORDION, where a carried
   group folded everything to its headers so the whole order fitted on one
   screen. With the card floating in the hand there is nothing to make room
   for - and the list jumping two hundred pixels shorter at the moment you
   pick something up is what was reported as *"it gets squeezed"*. The list
   stays exactly where it was; only the controls go, because nothing on it can
   be pressed while a card is in the air. */
/* ...and NOTHING is taken out of the flow, not even the "+ new list" button:
   it is 70px of the rail, and the list shifting 70px under your hand at the
   moment you pick something up is the same complaint one notch quieter. What
   changes is only what can be PRESSED, which costs no layout. */
#watch.dragging .wnew,
#watch.dragging .wact{opacity:0; pointer-events:none}
/* ...and they FADE. One ⋯ per card going to nothing in a single frame is a row
   of them blinking out together. */
.wnew,.wact{transition:opacity 140ms var(--ease), color .12s, background .12s}
/* A card's first job is to open its list; carrying it is the held gesture, so
   the cursor says click, not drag. */
.wcard,.cgo{cursor:pointer}
.cname{pointer-events:none}              /* never start a text selection here */
/* The carried card is `.wfloat` on `body` now, not the section in flow — see
   *the card in the hand*. What stays here is the cursor. */
.wcard.ghost .cgo{cursor:grabbing}
/* A LIST'S RED BLOCK IS THE PHONE'S ALONE. On a desktop the `⋯` menu is the
   right control, with the confirm it has always had, and a small ✕ beside a
   list name is a control you have to already know about. Turned on in the
   coarse block, exactly as a row's is. */
.wcard>.x,.gtop>.x{display:none}
/* Where it will land. Gold, because the order is the user's. */
.wline{
  position:absolute; left:6px; right:6px; height:2px; margin-top:-1px; z-index:4;
  background:var(--gold); border-radius:2px; pointer-events:none;
  box-shadow:0 0 10px var(--gold-line);
}

/* ---- carrying a ROW, inside its own group ----
   Narrower than carrying a group, and deliberately: a row only ever moves
   within its own list, so nothing else on the rail has to fold out of the way
   and the group you are rearranging stays readable while you do it. */
#watch.rowdrag{user-select:none; touch-action:none}
#watch.rowdrag .ga,#watch.rowdrag .x{opacity:0}   /* nothing to press while carrying */
/* ...and they fade, for the reason the cards' own controls do one level up: a
   row of controls going to nothing in a single frame is a row of them blinking
   out together. */
.wrow .x{transition:opacity 140ms var(--ease), color .12s, background .12s}
/* THE LIST OPENS A GAP UNDER THE FINGER rather than drawing a line promising
   one. The rows either side step aside by exactly one row's height, so the
   place is already open when it is let go and the drop moves nothing - see
   gapShift() in watchlist.js. The carried row is the one exception: it
   follows the finger, so it may not be transitioned while it is held. The
   duration is SETTLE_MS, which is also how long the drop waits before the
   rail is rebuilt. */
#watch.rowdrag .wrow{transition:transform .16s var(--ease)}
.wrow.rlift{
  z-index:5; border-radius:var(--r); transition:none;
  background:var(--panel2); box-shadow:0 8px 20px rgba(0,0,0,.5);
  outline:1px solid var(--gold-line); outline-offset:-1px;
}
.wrow.rlift .sym{color:var(--gold-hi)}
/* A LIST IS A QUOTE COLUMN (the look pass, 2026-09-27): the rows sit on the
   panel with no fill of their own, and the right edge is a column of soft
   pills that reads as the list's breadth at a glance. Each row keeps its
   hairline frame, because the frame is what the trail lights (above) and a
   flush row has no edge to light. */
.wrow{
  position:relative; display:flex; align-items:center; gap:6px;
  padding:4px 8px 4px 9px; cursor:pointer; min-width:0;
  background:transparent; border:1px solid var(--line); border-radius:var(--r);
}
.wrow:hover{background:var(--hover)}
/* YOU ARE HERE. The instrument on the chart is the one gold thing in a list: a
   2px leading edge drawn inside the row, never a border that changes its size,
   on the raised fill, with its name in the pale gold. */
.wrow.on{background:var(--raise); box-shadow:inset 2px 0 0 var(--gold)}
.wrow.drag{opacity:.4}
.wrow.dead{cursor:default; opacity:.5}
.wrow .l{display:flex; flex-direction:column; gap:1px; min-width:0; flex:1}
.wrow .sym{
  font:500 13px var(--sans); color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.wrow.on .sym{color:var(--gold-hi)}
.wrow .l>span:not(.sym),.wrow .opt,.wrow .qty,.wrow .nm{
  font:400 11.5px var(--sans); color:var(--dim);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.wrow .opt{font-family:var(--num); color:var(--muted)}
.wrow .opt.warn{color:var(--warn)}          /* an expiry inside a week */
.wrow .opt+.qty{margin-left:5px}
.wrow .l{flex-direction:row; flex-wrap:wrap; align-items:baseline; gap:0 5px}
.wrow .l .sym{flex-basis:100%}
/* ...AND ON A ROW CARRYING AN EXTENDED PRICE THE SYMBOL MAY REACH INTO THE
   FIGURES' COLUMN. `pre 240.61 +0.72%` widened the right column by the price,
   and on a desktop rail that came out of the left one: `NVDA` read `NV...`.
   The figures' first line is only the price, right-aligned, so the space over
   the extended figure is empty - the symbol takes that, the company name under
   it stays inside its own column and truncates as it always did. `.xs` is
   only a row with nothing else on the top line (no position, no contract). */
.wrow.xs .l .sym{flex:0 0 calc(100% + 72px)}
/* THE ALERTS LIST'S OWN LINE: each armed alert on the row, its price and how
   far the row's price is from it, nearest first - in the alert's own magenta
   (`--alert`), the colour its line wears on the chart, so a chip here and the
   line there read as one thing. Wraps rather than clips - a level cut in half
   is a level misread. Under 1% away the chip fills. */
.wrow .l>.walr{flex-basis:100%; display:flex; flex-wrap:wrap; gap:3px 4px;
  margin-top:3px; white-space:normal; overflow:visible}
.walr b{font:500 10.5px var(--num); color:var(--alert); white-space:nowrap;
  padding:1px 5px; border-radius:var(--r-sm);
  background:var(--alert-soft); border:1px solid var(--alert-line)}
.walr b i{font-style:normal; font-weight:400; opacity:.75; margin-left:4px}
.walr b.near{background:var(--alert); border-color:var(--alert); color:#12060f}
.walr b.near i{opacity:.8}
.wrow .r{
  display:flex; flex-direction:column; align-items:flex-end; gap:1px; flex:none;
  font-variant-numeric:tabular-nums;
}
.wrow .top{display:flex; align-items:baseline; gap:6px}
.wrow .px{font:500 13px/16px var(--num); color:var(--text)}
/* What the position made today, in dollars - beside the price, because the
   top line of a portfolio row is the money and the second line is the
   position that produced it. */
/* What the position is worth. Neutral, because an amount is not a gain - it
   steps back and lets the coloured figure beside it carry the day. */
.wrow .val{font:400 11.5px var(--num); color:var(--muted)}
.wrow .pnl{font:500 11.5px var(--num)}
.wrow .pnl.up{color:var(--up)} .wrow .pnl.down{color:var(--down)}
.wrow .pnl.flat{color:var(--dim)}
.wrow .chs{display:flex; align-items:baseline; gap:5px}
/* THE SIGNATURE: the day's move as a soft pill, one fixed minimum width so the
   pills stand in a straight column down the right edge. `.ch` is a flex item
   of `.chs`, so it is already a block - the width and height are its own, and
   nothing about its display changes. */
.wrow .ch{
  font:500 11.5px/17px var(--num); min-width:58px; height:17px; padding:0 6px;
  border-radius:var(--r-sm); text-align:right; white-space:nowrap;
}
.wrow .ch.up{color:var(--up); background:var(--up-soft)}
.wrow .ch.down{color:var(--down); background:var(--down-soft)}
.wrow .ch.flat{color:var(--dim); background:var(--raise)}
/* The contract's own move, beside the stock's. It reads left-to-right off the
   strike on the line under it: 95C · 3d · x4 ... +48.35% · +5.78%. */
.wrow .och{font:400 11px var(--num)}
.wrow .och.up{color:var(--up)} .wrow .och.down{color:var(--down)}
.wrow .och.flat,.wrow .och.none{color:var(--dim)}
/* The dot after the contract's own move belongs before whatever follows it -
   the hours around the session when the row has them, the session's move when
   it does not. */
.wrow .och + .ch::before,
.wrow .och + .xch::before{content:"·"; color:var(--dim); margin-right:5px}
/* The hours around the session, BEFORE the session's own move and quieter than
   it. The right column is flex-end, so the rightmost figure is the one the eye
   runs down - that is the close, under the price - and these trail to its
   left, which is also the order they happened in. On most evenings this is
   +0.00%, because most stocks do not trade after the bell, and a figure that
   is usually zero must not compete with the one that never is. The word says
   which hours: `o/n` and `after` are different venues, not the same one
   twice. */
.wrow .xch{font:400 10.5px var(--num); color:var(--dim)}
/* A price the feed is late with: its own print time, in the orange the chart's
 * "delayed" wears (`lateOf`, web/folders.js). */
.wrow .late{font:400 10.5px var(--num); color:var(--warn); margin-right:5px;
  direction:ltr; unicode-bidi:isolate}
.wrow .xch[hidden]{display:none}
.wrow .xch i{font-style:normal; color:var(--dim); opacity:.75; margin-right:3px}
/* The extended session's own price, between its word and its move: neutral
   ink, because the percent beside it already says which way (watchlist.js,
   `xchHtml`). */
.wrow .xch b{font-weight:400; color:var(--muted); margin-right:4px}
/* ...AND THE MAIN FIGURE WEARS ITS DAY THE SAME WAY, out of hours, when the
   session it belongs to is not today. Same idiom as the extended word beside
   it, so nothing new is learnt to read a row. */
.wrow .ch i{font-style:normal; color:var(--dim); opacity:.75; margin-right:3px; font-size:.92em}
/* ⚠ AND A FIGURE CELL IS LEFT-TO-RIGHT WHATEVER IS IN IT. A single Hebrew
   word next to the digits - a weekday from `toLocaleDateString` on a Hebrew
   phone - put the bidi algorithm in charge of the whole run and `+2.69%`
   came out as `2.69%+`. The words in here are English on purpose now, and
   this is the guard that makes that a belt rather than the only rope:
   `isolate` stops the cell inheriting a direction AND stops its own content
   escaping into the row around it. */
.wrow .ch,.wrow .xch{direction:ltr; unicode-bidi:isolate}
.wrow .ch i,.wrow .xch i{unicode-bidi:isolate}
.wrow .xch.up{color:var(--up); opacity:.75}
.wrow .xch.down{color:var(--down); opacity:.75}
/* ⚠ A PRICE THAT IS NOT FROM NOW IS DRAWN AS NOT FROM NOW, and it is the only
   thing on a row that says so. The rail repaints from the last reading it
   holds while the first poll of an opening is still out, so the number on
   screen for that second is the one from the last time you looked - and a
   reading from yesterday and a reading from this second were the same pixels
   until this rule existed (Conventions: never present a stale price as live).

   A DIM AND NOT A TIMESTAMP, which is deliberately the opposite of what a
   folder row does with `.wage`. A folder's figure is old for as long as you
   stand on the home screen and owes a clock; a row's is old for about a
   second, and writing a time into sixty rows and taking it out again is a
   flicker across the thing the eye is reading (rule 5). The clock is in the
   price's `title` for a pointer that wants it.

   The whole reading goes down together - price, percent, the hours figure,
   the dollars - because they are one answer taken at one moment, and a bright
   percentage beside a dim price would say the move was fresher than the price
   it was measured off. It comes back UP on a transition rather than snapping,
   so the refresh is followed rather than noticed. */
.wrow.aged .px,.wrow.aged .ch,.wrow.aged .och,
.wrow.aged .xch,.wrow.aged .pnl,.wrow.aged .val{opacity:.42}
/* the forming candle as the last stream left it, until the feed replaces it */
#tLast.aged .v,#tLast.aged small{opacity:.42}
.wrow .px,.wrow .ch,.wrow .och,.wrow .pnl,.wrow .val{
  transition:opacity 200ms var(--ease);
}
@media (prefers-reduced-motion:reduce){
  .wrow .px,.wrow .ch,.wrow .och,.wrow .pnl,.wrow .val{transition:none}
}
.wrow .x{
  position:absolute; right:4px; top:50%; transform:translateY(-50%);
  background:var(--raise); border:0; color:var(--dim); cursor:pointer;
  font-size:10px; padding:2px 4px; border-radius:var(--r-xs); opacity:0;
}
.wrow:hover .x{opacity:1}
.wrow .x:hover{color:var(--down); background:linear-gradient(var(--down-soft),var(--down-soft)),var(--raise)}

/* ---- THE STAR, AND THE FRAME IT LEAVES (Dor, 2026-09-28) ----
   A starred row sends every headline about the company to the phone, and says
   so with its FRAME in gold - the rail already speaks in frames (the trail
   lights them), so this is one more thing a frame can say rather than a new
   mark on every row. EXACTLY `--gold`, the gold of `.on`'s leading edge
   (Dor, 2026-09-28: *the same gold as the tab on the stock that is open*),
   with a short bloom hugging the edge so it reads as a lit frame and not as a
   hairline; the trail still lights it, because `.lit` adds the brighter ring.
   Not the leading edge itself, which is `.on`'s: a row both charted and
   starred carries both, so the edge is repeated in that one rule. The star
   arrives with the hover like the ✕ beside it; a finger gets it as a block
   under the swipe (below, in the phone block). */
.wrow.news:not(.lit){
  border-color:var(--gold);
  box-shadow:0 0 10px -6px var(--gold);
}
.wrow.news.on:not(.lit){box-shadow:inset 2px 0 0 var(--gold), 0 0 10px -6px var(--gold)}
.wrow>.star{
  position:absolute; right:22px; top:50%; transform:translateY(-50%);
  background:var(--raise); border:0; color:var(--dim); cursor:pointer;
  font-size:11px; line-height:1; padding:2px 4px; border-radius:var(--r-xs);
  opacity:0; transition:opacity 140ms var(--ease), color .12s;
}
.wrow>.star:last-child{right:4px}          /* a fixed list has no ✕ beside it */
.wrow:hover>.star{opacity:1}
.wrow>.star.on{color:var(--gold)}
.wrow>.star:hover{color:var(--gold-hi)}
#watch.rowdrag .star{opacity:0}

.wempty{padding:10px; color:var(--dim); font-size:11px; line-height:1.5}
.wempty .why{color:var(--warn); font-size:10px}

.wadd{padding:6px 8px 4px}
.wadd input{
  width:100%; height:26px; padding:0 8px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r);
  font:400 12.5px var(--sans); outline:none;
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
.wadd input:focus{border-color:var(--gold-line); box-shadow:var(--ring)}
.whits{margin-top:4px; padding:3px; background:var(--panel2); border:1px solid var(--line2);
  border-radius:var(--r); box-shadow:var(--shadow-pop)}
.whit{display:flex; flex-direction:column; padding:5px 7px; cursor:pointer; border-radius:var(--r-sm)}
.whit:hover{background:var(--hover)}
.whit .sym{font:500 11px var(--num); color:var(--text)}
.whit .sub{
  font:400 10px var(--sans); color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

.wnew{
  margin:8px; padding:6px; background:rgba(255,255,255,.025); border:1px solid transparent;
  border-radius:var(--r); color:var(--muted); cursor:pointer; font:400 11.5px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1);
}
.wnew:hover{color:var(--text); background:var(--hover)}

/* ---- the analyst panel ----
   The chart is the message and the chat is the margin note, so the panel is
   narrow and the tool calls are one line each.

   NARROW, BUT NOT 360px ANY MORE (Dor, 2026-10-03: *it may look a bit
   narrow*). Measured on a recorded-shape answer, longest line: 360 is 55
   characters in English and 51 in Hebrew - the low end of comfortable prose,
   and every answer here is prose. `clamp(360px, 30vw, 460px)`: under a
   1,200px window it is the 360 it always was, so a narrow window gives the
   chart nothing; at 1440 it is 432 (66 a line, 60 in Hebrew - the textbook
   optimum) and from 1,534 up 460 (72 and 64). Past ~480 English runs over 75
   a line, which is where the default stops; wider is the hand's choice.

   AND IT IS THE USER'S TO WIDEN, by dragging the hairline itself (`#chatGrip`,
   index.html, *the analyst's width*). What the hand chose rides `--chat-w`,
   written by the page and kept in localStorage - a screen preference, which
   syncs to nothing. Registered `inherits:false` (the `--sheet` lesson): the
   drag writes it once a frame, and an inheriting property would restyle the
   whole transcript under it on every write. */
@property --chat-w { syntax: "*"; inherits: false; }
/* `--gutter` is the caption column's width, published on #wrap by
   repositionOverlay() for whoever needs the number - and NO RULE READS IT, so
   it has no business restyling every node in the chart when it changes, which
   is every frame the pane changes width (an analyst-edge drag, a window
   resize). The column the CAPTIONS read is `--cap-tag`, and that one inherits. */
@property --gutter { syntax: "*"; inherits: false; }
#main{display:flex; flex:1; min-height:0}
#chat{
  position:relative; flex:0 0 var(--chat-w, clamp(360px, 30vw, 460px)); display:flex; flex-direction:column; min-width:0;
  background:var(--panel);
}
/* THE LINE IS THE HANDLE - one control, where the eye already puts the edge.
   9px of target centred on the 1px hairline, so 4 of it lie over the price
   scale's outer edge: a resize handle that sits only on one side of the line
   it moves is a handle you aim at rather than grab. A drag resizes, a
   double-click toggles between the default and the wide preset. The desktop's
   alone - the phone's sheet has its own handle, and the phone block takes
   this one away. */
#chatGrip{
  position:absolute; top:0; bottom:0; left:-4px; width:9px; z-index:7;
  cursor:col-resize; touch-action:none;
}
#chatGrip::after{
  content:""; position:absolute; top:0; bottom:0; left:4px; width:1px; pointer-events:none;
  background:var(--gold); opacity:0; transition:opacity var(--dur-1), width var(--dur-1), left var(--dur-1);
}
#chatGrip:hover::after, #chatGrip.on::after{opacity:.55; left:3px; width:3px}
body.chat-resizing, body.chat-resizing *{cursor:col-resize !important; user-select:none}
/* The panel's edge is the same hairline as the header's, stood on end. */
#chat::before{
  content:""; position:absolute; top:0; bottom:0; left:0; width:1px; pointer-events:none;
  background:linear-gradient(180deg,rgba(228,199,120,.5),rgba(228,199,120,.16) 55%,rgba(228,199,120,0));
  transition:opacity .9s var(--ease-out);
}
/* THE ANALYST'S PRESENCE, AND IT IS THE ONE LIGHT THIS PANEL CARRIES.
   While a turn is running, the seam between the chart and the conversation
   changes hands: the gold hairline (the page's own chrome) gives way to the
   analyst's ice, brightest at the head where its name is and spilling a
   breath of light into the panel, because the seam is exactly where its
   work crosses over onto the candles. Nothing moves in it and nothing loops:
   it comes up as the turn starts and goes down as it ends, and a reduced-
   motion reader gets the same light with no fade. Keyed off the dot
   `chat.js` already sets (`.cdot.work`, from the first `working` to `done`),
   so the page adds no state for it - the panel is read, not told. */
#chat::after{
  content:""; position:absolute; top:0; bottom:0; left:0; width:44px; pointer-events:none;
  background:
    linear-gradient(180deg,rgba(110,169,255,.95),rgba(110,169,255,.46) 36%,rgba(110,169,255,.16) 74%,rgba(110,169,255,0)) 0 0/1px 100% no-repeat,
    radial-gradient(48px 48% at 0 0,rgba(110,169,255,.16),rgba(110,169,255,0));
  opacity:0; transition:opacity .9s var(--ease-out);
}
#chat:has(#chatDot.work)::before{opacity:0; transition-duration:.35s}
#chat:has(#chatDot.work)::after{opacity:1; transition-duration:.35s}
#chat[hidden]{display:none}
/* THE HEAD STAYS ABOVE THE OFFER, AND THAT IS NOT A DETAIL. `#newchat` covers
   the panel at `z-index:5`, and this row is how the panel is PUT AWAY - the ✕
   on a desktop, and on a phone the grab handle that drags the sheet down. A
   layer that covers it makes "close the chat" impossible while a question
   about the chat is up, which is the one thing that must never be true of
   something that blocks. Everything under the row is blocked; the row itself
   goes on working, undimmed, and the offer goes down with the panel because it
   is INSIDE it. */
/* A NAME, A STATE AND TWO QUIET CONTROLS - in that order of voice. The name is
   the analyst's own ice at title weight; the dot and the word beside it are a
   small reading of the connection, not a heading; the cost is a figure to
   glance at; `clear` and ✕ are resting controls that say nothing until a
   pointer is on them. */
.chead{
  position:relative; z-index:6;
  display:flex; align-items:center; gap:8px; height:44px; padding:0 8px 0 16px;
  border-bottom:1px solid var(--line); font-size:12px;
}
.chead b{color:var(--agent); font-weight:600; font-size:13px; letter-spacing:.01em; margin-right:2px}
.chead .st{color:var(--muted); font-size:var(--fs-meta); flex:1}
/* What the turn cost. Quieter than the status word and never allowed to push
   the buttons off the row: it is a thing to glance at, not to read. */
.chead .cspend{flex:0 0 auto; color:var(--dim); font:500 var(--fs-cap) var(--num); font-variant-numeric:tabular-nums;
               white-space:nowrap; cursor:default; margin-right:4px}
.cdot{width:6px; height:6px; border-radius:50%; background:var(--dim); transition:background .2s, box-shadow .2s}
.cdot.ok{background:var(--up)}
/* The existing blink, slowed: the seam is the light now, and the dot only has
   to say the same thing in the corner of the eye. */
.cdot.work{background:var(--agent); animation:blink 1.8s ease-in-out infinite}
.cdot.bad{background:var(--down)}
.chead button{
  height:24px; padding:0 8px; background:none; border:0; border-radius:var(--r-sm);
  color:var(--muted); font:400 var(--fs-meta) var(--sans); cursor:pointer;
  transition:color var(--dur-1), background var(--dur-1);
}
.chead button:hover{color:var(--text); background:var(--hover)}
.chead button:active{background:var(--press)}
.chead #chatClose{width:28px; padding:0; font-size:13px; font-weight:400}
.clog{
  flex:1; overflow-y:auto; padding:16px 16px 20px; display:flex; flex-direction:column;
  gap:16px; font-size:14px; line-height:1.7;
}
/* THE USER'S WORDS ARE THE USER'S GOLD, SAID QUIETLY: a gold hairline round
   them and barely a breath of it underneath, and the text itself the page's
   own white. A tinted fill was tried and at any alpha that leaves the words
   readable it goes olive over this blue-black; the edge is where gold stays
   gold. The corner that points at the speaker is the tight one. */
.clog .u{
  align-self:flex-end; max-width:88%; color:var(--text); white-space:pre-wrap;
  background:rgba(228,199,120,.04); border:1px solid rgba(228,199,120,.3);
  padding:8px 13px; border-radius:var(--r-lg) var(--r-lg) var(--r-xs) var(--r-lg);
}
.clog .a{color:var(--text); white-space:pre-wrap; letter-spacing:.1px}
/* ONE DIRECTION PER MESSAGE, DECIDED BY ITS LANGUAGE AND NOT BY ITS FIRST
   LETTER. `plaintext` picked a direction per LINE from the first strong
   character, so a Hebrew answer whose line opened on `NVDA` or a number read
   left-to-right mid-answer. `dirOf()` in `agent/chat.js` counts the letters
   and writes `dir` on the bubble; `isolate` keeps it from leaking. A note is
   one short line and still lets the browser decide. */
.clog .u, .clog .a{unicode-bidi:isolate; text-align:start}
.clog .note{unicode-bidi:plaintext}
.clog .a code{
  background:var(--well); border:1px solid var(--line); border-radius:var(--r-xs);
  padding:0 5px; font:.86em var(--mono); font-variant-numeric:tabular-nums;
  unicode-bidi:isolate; direction:ltr;
}
/* A SOURCE THE ANSWER CITES (chat.js md()): the words are a link, and the site
   beside them says whose words they are. A headline is often Latin inside a
   Hebrew answer, so it is isolated like code; it wraps, because a title is
   longer than a phone is wide. */
.clog .a a{color:var(--agent); text-decoration:underline; text-underline-offset:2px;
  overflow-wrap:anywhere; unicode-bidi:isolate}
.clog .a .lsite{color:var(--dim); font-size:.86em; unicode-bidi:isolate; direction:ltr}
/* ---- WHAT THE ANALYST IS DOING, AS ONE NODE PER BURST ----
   It was `.clog .tool`: one monospace line per call, kept for ever, so a turn
   that read the chart, ran four finders and drew six marks left eleven lines
   in a 360px column and pushed the answer off the screen. The trace is worth
   watching WHILE it happens and worth almost nothing afterwards, so it is live
   and then collapses to `6 steps · 12.4s` with the ordered list one click away.
   The machinery, and why a run is a BURST rather than a turn: `agent/chat.js`.

   THE ICE IS FOR WHILE IT IS HAPPENING. Live, the node is the analyst at work
   and carries its colour - a faint ice ground, an ice hairline, the verb that
   is moving. Settled, it is a record: one quiet line on the panel itself with
   no box around it, the tick the only ice left, and a hairline frame only
   while somebody has it open to read. */
.clog .run{
  align-self:stretch; display:flex; flex-direction:column;
  border:1px solid transparent; border-radius:var(--r);
  background:transparent; overflow:hidden;
  transition:background var(--dur-3) var(--ease-out), border-color var(--dur-3) var(--ease-out);
}
.clog .run.live{background:var(--agent-dim-soft); border-color:rgba(110,169,255,.15)}
.clog .run.done.open{border-color:var(--line)}
.clog .run .rbar{
  display:flex; align-items:center; gap:8px; width:100%; min-width:0;
  padding:6px 10px; background:none; border:0; cursor:pointer;
  color:var(--agent); font:500 12px/1.4 var(--sans); text-align:start;
  transition:background var(--dur-1);
}
.clog .run .rbar:hover{background:var(--hover)}
.clog .run.live .rbar:hover{background:var(--agent-soft)}
.clog .run.done .rbar{color:var(--muted)}
/* The icon slot IS the animation: a spinner while it is thinking, the family
   of whatever call is running now, a tick once the burst is closed. */
.clog .run .ric{flex:0 0 auto; display:flex; width:12px; height:12px}
.clog .run.done .rbar .ric{color:var(--agent)}
.clog .run svg{
  width:12px; height:12px; fill:none; stroke:currentColor; stroke-width:1.3;
  stroke-linecap:round; stroke-linejoin:round;
}
.clog .run svg.sp{animation:spin .85s linear infinite}
.clog .run .rsay{flex:1; min-width:0; overflow:hidden; unicode-bidi:plaintext}
/* A NEW NODE PER LINE, which is what restarts the animation - the swap IS the
   motion, and nothing is measured or written per frame to get it (rule 5). */
.clog .run .rw{
  display:block; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;
  animation:stepIn .26s var(--ease) both;
}
.clog .run .rw.think{color:var(--muted); font-weight:400}
.clog .run .rw.sum{color:var(--muted)}
.clog .run .rtime{
  flex:0 0 auto; color:var(--dim); font:400 var(--fs-cap) var(--num);
  font-variant-numeric:tabular-nums;
}
.clog .run .rchev{flex:0 0 auto; display:flex; color:var(--dim); transition:transform var(--dur-2) var(--ease-out)}
.clog .run.live .rchev{opacity:.3}
.clog .run.open .rchev{transform:rotate(180deg)}
/* ONE SEGMENT PER STEP, SHARING THE WIDTH. A row of pips runs off the end of a
   360px column at about twelve calls; a rail that SUBDIVIDES says how many and
   which one is now at any count, and it is the signature the collapsed line
   keeps - in the analyst's ice while it runs, in slate once it is a record. */
.clog .run .rrail{display:flex; gap:2px; height:2px; padding:0 10px; margin-bottom:8px}
.clog .run .rrail i{
  flex:1 1 0; min-width:0; height:2px; border-radius:1px;
  background:var(--line2); transform-origin:left center;
  animation:segIn .3s var(--ease) both;
}
.clog .run.live .rrail i{background:var(--agent-dim)}
.clog .run .rrail i.cur{
  background:var(--agent);
  animation:segIn .3s var(--ease) both, segPulse 1.2s ease-in-out .3s infinite;
}
/* The record, one click away: the call's own name, what it was about, and how
   long it took - which is only knowable once the next one has landed. */
.clog .run .rlist{
  border-top:1px solid var(--line); padding:4px 0 6px;
  /* Its own scroller, capped against the log by `measure()`. `contain` so the
     end of a finger's scroll does not carry on into the transcript under it,
     which on a phone is how you lose your place in the conversation by reading
     a trace. */
  overflow-y:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch;
}
.clog .run.live .rlist{border-top-color:rgba(110,169,255,.13)}
.clog .run .rlist[hidden]{display:none}
/* ...and it fades while there is more under it - a bounded box with nothing
   saying so is the same fault one level down. Toggled by `more()`, only when
   the answer changes. The fade ends in whatever the node stands on: the panel
   for a record, the panel under its ice for a live one. */
.clog .run.more{position:relative}
.clog .run.more::after{
  content:""; position:absolute; left:1px; right:1px; bottom:0; height:22px;
  pointer-events:none; border-radius:0 0 7px 7px;
  background:linear-gradient(180deg,rgba(13,16,22,0),var(--panel) 85%);
}
.clog .run.live.more::after{
  background:linear-gradient(180deg,rgba(110,169,255,0),var(--agent-dim-soft) 85%),
             linear-gradient(180deg,rgba(13,16,22,0),var(--panel) 85%);
}
.clog .run .rstep{
  display:flex; align-items:center; gap:8px; padding:3px 10px;
  font:11px/1.5 var(--num); color:var(--dim);
}
.clog .run .rstep b{flex:0 0 auto; color:var(--agent); font-weight:500}
.clog .run.done .rstep b{color:var(--text); font-weight:500}
.clog .run .rstep .rdt{
  flex:1; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;
  color:var(--muted); unicode-bidi:plaintext;
}
.clog .run .rstep em{
  flex:0 0 auto; color:var(--dim); font-style:normal; font-variant-numeric:tabular-nums;
}
/* A THOUGHT IS PROSE, SO IT WRAPS AND FOLDS, and the call rows above it do
   neither: a call is a name and an argument, a thought is a paragraph. Two
   lines and an ellipsis at rest, the whole of it on a click - the gesture this
   chart already uses on a folded caption and on the board. Its own alignment,
   because a wrapped row hung on `center` puts the icon and the duration in the
   middle of the paragraph. Set in the prose face at the row's size and a
   step quieter than a call's detail: the reasoning is there to be looked into,
   not read past on the way to the answer. */
.clog .run .rstep.rthink{align-items:flex-start; padding-top:5px; padding-bottom:5px}
.clog .run .rstep.rthink .ric{margin-top:3px; color:var(--dim)}
.clog .run .rstep.rthink em{margin-top:1px}
.clog .run .rstep.rthink .rdt{
  white-space:normal; overflow:hidden; cursor:pointer;
  font:400 11.5px/1.55 var(--sans); color:var(--muted); letter-spacing:.1px;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  transition:color var(--dur-1);
}
.clog .run .rstep.rthink .rdt:hover{color:var(--text)}
.clog .run .rstep.rthink.open .rdt{display:block; -webkit-line-clamp:none; color:var(--text)}
@keyframes stepIn{from{opacity:0; transform:translateY(5px)}to{opacity:1; transform:none}}
@keyframes segIn{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes segPulse{50%{opacity:.35}}
/* A message ARRIVES rather than appearing. `#newchat` keeps its own `pop`. */
/* AND NOTHING IN A TRANSCRIPT IS EVER SHRUNK TO FIT IT. `.clog` is a column
   flex container, so every child defaults to `flex-shrink:1` - and the
   automatic minimum size that normally stops an item being squashed below its
   content DOES NOT APPLY to one whose `overflow` is not `visible`. The run node
   has `overflow:hidden` for its own corners, so it alone became squashable: 30
   steps measured 391px of node over 722px of content, with the log reporting
   `scrollHeight === clientHeight` - the rows were not merely hidden, there was
   nothing to scroll to reach them. Stated over every child, so the next thing
   added here with an `overflow` cannot inherit it. */
.clog>*{flex:none; animation:msgIn .2s var(--ease) both}
@keyframes msgIn{from{opacity:0; transform:translateY(4px)}to{opacity:1; transform:none}}
/* ...and the answer being written carries the cursor, so a stream that pauses
   is visibly a stream rather than a finished answer. */
.clog .a.writing::after{
  content:""; display:inline-block; width:2px; height:1.05em; margin-inline-start:3px;
  vertical-align:-2px; border-radius:1px; background:var(--agent);
  animation:blink 1.05s ease-in-out infinite;
}
.clog .note{color:var(--muted); font-size:var(--fs-meta); line-height:1.5}
/* THE ANALYST'S OBJECTS IN THE TRANSCRIPT - the report card, the level
   notice, and in lesson.css the moment and the play card - ARE ONE KIND OF
   THING AND WEAR ONE SURFACE: a faint ice ground and an ice hairline, a row's
   radius, and a control that is ice on ice rather than a box cut out of the
   page. The prose around them is the panel's own; these are what the analyst
   left behind to come back to. */
/* The report, as a card in the transcript: the analyst's blue, a button that
   brings the window back. Rebuilt from the transcript on every reload. */
.clog .rcard{
  align-self:stretch; display:flex; flex-direction:column; gap:8px;
  padding:12px 14px; border:1px solid rgba(110,169,255,.17); border-radius:var(--r);
  background:var(--agent-dim-soft);
}
.clog .rcard .rhead{display:flex; align-items:center; gap:8px; font:600 12.5px var(--sans); color:var(--agent)}
.clog .rcard .rhead i{width:8px; height:10px; border:1.5px solid currentColor; border-radius:1px; opacity:.8}
.clog .rcard .rhead .tk{font:500 11px var(--num); color:var(--agent-hi); padding:1px 6px; border:0; border-radius:var(--r-xs); background:rgba(110,169,255,.1)}
.clog .rcard .rhead .when{margin-left:auto; font:var(--fs-cap) var(--num); color:var(--dim)}

/* A TICKER IN THE ANSWER, WHICH OPENS ITS CHART (web/ticker-match.js). The
   app's own ticker shape - the report card's `.tk` above - so it reads as a
   symbol before anybody learns it is a control, and it is none of the other
   three offers in an answer: not the glossary's gold letters, not the board's
   blue underline, not the memory's dashed one. `isolate`, because a Latin run
   inside a Hebrew sentence must not pull the punctuation beside it across.
   `nowrap`, because a company named in two words ("Plug Power") is ONE chip,
   and a chip broken over two lines reads as two. A filled chip rather than an
   outlined one: a symbol is a token, and a box drawn round a word mid-sentence
   is louder than the ice it sits on. */
.clog .tk-ref{
  cursor:pointer; unicode-bidi:isolate; white-space:nowrap;
  font:500 .88em var(--num); color:var(--agent-hi);
  padding:1px 5px; border:0; border-radius:var(--r-xs);
  background:rgba(110,169,255,.11); box-shadow:inset 0 0 0 1px rgba(110,169,255,.16);
  transition:background var(--dur-1) ease, box-shadow var(--dur-1) ease, opacity var(--dur-1) ease;
}
.clog .tk-ref:hover,
.clog .tk-ref:focus-visible{background:rgba(110,169,255,.2); box-shadow:inset 0 0 0 1px rgba(110,169,255,.36); outline:none}
/* A PLACE ON THE CHART, IN THE ANSWER (web/place-match.js): a price one of the
   marks stands at, a price the stock traded at, a day. Tapped, the chart goes
   there and lights it. The ticker's chip one step quieter - it is a number
   inside the sentence rather than a name dropped into it - and it leads with
   a SIGN of what it goes to, drawn in that thing's own ink: a filled dot in
   the mark's colour (the silver target, the orange invalidation), a hollow
   ring for a price nobody drew, a small calendar leaf for a day. So the chip and the line
   it lights are visibly one object before anybody presses anything. */
.clog .pl-ref{
  --pl-ink:var(--agent);
  cursor:pointer; unicode-bidi:isolate; white-space:nowrap;
  font-weight:500; font-variant-numeric:tabular-nums; color:var(--agent-hi);
  padding:1px 6px 1px 5px; border-radius:var(--r-xs);
  background:rgba(110,169,255,.07); box-shadow:inset 0 0 0 1px rgba(110,169,255,.15);
  transition:background var(--dur-1) ease, box-shadow var(--dur-1) ease, opacity var(--dur-1) ease;
}
.clog .pl-mark,.clog .pl-price{font-family:var(--num); font-size:.92em}
.clog .pl-ref::before{
  content:''; display:inline-block; width:6px; height:6px; margin-inline-end:5px;
  border-radius:50%; background:var(--pl-ink); vertical-align:.12em;
  box-shadow:0 0 0 2px color-mix(in srgb, var(--pl-ink) 24%, transparent);
}
.clog .pl-price::before{width:7px; height:7px; background:none; box-shadow:inset 0 0 0 1.5px var(--pl-ink)}
.clog .pl-date::before{
  width:8px; height:8px; border-radius:2px; vertical-align:.02em;
  background:linear-gradient(var(--pl-ink) 0 3px, transparent 3px);
  box-shadow:inset 0 0 0 1.4px var(--pl-ink);
}
.clog .pl-ref:hover,
.clog .pl-ref:focus-visible{background:rgba(110,169,255,.16); box-shadow:inset 0 0 0 1px rgba(110,169,255,.38); outline:none}
.clog .pl-ref:active{background:rgba(110,169,255,.24)}
.clog .pl-ref.busy{opacity:.55; cursor:progress}
.clog .rcard .rsum{
  margin:0; font-size:12.5px; line-height:1.5; color:var(--muted); unicode-bidi:plaintext; text-align:start;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}
.clog .rcard .rsum.dim{color:var(--dim)}
.clog .rcard button{
  align-self:flex-start; height:28px; padding:0 12px; border-radius:var(--r-sm); cursor:pointer;
  border:0; background:rgba(110,169,255,.1); color:var(--agent-hi);
  box-shadow:inset 0 0 0 1px rgba(110,169,255,.28); font:500 12px var(--sans);
  transition:background var(--dur-1), box-shadow var(--dur-1);
}
.clog .rcard button:hover{background:rgba(110,169,255,.18); box-shadow:inset 0 0 0 1px rgba(110,169,255,.42)}
/* THE LEVEL MOVED, AND THE WAY BACK IS BESIDE IT (docs/פרופיל-הקורא.md §9).
   Not a `.note`, which is small grey text and is the panel's word for a FAILURE -
   this is the analyst saying what it is going to do, and it carries a control.
   The agent's blue for the same reason the report card takes it: it is the
   analyst's own hand. It stretches, so the undo cannot be scrolled past
   sideways on a phone, and wraps under the sentence when the row is narrow. */
.clog .rnotice{
  align-self:stretch; display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  padding:9px 8px; padding-inline-start:12px; border:1px solid rgba(110,169,255,.17); border-radius:var(--r);
  background:var(--agent-dim-soft);
}
.clog .rnotice .rntext{
  flex:1 1 180px; min-width:0; font:400 12px/1.5 var(--sans); color:var(--text);
  unicode-bidi:plaintext; text-align:start;
}
.clog .rnotice .rnundo{
  flex:none; height:28px; padding:0 12px; border-radius:var(--r-sm); cursor:pointer;
  border:0; background:rgba(110,169,255,.1); color:var(--agent-hi);
  box-shadow:inset 0 0 0 1px rgba(110,169,255,.28);
  font:500 12px var(--sans); white-space:nowrap;
  transition:background var(--dur-1), box-shadow var(--dur-1);
}
.clog .rnotice .rnundo:hover:not(:disabled){background:rgba(110,169,255,.18); box-shadow:inset 0 0 0 1px rgba(110,169,255,.42)}
.clog .rnotice .rnundo:disabled{cursor:default; opacity:.5}
/* THE WEEKLY ALLOWANCE, IN THE CONVERSATION (web/usage.js, Dor 2026-10-05).
   Quiet at 75 and 80 - the panel's own grey, framed so it is not mistaken for
   a failure `.note`. From 90 it is the warning orange, and `--u` (0 at 89, 1 at
   the wall) turns the orange UP each percent: the frame, the wash and the
   ink all strengthen, so 98 reads louder than 91 without a word changing.
   Never the user's gold or the analyst's blue - this is the app speaking. */
.clog .unotice{
  --u:0;
  align-self:stretch; padding:8px 12px; border-radius:var(--r);
  font:400 12px/1.5 var(--sans); color:var(--muted);
  border:1px solid var(--line); background:transparent;
  unicode-bidi:plaintext; text-align:start;
}
.clog .unotice.urgent{
  color:color-mix(in srgb, var(--warn) calc(55% + var(--u) * 45%), var(--text));
  border-color:color-mix(in srgb, var(--warn) calc(30% + var(--u) * 55%), transparent);
  background:color-mix(in srgb, var(--warn) calc(5% + var(--u) * 13%), transparent);
  font-weight:500;
}
.clog .unotice.over{color:var(--text); border-color:var(--warn); background:var(--warn-soft); font-weight:600}
.cbar{
  display:flex; gap:8px; align-items:flex-end; padding:12px 12px 8px;
  border-top:1px solid var(--line);
  /* The circled-region chip (lesson.css) is put in here beside the field; it
     takes a line of its own above it, which is where it was always drawn to
     sit - it was squeezing the field into a sliver beside it. */
  flex-wrap:wrap;
}
/* THE BOX THE USER SPEAKS FROM IS A WELL, AND THE GOLD IS WHERE THE HAND IS.
   At rest it is a step below the panel with a hairline round it - it said
   "gold" at rest for a while, and a field shouting before anybody has typed
   is a field the eye learns to skip. Gold arrives with focus, the brief's own
   field rule, and the send button beside it says the rest. */
.cbar textarea{
  flex:1; resize:none; max-height:140px; min-height:36px; unicode-bidi:plaintext;
  background:var(--well); color:var(--text); border:1px solid var(--line); border-radius:var(--r);
  padding:8px 12px; font:13px/1.45 var(--sans); outline:none;
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
.cbar textarea::placeholder{color:var(--dim)}
.cbar textarea:hover{border-color:var(--line2)}
.cbar textarea:focus{border-color:var(--gold-line); box-shadow:var(--ring)}
/* THE SEND IS THE ONE GOLD THING HERE, AND IT IS LIT WHEN THERE IS SOMETHING
   TO SEND. Flat gold with the lit top edge every raised thing wears - no
   sheen, no glow - and while the box is empty (its placeholder showing) it
   rests as a gold outline on a breath of gold, the brief's `.on` shape. A
   suggestion that fills the box lights it, which is what "the ↑ is one tap
   away and lit" asks of the starters. Pure selector state: nothing is set. */
.cbar button{
  height:36px; min-width:36px; padding:0 11px; background:var(--gold); color:#1a1408;
  border:0; border-radius:var(--r); font:600 14px var(--sans); cursor:pointer;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3);
  transition:background var(--dur-1), color var(--dur-1), box-shadow var(--dur-1);
}
.cbar button:hover{background:var(--gold-hi)}
.cbar button:active{box-shadow:inset 0 1px 2px rgba(0,0,0,.28)}
.cbar textarea:placeholder-shown ~ #chatSend{
  background:var(--gold-soft); color:var(--gold); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
}
.cbar textarea:placeholder-shown ~ #chatSend:hover{background:rgba(228,199,120,.2); color:var(--gold-hi)}
.cbar #chatStop{
  background:transparent; color:var(--muted); font:500 12px var(--sans);
  box-shadow:inset 0 0 0 1px var(--line2);
}
.cbar #chatStop:hover{color:var(--text); background:var(--hover)}
/* THE PEN, WHERE IT LIVES ON EVERY SCREEN: at the left of the field, the
   place a phone keeps "attach" (chrome.js, COMPOSER; the desktop followed
   2026-10-05). A quiet key the height of the
   field - not the gold of the send, which is the one gold thing here - and
   gold only while it is armed, the way every tool says it is on. */
.cbar .ind{flex:none; align-self:flex-end}
.cbar #penBtn{
  width:36px; height:36px; min-width:36px; padding:0; display:grid; place-items:center;
  background:var(--well); color:var(--muted); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:none;
}
.cbar #penBtn svg{width:17px; height:17px}
.cbar #penBtn:hover{background:var(--hover); color:var(--text)}
.cbar #penBtn.on{color:var(--gold); background:var(--gold-soft); border-color:var(--gold-line)}
/* THE STANDING LINE UNDER THE COMPOSER, SET AS A FOOTNOTE TO IT. Its own rule
   lives with the earnings rows (`.cdisc`, further down) and is left alone;
   this is how it sits inside the panel: the caption size, tight under the
   field it qualifies, a colour between the two greys that still clears 4.5:1
   on the panel - it has to be read, and it must not be read first. */
#chat .cdisc{
  padding:0 16px 10px; font-size:var(--fs-cap); line-height:1.4; letter-spacing:.01em;
  color:var(--dim); color:color-mix(in oklab, var(--muted) 45%, var(--dim));
}

footer{
  display:flex; align-items:center; gap:8px; height:28px; padding:0 14px; white-space:nowrap;
  color:var(--dim); font-size:11px; border-top:1px solid var(--line); background:var(--panel);
}
footer .sep{color:var(--dim); opacity:.5}
/* THE BAND SHOWS ONLY WHILE IT HAS SOMETHING TO SAY, ON A DESKTOP TOO
   (2026-09-28, Dor: the chart as big as it can be). While everything is well
   the row said the same things on every chart every day - the provider,
   "IBKR connected", the timezone, the ways to help - and each is said
   elsewhere: the header's own dots, the brand mark and `?` for help, the help
   window for a bug report. So it goes, and the chart takes its 28px; it is
   back the moment the connection is not well (`ibkr-bad`, written by the
   check itself), and while a lesson's pace control is docked in it. The bell
   and the credit left it first, so nothing that lives only here is hidden. */
body:not(.phone):not(.ibkr-bad) footer:not(:has(.sketch-pace.docked)){display:none}
/* THE LIBRARY'S CREDIT, in the "what is this" window since 2026-10-06
   (web/vendor/NOTICE) - a footnote under the about pane, never on the chart
   screen, where a stray tap left the app. Only the name is the link. */
.credit a{color:inherit; text-decoration:underline; text-underline-offset:2px}
footer #ibkr{display:inline-flex; align-items:center; gap:5px; min-width:0; overflow:hidden; text-overflow:ellipsis}
footer .dot{display:inline-block; width:7px; height:7px; animation:none; margin-right:2px; flex:none}
footer .dot::after{content:none}
/* THE FOOTER IS THE DEVICE'S OWN FACTS, SO IT SPEAKS QUIETLY (the look pass):
   its ways out are in the muted ink and brighten under the pointer, and gold is
   kept for the one that is an action worth reaching for - `connect`. A row of
   five gold links was the loudest line on the page and said the least. */
footer a{color:var(--muted); text-decoration:none; transition:color var(--dur-1)}
footer a:hover{color:var(--text); text-decoration:underline; text-underline-offset:2px}
.lnk{background:none; border:0; padding:0; color:var(--muted); font:inherit; cursor:pointer; transition:color var(--dur-1)}
.lnk:hover{color:var(--text); text-decoration:underline; text-underline-offset:2px}
.lnk.strong{color:var(--gold); font-weight:500}
.lnk:disabled{color:var(--dim); cursor:default; text-decoration:none}

/* ---- the bell ----
   Styled by its class and not by where it sits: the footer is its desktop home
   and the foot of the dock is its phone one (chrome.js PLACEMENT.dockfoot).
   The one control in this row that is a SETTING rather than a state, so it
   borrows the row's existing idiom instead of inventing one: a dot that is
   hollow when nothing is subscribed and filled when something is, exactly like
   the IBKR dot two items to its left. Gold, because enabling notifications is
   the USER's act and gold is what the user does (the two temperatures); orange
   only when something is wrong, because gold owns yellow.
   The padding/margin pair grows the tap target to ~32px without growing the
   row - a 22px footer on a phone is under the thumb's own size. */
.bell{display:inline-flex; align-items:center; gap:5px; flex:none;
  padding:8px 4px; margin:-8px 0}
.bell::before{
  content:''; width:7px; height:7px; border-radius:50%; flex:none;
  border:1px solid currentColor;
}
.bell.on{color:var(--gold-hi)}
.bell.on::before{background:currentColor}
.bell.warnish{color:var(--warn)}
.bell.sending{opacity:.45}
.bell:hover{text-decoration:none}
/* IN THE HEADER IT IS THE DOT ALONE, ON EVERY SCREEN (2026-09-28). The phone
   put it there first, beside the feed's own dot, and the desktop followed when
   the footer band stopped showing while everything is well. The words stay in
   the `title` for a pointer to hover; the dot IS the state - hollow when
   nothing is subscribed, filled when something is. The hint hangs from the
   same corner. */
header .bell{font-size:0; gap:0; padding:9px 6px; margin:-9px 0}
header .bell::before{width:8px; height:8px}

/* The iOS instruction, shown where it is needed. Its own node, owned by
   alerts.js and removed rather than hidden. Anchored to the footer because
   that is what it is explaining. */
#bellHint{
  position:fixed; z-index:70; left:auto; right:10px; max-width:340px;
  top:50px; bottom:auto; padding:12px 14px 13px;
  background:var(--panel2); border:1px solid var(--line2); border-radius:var(--r-lg);
  box-shadow:var(--shadow-pop); color:var(--text); font-size:12.5px;
  line-height:1.5;
}
#bellHint b{color:var(--gold)}
#bellHint p{margin:6px 0 8px; color:var(--muted)}
#bellHint ol{margin:0; padding-left:18px; color:var(--text)}
#bellHint li{margin:3px 0}
footer code{
  font:10.5px var(--mono); color:var(--muted); background:var(--well); border:1px solid transparent;
  border-radius:var(--r-xs); padding:1px 6px; user-select:all; cursor:text;
}

/* ---- fullscreen ----
   "The whole screen" has to mean the chart and not the window, so the header
   and the strip come out of the layout and float over the top edge, shown
   when the pointer goes looking for them. The chart is then the only thing
   with any height, which is the point. */
body.fs footer{display:none}
/* THE SCRIM GOES WHERE THE DRAWER GOES. `#watch` is hidden here by CSS, but on
   a phone "open" is an ATTRIBUTE - syncScrim() reads `!el.watch.hidden` - and
   display:none does not change that. So entering fullscreen with the list open
   took the list off the screen and left its 50%-black scrim over the whole
   pane: the chart dimmed by half, in the one mode whose entire purpose is
   seeing the chart, with nothing on screen to say why or any way to dismiss it.
   Found by measuring a fullscreen screenshot that looked washed out. */
body.fs #watch,body.fs #scrim{display:none}
/* THE EDGE TAB IS THE PHONE'S ALONE, and it is off by default rather than on:
   a desktop has the rail as a column beside the chart, so a handle for pulling
   one over the top of it would be a control for a gesture that does not exist
   there. It goes with the drawer in fullscreen for the reason above - the rail
   is display:none in that mode, so a door onto it would open nothing. */
#railTab{display:none}
body.fs #railTab{display:none}
body.fs #chrome{
  position:fixed; top:0; left:0; right:0; z-index:30;
  opacity:0; pointer-events:none; transition:opacity .15s;
  box-shadow:0 12px 34px rgba(0,0,0,.55);
}
body.fs.peek #chrome{opacity:1; pointer-events:auto}


/* ---- scale, without the wheel ----
   Placed against the price scale rather than the window edge, so it never
   lands on top of the axis labels however wide those get. */
/* ---- the rail: fit, copy the picture, fullscreen ----
 * THE SAME THREE, IN THE SAME PLACE, ON EVERY SCREEN. Their subject is the
 * VIEW - one makes all of it visible, one copies it, one gives it the whole
 * screen - so they live on the pane's own price scale rather than in the
 * chrome. The phone had them there first; a desktop that arranges them
 * differently is a second app to learn. Dor's call, 2026-09-13.
 *
 * In the MARKUP now, not moved by chrome.js: a control that lives in one place
 * on every screen needs no machinery to put it there.
 *
 * The top corner of the scale is dead space - the chart draws nothing there -
 * so the three cost the topmost price label and nothing else.
 *
 * NO BOX. A bordered, rounded, shadowed panel on the scale reads as a second
 * window over the chart; what makes it read that way is the EDGE, not the fill.
 * Stripped of those and set in --axis-text, the icons sit among the prices.
 * The fill stays: without it they land on whatever the chart draws up there and
 * neither can be read. --bg is the colour the pane's gradient starts at, so the
 * patch has no seam, and its bottom FADES because a hard edge has to land
 * somewhere and would sometimes slice a price label through its digits.
 *
 * The four that used to be here - taller, flatter, in, out - are hidden rather
 * than deleted: ZOOM still answers +, - and 0 from the keyboard, the wheel
 * zooms time, and dragging the price scale stretches it (axisDrag, index.html),
 * so nothing they did is gone.
 *
 * pointer-events:none on the patch, auto on the buttons - the scale is
 * something you DRAG, and a press starting on the gradient has to reach it. */
#zoom{
  position:absolute; z-index:3; top:0; right:0; left:auto;
  width:max(var(--pad-right,74px), var(--rail-min,0px)); height:48px;
  display:flex; flex-direction:row; gap:var(--rail-gap,2px);
  align-items:flex-start; justify-content:center; padding-top:3px;
  opacity:1; pointer-events:none;
  background:linear-gradient(to bottom, var(--bg) 0, var(--bg) 34px, transparent 48px);
  border:0; border-radius:0; box-shadow:none; backdrop-filter:none;
}

/* AND THE WAY OUT OF FULLSCREEN MOVED SINCE THAT WAS WRITTEN, WHICH BROKE IT.
   `#fsBtn` used to sit in the phone's dock, which is why that dock is kept in
   fullscreen. It lives in the rail on the price axis now - the same three on
   every screen - and fullscreen then put the rail in the one place it cannot
   be pressed. Two faults, and the second one is on EVERY screen:

   - `#chrome` comes out of the layout in fullscreen, so `#wrap` starts at y=0
     and `#zoom`, which is `top:0` inside it, goes with it - on a phone that is
     straight under the STATUS BAR AND THE NOTCH. Nothing else needed the inset,
     because in every other layout the header stands in that band and carries
     it; take the header out and there is nothing between the rail and the
     hardware. "The buttons of fit, copy screen and un-full-screen are outside
     the screen and I cannot un-full-screen."
   - and the floating `#chrome` is `position:fixed; top:0; z-index:30`, so while
     it peeks it sits exactly on top of the rail and takes its taps:
     `elementFromPoint` over the exit returned HEADER, and the click did not
     leave the mode. **The desktop is worse, not better**: there the peek is
     driven by `mousemove`, so moving the pointer TO the exit is what summons
     the thing that covers it.

   So: down by the safe-area inset (0 where there is no notch, so the desktop is
   unchanged by that half), and above the chrome that floats over it. The only
   thing it can now overlap is the header's right-hand end, which is `#src` and
   the live dot - readouts, not controls.

   THE RULE IS THE FOURTH DEVICE ROUND'S AND IT HAS NOW BEEN BROKEN TWICE: the
   control that leaves a mode must be visible and hittable FROM INSIDE THAT
   MODE. Placing it once is not enough - it has to survive the next thing that
   moves. tests/phone.test.mjs holds it against both faults. */
/* AND ON A FINGER IT CLEARS THE TOP EDGE BY MORE THAN THE INSET.
   `--sat` is 47-59px in portrait, which put the rail well clear of the glass
   without anyone deciding to - and it is ZERO in landscape, where the rail then
   starts 3px from the top of the screen. iOS defers touches in the screen-edge
   system-gesture regions while it decides whether a press is the start of a
   Notification Centre pull, which is a delay a web page cannot opt out of and
   exactly matches "it does not respond immediately", reported in LANDSCAPE and
   only there. `--rail-edge` is the floor under the inset: 0 on a mouse, so the
   desktop's fullscreen is unchanged. UNCONFIRMED ON GLASS - the deferral cannot
   be reproduced in a headless browser, and 12px costs nothing if it is wrong. */
body.fs #zoom{top:max(var(--sat,0px), var(--rail-edge,0px)); z-index:31}

/* ---- and FIT, at the FOOT of the scale it acts on ----
   Dor's call, 2026-09-13. The button stopped touching the time range in the
   round before this, so it is a control on the PRICE AXIS and nothing else,
   while snap and fullscreen are about the view as a whole - two kinds of thing
   at the two ends of the scale, rather than three in a row meaning three
   different things. It is also where the hand already is: the axis is what you
   DRAG to stretch the scale, and the drag usually ends low.

   ONE RULE, GLOBAL, LIKE #zoom'S - see the note above it. No base plus a
   per-screen override, no inline position to beat it, and the same on every
   screen so the two layouts teach each other.

   IT SITS IN THE CORNER WHERE THE TWO AXES MEET, WHICH IS THE MIRROR OF THE
   ONE #zoom USES. Sitting it ON the price scale, just above the date row, was
   the obvious first answer and it is wrong: the VOLUME series' last-value tag
   is on that scale too, its y is the volume itself, and the volume band is the
   bottom fifth of the pane - so the tag roams 140px of a 702px pane and there
   is no height there that it cannot reach. Measured: at 19.98M it landed
   exactly under the button, and the patch cut it in half. The corner below the
   scale and right of the dates is the one piece of that edge nothing is drawn
   in, the same argument the top corner is borrowed on.

   `--pad-bottom` is the date row, published by repositionOverlay() beside
   `--pad-right` and asked of the library rather than guessed: its height is
   the library's own layout and grows with the font and the labels, so a
   constant here is how a control ends up sitting on top of "2026". The four
   pixels above it are the only part that reaches into the pane, and they are
   what makes the target 32px rather than 28.

   The gradient runs the other way for the same reason #zoom's runs down - the
   patch has to fade INTO the axis, not end in a line across it. */
#scale{
  position:absolute; z-index:3; right:0; left:auto;
  bottom:0; height:calc(var(--pad-bottom,26px) + 4px);
  width:max(var(--pad-right,74px), var(--rail-min,0px));
  display:flex; flex-direction:row; gap:var(--rail-gap,2px);
  align-items:flex-end; justify-content:center; padding-bottom:1px;
  opacity:1; pointer-events:none;
  background:linear-gradient(to top, var(--bg) 0, var(--bg) 70%, transparent 100%);
  border:0; border-radius:0; box-shadow:none; backdrop-filter:none;
}
/* The corner is short, so the button fills it rather than keeping the rail's
   32px - `#zoom button` is one selector less specific than this pair. */
#scale button{height:100%}
/* It is the axis's own furniture, so it wears the axis's weight - the same
   statement #zoom makes, and the reason neither reads as a window floating
   over the chart. */
#scale button{pointer-events:auto}

/* ---- HOW LONG THIS CANDLE HAS LEFT ----
   The chip that hangs under the live price tag on the price scale. The axis
   is the chart's clock as much as its scale - the price now at the top of it,
   the dates along the bottom - and this is the piece of that clock the bars
   cannot show you: how much of the candle you are reading has already
   happened.

   IT IS THE AXIS'S OWN FURNITURE, so it wears the axis's weight - `--axis-text`
   and `--mono`, the same pair the library prints the prices in, one size down
   because the price is the number and this is the footnote to it. Not gold as
   the user's hand and not blue as the agent's: nobody drew it, it is the
   chart's own readout, which is the same argument the price scale, the OHLC
   legend and the watermark are exempt on (`withoutText()`, index.html).

   `--pad-right` is the scale's real width, published by repositionOverlay() -
   the same number `#zoom` and `#scale` are sized on - so the chip is centred
   under the tag whatever the digits do to the axis. The `y` is written as a
   transform from the page, because it follows a PRICE and the price scale
   moves under a drag (rule 5).

   A GROUND, because the scale goes on drawing its gridline prices behind it
   and two numbers printed through each other is neither. `--ovl-tag` is the
   weight used everywhere a small figure sits over the chart, and it is flat
   rather than a gradient for snapshot.js's sake, which is the note beside the
   token. A long one - `4d 01:13`, a weekly or monthly bar - is allowed to
   spill a few pixels off a narrow axis rather than being clipped: a countdown
   with its hours cut off is worse than one that overhangs. */
/* HOW FAR EACH LINE IS FROM THE PRICE - one tag per line, all on one x out
   past the far end of the lines themselves, so they read as a ladder. Each
   wears its own line's colour (the tag IS that line speaking) and nothing
   else: no ground, no border, for the countdown chip's reason - a box beside a
   line reads as a second object rather than as part of it. See
   paintDistances(). */
.dists{position:absolute; inset:0; z-index:3; pointer-events:none; overflow:hidden}
.dists b{
  position:absolute; top:0; left:0; will-change:transform;
  font:500 10px/14px var(--mono); font-variant-numeric:tabular-nums;
  white-space:nowrap; letter-spacing:.2px;
  /* IT STANDS ON THE PANE'S OWN GROUND. The tag is past the end of every
     sloping line since 2026-09-19, but a LEVEL is one of the library's own
     price lines, painted across the whole pane on the canvas - and a level is
     most of what a marked chart carries - so the number would be written
     through one. `--bg` is exactly the colour the pane's gradient starts at,
     the same patch the axis rail uses, so it has no seam. No border and no
     radius: a box beside a line is a second object. */
  background:var(--bg); padding:0 3px; border-radius:2px;
}
/* A MARK'S SYMBOL, INSIDE ITS OWN TAG (pinColumn() in index.html): `◎ +10.6%`
   reads as *the target, 10.6% away*. Ringed like the symbols on the chart, and
   for a reason beyond the family look: a level's `–` bare beside a number is a
   minus sign. */
.dists b i{
  display:inline-flex; align-items:center; justify-content:center;
  box-sizing:border-box; width:12px; height:12px; margin:0 3px 0 0;
  vertical-align:-2px; border:1px solid currentColor; border-radius:50%;
  font:600 8px/1 var(--sans); font-style:normal;
}
.dists b i[data-role="trigger"]{font-size:10px}

/* A mark past the top or the bottom of the scale, said at that edge, just
   inside the price axis (paintOffscale()). The distance tags' own face and
   ground, so the two read as one family: a number about a line. */
.offscale{position:absolute; inset:0; z-index:3; pointer-events:none; overflow:hidden}
.offscale b{
  position:absolute; right:calc(var(--pad-right, 74px) + 6px);
  font:500 10px/14px var(--mono); font-variant-numeric:tabular-nums;
  white-space:nowrap; letter-spacing:.2px; color:var(--dim);
  background:var(--bg); padding:0 3px; border-radius:2px;
}

#cdown{
  position:absolute; z-index:4; top:0; right:0; left:auto;
  /* AT LEAST the axis, and wider to the LEFT when the words are: a phone's
     axis is 48-54px and `6:29:59` at 11px is 50 of text, which ran off the
     glass (Dor, 2026-09-29). Anchored right, so it only ever grows inward. */
  width:auto; min-width:var(--pad-right,74px); box-sizing:border-box; padding:0 2px;
  height:15px; line-height:15px; text-align:center; white-space:nowrap;
  pointer-events:none;
  /* NO GROUND. It shipped for one round as a filled badge in the tag's own
     colour, which is the most literal reading of "part of the tag" and the
     wrong one: a second solid block under the price doubles the tag's height
     and reads as a second window (Dor, 2026-09-14 - *"much more gentle, not a
     big second window"*). What makes it belong to the price is the COLOUR and
     the alignment, not a box. So the digits are the tag's own fill colour,
     written by the page, on nothing at all - flush under it, the axis's width,
     one size down and one weight down from the price. */
  background:none; color:var(--down);
  font:600 11px var(--mono); font-variant-numeric:tabular-nums;
  letter-spacing:.02em; height:21px;
}
/* LOUDER, STILL NO BOX (Dor, 2026-09-29: *"more visible, a bit more
   prominent"*). The digits went up a size and a weight and lost the .85, and
   they stand on the pane's own `--bg` - the `.dists` tag's ground, which reads
   as nothing at all and only stops a gridline price being written through
   them. Under them, a 2px line in the tag's colour: how much of the candle
   has GONE, read the way a dial is read. In the last tenth the digits go to
   `--text` (white) - never a pulse, which would pull the eye in the middle of
   a reading. Both halves of the line are `currentColor` backgrounds, so
   snapshot.js paints them with nothing taught. */
#cdown .t{display:block; height:21px; line-height:16px; padding:0 4px;
  background:var(--bg); border-radius:2px}
#cdown.end .t{color:var(--text)}
#cdown i{position:absolute; left:6px; top:18px; height:2px; border-radius:1px;
  background:currentColor}
#cdown .trk{right:6px; opacity:.2}
#cdown .fill{width:calc((100% - 12px) * var(--f, 0))}
/* AND THE LEGEND IS THE SECOND THING IN THAT BAND, which the note above got
   wrong: it says "nothing else needed the inset", and the OHLC legend did.
   Same cause exactly - `#chrome` leaves the layout in fullscreen, `#wrap`
   starts at y=0, and with viewport-fit=cover that is under the status bar and
   the notch. The legend is `top:10px` and about 48px tall on a phone (two
   clamped lines), so a ~47px inset hid 10..47 and left the bottom strip
   showing: "he is still cut on my iPhone window at full screen. I can see only
   the bottom part of him." Reported from the phone, 2026-09-13, one round after
   the desktop's own legend defect and with a completely different cause.

   `--sat` is 0 on anything without a notch, so the desktop is untouched.

   THE RULE, now that it has been paid twice: **in fullscreen, ANYTHING
   anchored to the top of `#wrap` is anchored to the hardware.** In every other
   layout the header stands in that band and carries the inset; take it out and
   there is nothing between the furniture and the glass. The two CSS-positioned
   ones are here. `.o-board` is placed by JS as a fraction of the pane and is
   clamped against the pane rect, so a board parked at the very top of a
   fullscreen phone has the same exposure - untested, and the clamp is where it
   would be fixed. */
body.fs #legend{top:calc(10px + var(--sat,0px))}
#zoom button,#zoom .ind,#zoom .ind>button{pointer-events:auto}
#zoom>button:not(.gap){display:none}
#zoom .ind{display:flex; flex:1 1 0; min-width:0}
/* `#zoom button.gap` is one class more specific than `#zoom button`, so the
   margin that spaced `fit` away from the four above it in the old COLUMN
   survived into this ROW and sat it 4px below its neighbours. */
#zoom button.gap,#scale button.gap{margin:0; padding:0; border-top:0}
/* Three abreast, sharing whatever the scale's width actually is - fixed boxes
   came to 96px against a 74px scale and hung over the candles. The ICON stays
   15px, the weight of the axis type beside it rather than of a button, so
   shrinking the box does not shrink what you are aiming at. */
#zoom button,#zoom .ind>button,#scale button{
  flex:1 1 0; min-width:0; width:auto; height:32px; padding:0; margin:0;
  display:grid; place-items:center; cursor:pointer;
  background:none; border:0; border-radius:0; box-shadow:none;
  color:var(--axis-text); opacity:.72; font:15px/1 var(--mono);
}
#zoom .ind>button{width:100%}
/* The top rail only. `#scale` is exactly as tall as the date row plus the four
   pixels above it, so a button taller than 32px there does not gain a target -
   it grows UP, out of its own gradient and onto the newest candles. */
#zoom button,#zoom .ind>button{height:var(--rail-btn,32px)}
#zoom button:active,#zoom .ind>button:active,#scale button:active{opacity:1; background:none}
#zoom button.sm{font-size:11px}
/* The cluster is glyphs except for `fit`, which needed a drawing: no character
   means "put all the data back between the edges" without also meaning
   fullscreen. Sized and stroked like every other icon button on the page. */
#zoom button svg,#scale button svg{
  width:15px; height:15px; fill:none; stroke:currentColor;
  stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round;
}
/* A hover brightens the icon; it does not put a box round it. The rail has no
   chrome of its own and a hover panel would hand it some back. */
#zoom button:hover,#zoom .ind>button:hover,#scale button:hover{opacity:1; background:none}
/* A CONTROL WITH NOTHING TO DO SAYS SO AND STAYS WHERE IT IS. `#undoBtn` is the
   only button in either rail that can be spent - the stack empties - and hiding
   it then would move FIT out from under a thumb that had learned where it was.
   So it dims to the weight of the axis's faintest text and stops answering.
   After the hover rule, which would otherwise brighten it back. */
#scale button:disabled,#scale button:disabled:hover{opacity:.22; cursor:default}
/* FIT LIT IS A HELD SCALE. A hand on the price frees the scale from the candles
   (`freeRange` in index.html) and FIT is the way back, so while it is held the
   button says so in the user's own gold - a mode you can fall into needs its
   way out visible. */
#scale button.free,#scale button.free:hover{color:var(--gold); opacity:1}

/* ---- indicators ---- */
.panel{
  position:absolute; top:calc(100% + 6px); left:0; z-index:20; width:304px;
  max-height:min(72vh,660px); overflow-y:auto; padding:6px;
  background:var(--panel2); border:1px solid var(--line2); border-radius:var(--r-lg);
  box-shadow:var(--shadow-pop); animation:pop var(--dur-2) var(--ease-out);
}
/* THE `+` INSIDE THE SEARCH BOX, AT ITS RIGHT END (Dor, 2026-10-06): the box
   is where the charted symbol's name is, so the verb that puts THAT symbol on
   a list sits in it - a gold `+` and nothing else. A framed `+ List` after the
   box came first and did not look right; a bare glyph after it was not found.
   The box keeps the room for it on a desktop; the phone hides it (the tray
   carries it) and keeps its own padding. Its panel opens leftwards, under the
   box, since the `+` is at the box's right end. */
/* ⚠ No transform and no z-index on the wrapper: either makes it a stacking
   context at its own low level, and the panel (z 20) then opened UNDER the
   chart. Placed by `top` - the box is 30px and the button 24. */
.finder>.ind.addl{position:absolute; right:3px; top:3px}
.ind.addl>button{width:24px; height:24px; border-radius:7px; color:var(--gold)}
.ind.addl>button svg{width:15px; height:15px; stroke-width:2.1}
body:not(.phone) #q{padding-right:32px}
.alpanel{left:auto; right:0}
/* ---- THE `+` BESIDE THE CHARTED NAME (web/addlist.js, Dor 2026-10-06) ----
   Every list, grouped by folder, a ✓ where the symbol already is: a tap adds
   or takes it off, and the popover stays open so several lists are one visit.
   The ✓ is the user's gold - it is their list. The same body is painted into
   the phone's tray (`.swpanel`), sized for a finger in the phone block. */
.alpanel{width:280px; padding:4px 4px 6px}
.alhead{display:flex; align-items:center; gap:6px; padding:6px 8px 8px; margin-bottom:2px;
        border-bottom:1px solid var(--line)}
.alhead b{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
          font:600 12.5px/1.25 var(--sans); color:var(--text)}
.alhead b span{color:var(--gold)}
.alback{flex:none; width:28px; height:28px; padding:0; border:0; border-radius:var(--r-sm);
        background:none; color:var(--muted); cursor:pointer; font:400 20px/1 var(--sans)}
.alsec{padding:10px 8px 4px; font:600 10px/1 var(--sans); letter-spacing:.08em;
       text-transform:uppercase; color:var(--dim)}
.alrow{
  display:flex; align-items:center; gap:9px; width:100%; min-height:30px; padding:5px 8px;
  border:0; border-radius:var(--r); background:none; color:var(--text); cursor:pointer;
  font:500 12.5px/1.2 var(--sans); text-align:left; transition:background-color var(--dur-1);
}
.alrow:hover{background:var(--hover)}
.alrow:active{background:var(--press)}
.alck{flex:none; display:grid; place-items:center; width:16px; height:16px; border-radius:4px;
      box-shadow:inset 0 0 0 1px var(--line2); font:700 11px/1 var(--sans); font-style:normal;
      color:var(--gold); transition:background-color var(--dur-1), box-shadow var(--dur-1)}
.alrow.on .alck{background:var(--gold-soft); box-shadow:inset 0 0 0 1px var(--gold-line)}
.alname{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.alcount{flex:none; font:500 11px/1 var(--num); font-variant-numeric:tabular-nums; color:var(--dim)}
.alnewb{margin-top:6px; color:var(--muted)}
.alnewb .alck{box-shadow:none; font-size:15px; color:var(--muted)}
.alnew{display:flex; gap:6px; margin-top:6px; padding:4px 4px 0}
.alnew input{flex:1; min-width:0; height:30px; padding:0 9px; border-radius:var(--r);
             background:var(--well); border:1px solid var(--line2); color:var(--text);
             font:500 12.5px var(--sans); outline:none}
.alnew input:focus{border-color:var(--gold-line)}
.alok{flex:none; height:30px; padding:0 12px; border:0; border-radius:var(--r); cursor:pointer;
      background:var(--gold); color:#14110a; font:600 12px var(--sans)}
.alempty{padding:10px 8px; font-size:12px; color:var(--muted)}
/* A section heading, with room on the right for what the section is: a count
   of marks, or what pressing something in it will do. */
.panel .grp{
  display:flex; align-items:baseline; gap:8px;
  padding:12px 10px 5px; font:600 11.5px/1 var(--sans); letter-spacing:.01em; color:var(--muted);
}
.panel .grp:first-child,.panel .sep + .grp{padding-top:6px}
/* The view row is not a layer, so it is ruled off from the ones that are. */
.panel .sep{height:1px; margin:5px 4px 0; background:var(--line)}
.panel label.view .slot{font-family:var(--num); letter-spacing:.02em}
.panel label{
  display:flex; align-items:center; gap:10px; padding:6px 10px;
  border-radius:var(--r); cursor:pointer; font-size:12.5px; color:var(--text);
}
.panel label{transition:background-color var(--dur-1)}
.panel label:hover{background:var(--hover)}
/* THE SWITCH IS A WELL WITH A KNOB IN IT, AND ON IS A GOLD KNOB - not a gold
   slab. A panel of eight layers switched on was eight filled gold pills; the
   jewellery is the knob, and the track only warms under it. */
.panel label input[type=checkbox]{
  appearance:none; -webkit-appearance:none; flex:none; position:relative; cursor:pointer;
  width:28px; height:16px; margin:0; border-radius:999px;
  background:var(--well); border:1px solid var(--line2);
  transition:background-color var(--dur-2), border-color var(--dur-2);
}
.panel label input[type=checkbox]::after{
  content:""; position:absolute; top:2px; left:2px; width:10px; height:10px; border-radius:50%;
  background:var(--muted); transition:transform var(--dur-2) var(--ease-out), background-color var(--dur-2);
}
.panel label input[type=checkbox]:checked{background:var(--gold-soft); border-color:var(--gold-line)}
.panel label input[type=checkbox]:checked::after{transform:translateX(12px); background:var(--gold); box-shadow:0 0 6px rgba(228,199,120,.35)}
/* What a row costs in screen space, or the key that opens it: a quiet chip. */
.panel .slot{margin-left:auto; font:500 10.5px var(--sans); color:var(--dim); background:var(--hover); border-radius:var(--r-xs); padding:3px 6px}
.panel .ctl{
  display:flex; align-items:center; flex-wrap:wrap; gap:4px 6px; padding:3px 10px 3px 48px;
  font-size:11px; color:var(--muted);
}
.panel .ctl .lbl{min-width:72px}
.panel .ctl button{
  height:22px; padding:0 8px; background:var(--hover); color:var(--muted);
  border:1px solid transparent; border-radius:var(--r-sm); font:500 11px var(--sans); cursor:pointer;
  transition:color var(--dur-1), background-color var(--dur-1);
}
.panel .ctl button:hover{color:var(--text); background:var(--press)}
.panel .ctl button.armed{color:var(--warn); background:var(--warn-soft); box-shadow:inset 0 0 0 1px rgba(240,160,60,.3)}
.panel .ctl input[type=number]{
  height:22px; width:66px; padding:0 6px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r-sm); font:11.5px var(--num);
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
.panel .ctl input[type=number]:hover{border-color:var(--line2)}
.panel .ctl input[type=number]:focus{border-color:var(--gold-line); box-shadow:var(--ring)}
.panel .ctl input[type=checkbox]{accent-color:var(--accent); margin:0}
/* A picked range is a long string; it takes its own line under the buttons
   rather than folding into a six-line column beside them. */
.panel .ctl .cur{color:var(--dim); margin-left:auto; font:10.5px var(--num); white-space:nowrap; min-width:0; max-width:100%; overflow:hidden; text-overflow:ellipsis}
.panel .ctl input[type=text]{
  flex:1; min-width:0; height:24px; padding:0 8px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r-sm); font:11.5px var(--sans);
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
.panel .ctl input[type=text]:focus{border-color:var(--gold-line); box-shadow:var(--ring); outline:none}
.panel .ctl input[type=text]::placeholder{color:var(--dim)}
.panel .chips{display:flex; flex-wrap:wrap; gap:4px; flex:1; min-width:0}
.panel .chip{
  display:inline-flex; align-items:center; gap:5px; height:22px; padding:0 4px 0 8px;
  border-radius:var(--r-sm); border:1px solid transparent; color:var(--text);
  background:color-mix(in srgb, var(--c) 14%, transparent); font:500 11px var(--sans);
}
.panel .chip i{width:7px; height:7px; border-radius:50%; background:var(--c)}
/* A symbol that did not resolve keeps the one edge that says so. */
.panel .chip.bad{border-color:var(--line2); border-style:dashed; color:var(--muted)}
.panel .chip.bad i{background:var(--down)}
.panel .chip button{
  height:16px; width:16px; padding:0; border:0; border-radius:var(--r-xs); background:transparent;
  color:var(--muted); font:12px/1 var(--sans); cursor:pointer;
}
.panel .chip button:hover{color:var(--text); background:var(--press)}
/* A chip that IS a button: the expiries the strike map is drawing. It sits in
   a .ctl, so `.panel .ctl button` has already given it a 22px box and a 5px
   radius - this only has to make it a pill and give it an on state. The `on`
   colour is the ACCENT because picking which expiry to look at is the user's
   own choice, and `mon` is a weight rather than a hue: open interest
   concentrates in the monthlies, which is a fact about the list rather than a
   state of the control. */
/* These chips carry the class `pick` too, and the drawing-pick overlay's rule
   used to be a bare `.pick{position:absolute; inset:0; pointer-events:none}` -
   so they lay stacked at the top of the panel, out of their row and taking no
   press: the EMA's `show` row was empty and a gold line ran under the sheet's
   grab bar (Dor, 2026-10-06, circled on the phone). The overlay's rules are
   `div.pick` now, and these stay in their row. */
.panel .ctl .chips button.pick{
  height:22px; padding:0 8px; border-radius:var(--r-sm); color:var(--dim);
  border:1px solid transparent; background:var(--raise); font:500 11px var(--num);
}
.panel .ctl .chips button.pick.mon{color:var(--muted); font-weight:600}
.panel .ctl .chips button.pick.on{
  color:var(--gold); background:linear-gradient(var(--gold-soft),var(--gold-soft)),var(--raise);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
}
.panel .ctl .chips button.pick:hover{color:var(--text); background:linear-gradient(var(--hover),var(--hover)),var(--raise)}

/* ---- the fundamentals report, a window over the chart ----
 * Rendered by report.js from the derived payload. The document carries its own
 * token set because it has two grounds: the screen, where it is the site's
 * gold on black, and the paper it prints to, which the Paper button previews
 * and Save PDF produces through the print rules at the bottom. Everything in
 * the document reads a --doc-* token; nothing in it reads a page token
 * directly, or the paper version would keep one dark-ground colour. */
/* THE NEWEST WINDOW LEADS THIS LIST RATHER THAN TRAILING IT, and that is
   deliberate: tests/phone.test.mjs reads the phone rule below by its exact
   selector text, so a window joins at the front and that rule is left alone.
   The order of a selector list changes nothing about the cascade. `#newchat`
   takes the TOKENS from here and nothing else - it is scoped to the analyst
   panel rather than laid over the page - so it is in the list below this one
   and not in this one. */
#optv, #help, #report, #cal, #alerts{
  --doc-bg:#0a0d12; --doc-bg2:#0f1218; --doc-text:#e8e4d8; --doc-muted:#9a9789; --doc-dim:#6a685e;
  --doc-line:rgba(228,199,120,.12); --doc-line2:rgba(228,199,120,.28);
  --doc-accent:#e4c778; --doc-accent-hi:#f8e7b0; --doc-accent-soft:rgba(228,199,120,.1);
  --doc-accent-dim:rgba(228,199,120,.62);
  --doc-rule:linear-gradient(90deg,rgba(228,199,120,.5),rgba(228,199,120,0));
  --doc-agent:var(--agent); --doc-agent-soft:var(--agent-soft);
  --doc-warn:var(--warn); --doc-up:var(--up); --doc-down:var(--down);
  --doc-track:rgba(228,199,120,.14); --doc-edge:rgba(228,199,120,.26);
  position:absolute; inset:0; z-index:6;
}
#report.paper{
  --doc-bg:#f6f2e6; --doc-bg2:#ede7d6; --doc-text:#15171c; --doc-muted:#5a5850; --doc-dim:#8c887c;
  --doc-line:rgba(138,106,28,.2); --doc-line2:rgba(138,106,28,.38);
  --doc-accent:#8a6a1c; --doc-accent-hi:#6d5210; --doc-accent-soft:rgba(138,106,28,.09);
  --doc-accent-dim:rgba(138,106,28,.75);
  --doc-rule:linear-gradient(90deg,rgba(138,106,28,.55),rgba(138,106,28,0));
  --doc-agent:#2b5fb3; --doc-agent-soft:rgba(43,95,179,.1);
  --doc-warn:#b0620a; --doc-up:#1c8656; --doc-down:#c2394e;
  --doc-track:rgba(138,106,28,.18); --doc-edge:rgba(138,106,28,.35);
}
#optv .scrim, #report .scrim, #cal .scrim, #alerts .scrim, #help .scrim{position:absolute; inset:0; background:rgba(8,10,14,.62); backdrop-filter:blur(1.5px)}
#optv .win, #report .win, #cal .win, #alerts .win, #help .win{
  position:absolute; left:50%; top:22px; transform:translateX(-50%);
  width:min(940px, calc(100% - 44px)); max-height:calc(100% - 44px);
  display:flex; flex-direction:column; background:var(--doc-bg); color:var(--doc-text);
  border:1px solid var(--line2); border-radius:var(--r-xl); overflow:hidden;
  box-shadow:var(--shadow);
  font:12.5px/1.45 var(--sans);
}
#optv .wbar, #report .wbar, #cal .wbar, #alerts .wbar, #help .wbar{position:relative; display:flex; align-items:center; gap:10px; height:46px; padding:0 12px 0 16px; background:var(--doc-bg2); flex:none}
#optv .wbar::after, #report .wbar::after, #cal .wbar::after, #alerts .wbar::after, #help .wbar::after{content:""; position:absolute; left:0; right:0; bottom:0; height:1px; pointer-events:none; opacity:.55;
  background:linear-gradient(90deg,rgba(228,199,120,0),var(--doc-accent) 12%,var(--doc-accent) 88%,rgba(228,199,120,0))}
#optv .wbar h2, #report .wbar h2, #cal .wbar h2, #alerts .wbar h2, #help .wbar h2{margin:0; font:600 15px var(--sans); letter-spacing:-.005em; color:var(--doc-text); white-space:nowrap}
#report .wbar .tk{font:500 12px var(--num); color:var(--doc-accent); padding:2px 7px; border:1px solid transparent; background:var(--doc-accent-soft); border-radius:var(--r-xs)}
#report .wbar .who{display:flex; align-items:center; gap:6px; font:11px var(--num); color:var(--doc-agent); background:var(--doc-agent-soft); border-radius:999px; padding:2px 9px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; flex:0 1 auto}
#report .wbar .who::before{content:""; width:6px; height:6px; border-radius:50%; background:var(--doc-agent)}
/* The window is a container: when it is narrow - the rail and the chat both
   open - the pill keeps who and drops when, rather than clipping mid-word. */
#optv .win, #report .win, #cal .win, #alerts .win, #help .win{container-type:inline-size}
@container (max-width:720px){
  #report .wbar .who .when{display:none}
  #report .wbar .who{flex:none}
  #report .wbar h2{font-size:12px}
  #report .wbar .paper .lbl, #report .wbar .copy .lbl{display:none}
  #report .wbar .paper, #report .wbar .copy{padding:0 9px}
}
#optv .wbar .sp, #report .wbar .sp, #cal .wbar .sp, #alerts .wbar .sp, #help .wbar .sp{flex:1}
/* A TITLE BAR'S BUTTONS ARE RESTING CONTROLS (the look pass): no edge, the
   muted ink, a fill under the pointer, and `.on` in the page's one
   on-treatment. The one primary action of a window - Save PDF - is the only
   solid gold, and it no longer glows. */
#optv .wbar button, #report .wbar button, #cal .wbar button, #alerts .wbar button, #help .wbar button{height:30px; padding:0 11px; border-radius:var(--r-sm); border:1px solid transparent; background:transparent; color:var(--doc-muted); font:500 12.5px var(--sans); display:inline-flex; align-items:center; gap:6px; cursor:pointer; white-space:nowrap; flex:none; transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1)}
#optv .wbar button:hover, #report .wbar button:hover, #cal .wbar button:hover, #alerts .wbar button:hover, #help .wbar button:hover{color:var(--doc-text); background:var(--hover)}
#optv .wbar button:active, #report .wbar button:active, #cal .wbar button:active, #alerts .wbar button:active, #help .wbar button:active{background:var(--press)}
#optv .wbar button.on, #report .wbar button.on, #cal .wbar button.on, #alerts .wbar button.on, #help .wbar button.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#report.paper .wbar button:hover{background:rgba(21,23,28,.06)}
#report .wbar button.gold{background:var(--gold-sheen); color:#1a1408; border:0; font-weight:600; box-shadow:inset 0 1px 0 rgba(255,255,255,.35)}
#report .wbar button.gold:hover{filter:brightness(1.06)}
#optv .wbar button.x, #report .wbar button.x, #cal .wbar button.x, #alerts .wbar button.x, #help .wbar button.x{width:30px; padding:0; justify-content:center; font-size:15px}
#optv .wbar svg, #report .wbar svg, #cal .wbar svg, #alerts .wbar svg, #help .wbar svg{width:13px; height:13px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round}
#report .wbody{overflow:auto; padding:22px 28px 26px}
#report .rdoc{display:flex; flex-direction:column; gap:22px}
#report .num, #report td.n, #report .kv b, #report .ret b, #report .meter span, #report .qbars{font-family:var(--num); font-variant-numeric:tabular-nums}
#report .ident{display:flex; flex-direction:column; gap:10px}
#report .title{display:flex; align-items:baseline; gap:12px; flex-wrap:wrap}
#report .title h1{margin:0; font:600 21px/1.1 var(--sans); letter-spacing:-.01em; text-wrap:balance}
#report .title .tk{font:500 14px var(--num); color:var(--doc-accent)}
#report .title .ex{font:11px var(--sans); color:var(--doc-accent-dim); letter-spacing:.08em; text-transform:uppercase}
#report .title .ind{color:var(--doc-muted); font-size:12px; flex-basis:100%}
#report .desc{margin:0; color:var(--doc-muted); max-width:70ch}
#report .kvs{display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:8px 24px; padding:12px 0; border-top:1px solid var(--doc-line); border-bottom:1px solid var(--doc-line)}
#report .kv{display:flex; flex-direction:column; gap:1px; min-width:0}
#report .kv span{font-size:10.5px; letter-spacing:.08em; text-transform:uppercase; color:var(--doc-accent-dim)}
#report .kv b{font-weight:500; font-size:13.5px; color:var(--doc-text); white-space:nowrap}
#report .kv.hi b{color:var(--doc-accent-hi)}
#report .kv b small{display:block; font:11px/1.35 var(--sans); color:var(--doc-muted); margin:1px 0 0; white-space:normal}
#report .kv b small .est, #report .kv b .est{color:var(--doc-warn); font-style:normal}
#report .ret{display:flex; gap:8px; flex-wrap:wrap; color:var(--doc-muted); font-size:12px}
#report .ret b{font-weight:500}
#report .ret i{font-style:normal; color:var(--doc-dim)}
#report .up{color:var(--doc-up)} #report .dn{color:var(--doc-down)}
#report .blk{display:flex; flex-direction:column; gap:8px; min-width:0}
#report h3{display:flex; align-items:center; gap:10px; margin:0; font:500 10.5px var(--sans); letter-spacing:.12em; text-transform:uppercase; color:var(--doc-accent)}
#report h3::after{content:""; flex:1; height:1px; background:var(--doc-rule)}
#report h3 .asof{font:10.5px var(--num); letter-spacing:0; text-transform:none; color:var(--doc-dim); white-space:nowrap}
#report .foot{color:var(--doc-dim); font-size:11px}
#report .tw{overflow-x:auto}
#report table{border-collapse:collapse; width:100%}
#report th{font:500 10.5px var(--sans); letter-spacing:.08em; text-transform:uppercase; color:var(--doc-accent-dim); text-align:right; padding:0 10px 6px; border-bottom:1px solid var(--doc-line2)}
#report th:first-child, #report th.l{text-align:left} #report th:first-child{padding-left:0}
#report td{padding:5px 10px; border-top:1px solid var(--doc-line); vertical-align:middle; white-space:nowrap}
#report tbody tr:first-child td{border-top:0}
#report td:first-child{padding-left:0; color:var(--doc-text)}
#report td.n{text-align:right}
#report td.b{color:var(--doc-muted); font-size:11px; white-space:normal}
#report .none{color:var(--doc-dim)}
#report .two{display:grid; grid-template-columns:1fr 1fr; gap:22px 36px}
#report .lines{display:flex; flex-direction:column; gap:6px}
#report .lines div{display:flex; gap:8px; flex-wrap:wrap; align-items:baseline}
#report .lines div::before{content:""; width:4px; height:4px; border-radius:50%; background:var(--doc-line2); margin-right:2px; position:relative; top:-1px; flex:none}
#report .lines .num{color:var(--doc-text)}
#report .lines .lab{color:var(--doc-muted)}
#report .sum{display:flex; flex-direction:column; gap:10px}
#report .sum p{margin:0; padding:3px 0; padding-inline-start:16px; border-inline-start:2px solid var(--doc-accent);
  font-size:14px; line-height:1.75; color:var(--doc-text); unicode-bidi:plaintext; text-align:start}
#report .meter{display:inline-flex; align-items:center; gap:8px; vertical-align:middle}
#report .meter .track{position:relative; width:110px; height:6px; border-radius:3px; background:var(--doc-track)}
#report .meter .track i{position:absolute; top:-1px; bottom:-1px; width:1px; background:var(--doc-line2)}
#report .meter .track b{position:absolute; top:-3px; width:12px; height:12px; margin-left:-6px; border-radius:50%; background:var(--gold-sheen); box-shadow:0 0 0 2px var(--doc-bg), 0 0 10px rgba(228,199,120,.35)}
#report.paper .meter .track b{background:var(--doc-accent); box-shadow:0 0 0 2px var(--doc-bg)}
#report .meter span{font-weight:500; font-size:11.5px; color:var(--doc-text)}
#report .qbars{display:flex; gap:22px; align-items:flex-end; padding:6px 0 0 2px}
#report .qbars .q{display:flex; flex-direction:column; align-items:center; gap:4px}
#report .qbars .q b{font-weight:500; font-size:11.5px; color:var(--doc-text)}
#report .qbars .q i{display:block; width:26px; background:linear-gradient(180deg,var(--doc-accent-hi),var(--doc-accent)); border-radius:3px 3px 0 0; opacity:.9}
#report .qbars .q span{font-size:10.5px; color:var(--doc-dim)}
#report .qbars .base{align-self:stretch; width:1px; background:var(--doc-line2)}
#report .events tr.earn td:first-child{color:var(--doc-accent)}
#report .events td.b .when{color:var(--doc-dim)}
#report .flags .lines div::before{background:var(--doc-warn); width:7px; height:7px; top:0}
#report .flags .clear{color:var(--doc-dim); font-size:11.5px; padding-left:17px}
#report .prov{color:var(--doc-dim); font-size:11px; border-top:1px solid var(--doc-line2); padding-top:10px; display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap}
@media (max-width:900px){ #report .two{grid-template-columns:1fr} #report .kvs{grid-template-columns:1fr 1fr} }
@media (max-width:600px){ #report .win{width:calc(100% - 16px); top:8px; max-height:calc(100% - 16px)} #report .wbody{padding:16px} #report .kvs{grid-template-columns:1fr} }

}

/* A drawing can be picked up. Nothing else on the pane says so, so the cursor
   does - the same job the `measuring` crosshair does for the ruler. */
#wrap.overMark{cursor:pointer}
/* ...and while one is being carried. The caption itself is the thing moving,
   but the pointer is captured on the pane, so the pane is what wears it. */
#wrap.movingcap{cursor:grabbing}
/* A mark the hand drew can be MOVED, not only picked up - so it wears the move
   cursor rather than the pointer, and the grab while it travels. */
#wrap.overMove{cursor:move}
#wrap.movingmark, #wrap.movingmark canvas{cursor:grabbing}
/* A CAPTION UNDER THE FINGER, and the same three lines the board wears: the
   node rides a transform for the length of the gesture (index.html says why),
   so it is promoted for exactly that long and never on the pan path. The
   width easing is switched off with it - a caption that re-measures while it
   is being carried is the box changing shape under the hand. */
.ovl .drag{will-change:transform; transition:none; z-index:1}
/* AND A THING BEING PICKED UP SAYS SO WHILE THE FINGER WAITS.
   A press on a mark's words or on the board stays the CHART'S until the finger
   has been still for GRAB_HOLD_MS (index.html, `armGrab`) - which is right, and
   shipped completely invisible: a hand that pressed and then moved got a pan,
   with nothing on screen saying that waiting would have worked, so the gesture
   read as "these cannot be dragged at all".

   The ring grows over exactly that wait - `--grab-hold` is published by
   index.html so the two numbers cannot drift - and is whole at the moment the
   box can be moved, so a press that is really the first frame of a swipe shows
   a tenth of it and one that is meant shows the gesture arriving. An OUTLINE
   rather than a shadow: the boxes carry a drop shadow and the tags carry none,
   and a box-shadow ring would have to interpolate against both. `currentColor`
   is the mark's own colour, written inline by overlay.js - this is that caption
   answering, not a piece of chrome. No transform, because the drag writes one,
   and the transition is the whole animation (rule 5). */
.ovl>div,.o-board{outline:0 solid currentColor; outline-offset:2px}
.ovl .holding,.o-board.holding{
  outline-width:3px;
  transition:outline-width var(--grab-hold, 400ms) linear;
}
/* ...and once it is yours it is in the hand rather than on the chart: the ring
   stays, thinner, and the box is lifted. The board has worn this since it was
   the only thing here that could be picked up. */
.ovl .drag{outline-width:2px; box-shadow:0 10px 26px rgba(0,0,0,.6)}
/* The finders are run, not switched on, so their row is a control row rather
   than a toggle: a label, where to look, and the two verbs. */
.panel .ctl.find{padding-left:10px}
.panel .ctl.find .lbl{min-width:110px; color:var(--text); font-size:12px}
/* `min-width:190px` on the bare `select` above is for the header-sized ones and
   is wider than this whole panel; min-width beats width, so every select in
   here has to say so or it pushes its own label off the row. */
.panel .ctl select{
  flex:1; min-width:0; height:22px; padding:0 4px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r-sm); font:11px var(--num);
}
.panel .ctl.find .cur{font-size:10.5px}
.panel .grp .slot{margin-left:auto; border:none; padding:0; background:none; font-weight:400; color:var(--dim)}
.panel .ctl button[disabled]{opacity:.4; cursor:default}
.panel .ctl button[disabled]:hover{color:var(--muted); background:var(--hover)}

/* The selection halo. Its own layer over .ovl and .rule, never exported, and
   never in the way of a click - a highlight has no business intercepting one.
   A segment is a rotated bar, which is all a line is; a note or a marker is a
   point and gets a ring instead. */
.sel{position:absolute; inset:0; z-index:2; pointer-events:none; overflow:hidden}
.sel>div.r-yt{position:absolute; box-sizing:border-box}
/* THE LIGHT ON A PLACE THE ANSWER NAMED, for two seconds after the tap
   (flashPlace). The halo's own shapes in the thing's own ink rather than the
   halo's orange: this is "here it is", not "you are holding it". A day is a
   soft column over its candles. Opacity is driven per frame from the page. */
.jmp{position:absolute; inset:0; z-index:2; pointer-events:none; overflow:hidden; --jmp-ink:var(--agent)}
.jmp i{
  position:absolute; height:3px; margin-top:-1.5px; border-radius:2px;
  background:var(--jmp-ink); box-shadow:0 0 12px var(--jmp-ink), 0 0 3px var(--jmp-ink);
  transform-origin:0 50%;
}
.jmp i.dot{
  width:17px; height:17px; margin:-8.5px 0 0 -8.5px; border-radius:50%;
  background:none; border:2px solid var(--jmp-ink);
}
.jmp .r-yt{position:absolute; box-sizing:border-box; background:var(--jmp-ink)}
.jmp s{
  position:absolute; top:0; bottom:0;
  background:linear-gradient(to bottom, transparent,
    color-mix(in srgb, var(--jmp-ink) 26%, transparent) 14%,
    color-mix(in srgb, var(--jmp-ink) 26%, transparent) 86%, transparent);
  border-inline:1.5px solid color-mix(in srgb, var(--jmp-ink) 70%, transparent);
  box-shadow:0 0 14px color-mix(in srgb, var(--jmp-ink) 35%, transparent);
}
.sel i{
  position:absolute; height:3px; margin-top:-1.5px; border-radius:2px;
  background:var(--warn); box-shadow:0 0 7px var(--warn-line);
  transform-origin:0 50%; opacity:.9;
}
.sel i.dot{
  width:15px; height:15px; margin:-7.5px 0 0 -7.5px; border-radius:50%;
  background:none; border:2px solid var(--warn); box-shadow:0 0 7px var(--warn-line);
}
.sel b{
  position:absolute; transform:translate(-50%,-50%); white-space:nowrap;
  padding:2px 7px; border-radius:5px; font:10.5px/1.5 var(--sans); font-weight:500;
  color:var(--warn); background:var(--warn-soft); border:1px solid var(--warn-line);
}
/* THE ONE THING IN THE HALO THAT TAKES A POINTER. A finger has no Esc and no
   Delete, so a mark it picked up could not be put down - the hint went silent
   rather than lying, and the silence was the shape of a missing feature. This
   is that feature: the way out, drawn, in the halo's own colour so it reads as
   part of what is held rather than as a new control on the chart. */
.sel button.rm{
  position:absolute; transform:translate(-50%,-50%);
  width:30px; height:30px; padding:0; border-radius:50%;
  display:grid; place-items:center; cursor:pointer;
  font:16px/1 var(--sans); color:var(--warn);
  background:linear-gradient(var(--warn-soft),var(--warn-soft)), var(--bg);
  border:1px solid var(--warn-line); box-shadow:0 0 7px var(--warn-line);
  pointer-events:auto; touch-action:none; -webkit-tap-highlight-color:transparent;
}
.sel button.rm:active{background:var(--warn-line)}
/* THE GRIPS of a held mark: a solid ring on each point that reshapes it. Solid
   against the halo's hollow point-ring, so a grip never reads as a note. */
.sel i.hd{
  width:11px; height:11px; margin:-5.5px 0 0 -5.5px; border-radius:50%;
  background:var(--bg); border:2px solid var(--warn); box-shadow:0 0 6px var(--warn-line);
  transform:none; opacity:1;
}
/* WHAT THE MAGNET TOOK - a ring on the price and its name beside it, in the
   hand's gold wherever it is drawn, because it is the hand's point. */
.sel i.dot.snap, div.pick i.dot.snap{
  width:13px; height:13px; margin:-6.5px 0 0 -6.5px; border-radius:50%;
  background:none; border:2px solid var(--gold); box-shadow:0 0 8px var(--gold-line); opacity:1;
}
.sel b.snap, div.pick b.snap{
  transform:translate(0,-50%); color:var(--gold);
  background:var(--gold-soft); border:1px solid var(--gold-line);
}
/* An armed pick owns the next click, the same as an armed ruler does. */
#wrap.picking, #wrap.picking canvas{cursor:crosshair}

/* A pick in progress: the points already placed, and the shape the next click
   will make. Its own layer over the chart, never exported, never in the way of
   a click - the same discipline as the selection halo and for the same reason,
   a gesture must not repaint the chart it is happening on. */
div.pick{position:absolute; inset:0; z-index:2; pointer-events:none; overflow:hidden}
/* The crosshair a finger places a POINT with is the ruler's own - same classes,
   same dashes, same axis tags - because it answers the same question (which
   price, which bar) with the same hand. A second colour for it would read as a
   second question. */
div.pick>div{position:absolute}
div.pick i{
  position:absolute; height:1px; margin-top:-.5px;
  background:var(--gold-line); transform-origin:0 50%; opacity:.85;
}
/* A point that has been PLACED reads solid; the shape it is becoming is a
   guide and stays quiet, because only one of the two is a decision yet. */
div.pick i.pt{
  width:9px; height:9px; margin:-4.5px 0 0 -4.5px; border-radius:50%;
  background:var(--gold); opacity:1; box-shadow:0 0 8px var(--gold-line);
}
div.pick i.dot:not(.pt){
  width:9px; height:9px; margin:-4.5px 0 0 -4.5px; border-radius:50%;
  background:none; border:1px solid var(--gold-line);
}
/* The dot a finger aims a drawing's point with: the ruler's own ring
   (`.r-dot`), because it is the same gesture asking the same question - one
   finger moves it, a tap sets it, and a set point is the solid `.pt`. */
div.pick i.dot.aim{
  width:9px; height:9px; margin:-4.5px 0 0 -4.5px; opacity:1;
  border:1.5px solid var(--gold); background:rgba(11,14,19,.55);
  box-shadow:0 0 0 1px rgba(11,14,19,.75), 0 0 6px rgba(0,0,0,.5);
}
div.pick b{
  position:absolute; transform:translate(-50%,-100%); white-space:nowrap;
  padding:2px 7px; border-radius:5px; font:10.5px/1.5 var(--sans); font-weight:500;
  color:var(--gold); background:var(--gold-soft); border:1px solid var(--gold-line);
}

/* ---- option value, the chart's options window ----
   Same frame as the report and the calendar - scrim, window, title bar, the
   buttons - joined to their selector lists above rather than copied. What is
   its own: the legs, the grid of price against date, and the curve. Every
   control is one the page already has - the segmented track is `.tfs`, a
   menu is the `.panel` the I / D / N buttons open, a field is the well - so
   the window reads as part of the chart rather than as a calculator bolted
   on. The only inks are the page's: gold for what the user chose and for
   price-now, green and red at the grid's own low alpha, cyan for the band. */
#optv .win{width:min(1000px, calc(100% - 44px))}
#optv .wbody{overflow:auto; padding:16px 20px 20px; display:flex; flex-direction:column; gap:14px}
#optv .out{display:flex; flex-direction:column; gap:14px; min-width:0}
#optv .tk{font:500 12px var(--num); color:var(--doc-accent); padding:2px 7px; background:var(--doc-accent-soft); border-radius:var(--r-xs)}
#optv svg.i{width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; flex:none}
/* THE SEGMENTED TRACK, as the header's timeframe row drew it before the desktop
   turned that row into a ruler of words (`body:not(.phone) .tfs`): a well, one
   inset hairline, and the page's single on-treatment. Its own class, because
   `.tfs` is the timeframe row's and follows the header wherever it goes. */
#optv .seg{
  flex:none; display:flex; align-items:center; gap:2px; height:28px; padding:2px;
  background:var(--well); border-radius:var(--r); box-shadow:inset 0 0 0 1px var(--line);
}
#optv .seg button, #optv .wbar .seg button{
  height:24px; padding:0 10px; border:0; border-radius:var(--r-sm); background:none; color:var(--muted);
  font:500 12px var(--num); cursor:pointer;
  transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1);
}
#optv .seg button:hover{color:var(--text); background:var(--hover)}
#optv .seg button.on, #optv .wbar .seg button.on{background:var(--gold-soft); color:var(--gold); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#optv .wbar .seg button:disabled{opacity:.4; cursor:default; background:none}
#optv .cap{font:500 10.5px var(--sans); letter-spacing:.08em; text-transform:uppercase; color:var(--doc-accent-dim)}

#optv .legs{display:flex; flex-direction:column; gap:6px}
/* The position in one line - what the analyst opened is read far more often
   than it is edited - with Edit beside it; Done folds the controls back. */
#optv .lsum{display:flex; align-items:center; gap:12px; padding:10px 12px; background:var(--doc-bg2);
  border-radius:var(--r-lg); box-shadow:inset 0 1px 0 var(--hi)}
#optv .lsum .ltxt{display:flex; flex-direction:column; gap:2px; min-width:0; flex:1}
#optv .lsum .ltxt b{font:600 14px var(--num); color:var(--doc-text)}
#optv .lsum .ltxt span{font:12px var(--num); color:var(--doc-muted)}
#optv .lbtn{flex:none; height:30px; padding:0 12px; border:0; border-radius:var(--r-sm); cursor:pointer;
  background:var(--gold-soft); color:var(--gold); font:500 12.5px var(--sans);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#optv .lbtn:hover{background:rgba(228,199,120,.2)}
#optv .lfoot{display:flex; align-items:center; justify-content:space-between}
#optv .howto{font-size:11.5px; color:var(--doc-dim); line-height:1.5}
#optv .grid thead th.corner small{font-size:10px}
#optv .panel .opt.more{color:var(--doc-accent); font:500 12px var(--sans)}
#optv .panel .opt.more svg.i{width:12px; height:12px}
#optv .leg{
  display:flex; align-items:flex-end; gap:10px 12px; flex-wrap:wrap; padding:9px 12px 10px;
  background:var(--doc-bg2); border-radius:var(--r-lg); box-shadow:inset 0 1px 0 var(--hi);
}
#optv .leg .lno{align-self:center; min-width:40px}
#optv .field{display:flex; flex-direction:column; gap:4px}
#optv .dd{position:relative}
#optv .ddb{
  height:28px; display:inline-flex; align-items:center; gap:7px; padding:0 8px 0 10px;
  background:var(--well); color:var(--text); border:1px solid var(--line); border-radius:var(--r);
  font:500 12.5px var(--num); cursor:pointer; transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
#optv .ddb:hover{border-color:var(--line2)}
#optv .ddb[aria-expanded="true"]{border-color:var(--gold-line); box-shadow:var(--ring)}
#optv .ddb .sub{font:400 11px var(--num); color:var(--dim)}
#optv .ddb .lead{color:var(--dim); width:13px; height:13px}
#optv .ddb .chev{color:var(--gold); width:13px; height:13px; transition:transform var(--dur-2) var(--ease-out)}
#optv .ddb[aria-expanded="true"] .chev{transform:rotate(180deg)}
#optv .num-in{
  height:28px; width:72px; padding:0 9px; background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r); font:500 12.5px var(--num); outline:none;
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
#optv .num-in.qty{width:52px}
#optv .num-in::placeholder{color:var(--dim); font-weight:400}
#optv .num-in:hover{border-color:var(--line2)}
#optv .num-in:focus{border-color:var(--gold-line); box-shadow:var(--ring)}
#optv .dd .panel{width:260px; max-height:330px}
#optv .panel .opt{
  display:flex; align-items:center; gap:10px; width:100%; padding:6px 10px; border:0; border-radius:var(--r);
  background:none; color:var(--text); font:500 12.5px var(--num); cursor:pointer; text-align:left;
  transition:background-color var(--dur-1);
}
#optv .panel .opt:hover{background:var(--hover)}
#optv .panel .opt.on{color:var(--gold)}
#optv .panel .opt.on::before{content:""; width:5px; height:5px; border-radius:50%; background:var(--gold); margin:0 -3px 0 -2px}
#optv .panel .opt .pct{font:400 11px var(--num); color:var(--dim)}
#optv .panel .opt .itm{font:400 11px var(--sans); color:var(--dim)}
#optv .panel .slot{margin-left:auto; font:500 10.5px var(--num); color:var(--dim); background:var(--hover); border-radius:var(--r-xs); padding:3px 6px}
#optv .panel .slot + .slot{margin-left:6px}
#optv .panel .slot.earn{color:var(--gold); background:var(--gold-soft)}
#optv .panel .spotline{display:flex; align-items:center; gap:8px; padding:4px 10px; font:500 11px var(--num); color:var(--gold)}
#optv .panel .spotline::before, #optv .panel .spotline::after{content:""; flex:1; height:1px; background:var(--gold-line)}
#optv .now{margin-left:auto; display:flex; gap:18px; align-items:flex-end}
#optv .kv{display:flex; flex-direction:column; align-items:flex-end; gap:3px}
#optv .kv b{font:500 14px var(--num); color:var(--doc-text)}
#optv .kv b.warn{color:var(--warn)}
#optv .ib{
  height:28px; width:28px; display:inline-flex; align-items:center; justify-content:center; border:0;
  border-radius:var(--r-sm); background:transparent; color:var(--doc-muted); cursor:pointer;
  transition:color var(--dur-1), background-color var(--dur-1);
}
#optv .ib:hover{color:var(--stop); background:var(--stop-soft)}
#optv .add{
  align-self:flex-start; height:26px; padding:0 10px 0 8px; display:inline-flex; align-items:center; gap:6px;
  border:0; border-radius:var(--r-sm); background:transparent; color:var(--doc-accent); font:500 12px var(--sans); cursor:pointer;
}
#optv .add:hover{background:var(--gold-soft)}

#optv .basis{display:flex; gap:6px 16px; flex-wrap:wrap; align-items:center; font-size:11.5px; color:var(--doc-muted)}
#optv .basis>span{display:inline-flex; align-items:center; gap:5px}
#optv .basis svg.i{width:13px; height:13px}
#optv .basis b{color:var(--doc-text); font-weight:500}
#optv .basis em{font-style:normal; color:var(--doc-dim)}
#optv .basis .stale, #optv .basis .stale b{color:var(--warn)}
#optv .basis .gold{color:var(--gold)}
#optv .basis .ivs{margin-left:auto}
#optv .basis .ivs input{width:110px}
#optv .basis .ivv{min-width:52px; color:var(--gold)}

#optv .gridwrap{overflow:auto; border-radius:var(--r-lg); background:var(--doc-bg2); box-shadow:inset 0 1px 0 var(--hi)}
#optv table.grid{border-collapse:separate; border-spacing:0; width:100%}
#optv .grid th, #optv .grid td{padding:0 10px; height:26px; text-align:right; white-space:nowrap; font-variant-numeric:tabular-nums}
#optv .grid thead th{
  position:sticky; top:0; z-index:2; height:42px; vertical-align:bottom; padding-bottom:7px;
  background:var(--doc-bg2); border-bottom:1px solid var(--doc-line);
  font:500 11.5px var(--num); color:var(--doc-muted); transition:color var(--dur-1);
}
#optv .grid thead th small{display:block; font:400 10px var(--sans); color:var(--doc-dim)}
#optv .grid thead th.col-now{color:var(--doc-text)}
#optv .grid thead th.col-earnings, #optv .grid thead th.col-earnings small{color:var(--gold)}
#optv .grid thead th.col-expiry{color:var(--doc-accent-hi)}
#optv .grid thead th.corner{left:0; z-index:3}
#optv .grid tbody th{
  position:sticky; left:0; z-index:1; padding-right:12px; background:var(--doc-bg2);
  border-right:1px solid var(--doc-line); font:500 12px var(--num); color:var(--doc-muted);
  transition:color var(--dur-1);
}
#optv .grid tbody th small{margin-left:6px; font:400 10.5px var(--num); color:var(--doc-dim)}
#optv .grid tbody th .k{margin-right:4px; font-weight:400; color:var(--dim)}
#optv .grid th.hx{color:var(--gold-hi)}
#optv .grid tr.spot th{color:var(--gold)}
#optv .grid tr.spot td{box-shadow:inset 0 1px 0 var(--gold-line), inset 0 -1px 0 var(--gold-line)}
#optv .grid tr.band th{box-shadow:inset -2px 0 0 rgba(53,205,240,.55)}
#optv .grid td{font:400 12px var(--num); color:var(--doc-text); cursor:pointer}
#optv .grid td.zero{color:var(--doc-dim)}
/* WHERE IT TURNS: the edge between a cell in profit and the one under it at a
   loss, dashed gold - the break-even for that day, drawn rather than found. */
#optv .grid td.edge{border-bottom:1px dashed rgba(228,199,120,.55)}
#optv .grid td:hover{box-shadow:inset 0 0 0 1px var(--gold-line)}
/* `.held`, not `.sel`: `.sel` is the page's selection LAYER, absolutely placed. */
#optv .grid td.held{box-shadow:inset 0 0 0 1.5px var(--gold); color:var(--gold-hi)}

#optv .legend{display:flex; align-items:center; gap:6px 16px; flex-wrap:wrap; font-size:11px; color:var(--doc-muted)}
#optv .legend>span{display:inline-flex; align-items:center; gap:6px}
#optv .legend .sw{width:10px; height:10px; border-radius:3px}
#optv .legend .sw.up{background:rgba(47,208,138,.3)}
#optv .legend .sw.down{background:rgba(255,107,124,.3)}
#optv .legend .sw.edge{height:0; width:14px; border-radius:0; border-top:1px dashed rgba(228,199,120,.8)}
#optv .legend .sw.spot{background:var(--gold)}
#optv .legend .sw.band{background:rgba(53,205,240,.55)}
#optv .legend .hint{margin-left:auto; color:var(--doc-dim)}

#optv .lower{display:grid; grid-template-columns:minmax(0,1.65fr) minmax(0,1fr); gap:14px}
#optv .card{background:var(--doc-bg2); border-radius:var(--r-lg); box-shadow:inset 0 1px 0 var(--hi); padding:12px 14px; min-width:0}
#optv .card h3{margin:0 0 8px; display:flex; align-items:center; gap:8px; font:600 12px var(--sans); color:var(--doc-muted)}
#optv .card h3 .key{margin-left:auto; display:flex; gap:12px; font:400 10.5px var(--sans); color:var(--doc-dim)}
#optv .card h3 .key i{display:inline-block; width:14px; height:0; margin-right:5px; vertical-align:3px; border-top:2px solid}
#optv .key .k-now{border-color:rgba(148,163,184,.5)!important; border-top-style:dashed!important}
#optv .key .k-at{border-color:var(--gold)!important}
#optv .key .k-exp{border-color:rgba(232,228,216,.5)!important; border-top-width:1px!important}
#optv .curve{display:block; width:100%; height:230px; cursor:crosshair; overflow:visible}
#optv .curve text{font:10.5px var(--num); fill:var(--doc-dim)}
#optv .curve .grd{stroke:rgba(148,163,184,.07)}
#optv .curve .grd.v{stroke:rgba(148,163,184,.045)}
#optv .curve .axis{stroke:rgba(148,163,184,.16)}
#optv .curve .emb{fill:rgba(53,205,240,.05)}
#optv .curve .fill-up{fill:rgba(47,208,138,.09)}
#optv .curve .fill-dn{fill:rgba(255,107,124,.09)}
#optv .curve .base{stroke:rgba(228,199,120,.35); stroke-dasharray:2 4}
#optv .curve .basel{fill:rgba(228,199,120,.75)}
#optv .curve path[class^="c-"]{fill:none; stroke-linejoin:round}
#optv .curve .c-now{stroke:rgba(148,163,184,.5); stroke-width:1.2; stroke-dasharray:4 4}
#optv .curve .c-exp{stroke:rgba(232,228,216,.5); stroke-width:1.2}
#optv .curve .c-at{stroke:var(--gold); stroke-width:2}
#optv .curve .spotl{stroke:rgba(228,199,120,.45)}
#optv .curve .spott{fill:var(--gold)}
#optv .curve .selpt{fill:var(--doc-bg2); stroke:var(--gold); stroke-width:2}
#optv .curve .xv, #optv .curve .xz{stroke:var(--cross); stroke-dasharray:3 3}
#optv .curve .xdot{fill:var(--gold); stroke:var(--doc-bg2); stroke-width:2}
#optv .curve .tag rect{fill:var(--cross-label)}
#optv .curve .tag text{font:500 10.5px var(--num); fill:#15110a}
#optv .slider{display:flex; align-items:center; gap:10px; margin-top:8px; font-size:11.5px; color:var(--doc-muted)}
#optv .slider .dayl{min-width:104px; text-align:right; font-weight:500; color:var(--gold)}
/* ONE SLIDER LOOK: a well-coloured track and a gold knob with a soft ring -
   the switch's knob in the panels, lying down. */
#optv input[type=range]{
  flex:1; -webkit-appearance:none; appearance:none; height:4px; margin:0; border-radius:999px;
  background:var(--well); box-shadow:inset 0 0 0 1px var(--line2); outline:none; cursor:pointer;
}
#optv input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none; width:14px; height:14px; border-radius:50%; background:var(--gold);
  box-shadow:0 0 0 3px var(--gold-soft), 0 0 8px rgba(228,199,120,.35);
}
#optv input[type=range]::-moz-range-thumb{width:14px; height:14px; border:0; border-radius:50%; background:var(--gold)}
#optv input[type=range]:focus-visible{box-shadow:inset 0 0 0 1px var(--gold-line), var(--ring)}
#optv .read dl{margin:0; display:grid; grid-template-columns:1fr auto; gap:8px 12px}
#optv .read dt{color:var(--doc-muted)}
#optv .read dd{margin:0; text-align:right; font:500 12.5px var(--num); color:var(--doc-text)}
#optv .read dd.gold{color:var(--gold)}
#optv .read dd.up{color:var(--up)}
#optv .read dd.down{color:var(--down)}
#optv .read .note{margin-top:12px; padding-top:10px; border-top:1px solid var(--doc-line); display:flex; gap:7px; font-size:11px; color:var(--doc-dim)}
#optv .read .note svg.i{width:13px; height:13px; margin-top:1px; color:var(--doc-muted)}
#optv .state{display:flex; align-items:center; gap:10px; padding:18px 16px; border-radius:var(--r-lg); background:var(--doc-bg2); color:var(--doc-muted)}
#optv .state.err{color:var(--doc-text); box-shadow:inset 0 0 0 1px var(--warn-line)}
#optv .state.err svg.i{color:var(--warn)}
#optv .state .retry{margin-left:auto; height:28px; padding:0 12px; border:0; border-radius:var(--r-sm); background:var(--hover); color:var(--doc-text); font:500 12px var(--sans); cursor:pointer}
#optv .state .retry:hover{background:var(--press)}
/* NARROW - the rail and the chat both open, or a phone: the curve and its
   reading stack, and the legs' readout drops under the fields. */
/* THE CURVE IS READ BY SLIDING ALONG IT, so a finger's sideways travel is
   the curve's; the vertical is still the window's scroll. */
#optv .curve{touch-action:pan-y}
/* A FINGER: every control at the height the page's other coarse targets are,
   and every field at 16px - under that iOS zooms the whole page the moment a
   field takes the focus, and does not zoom back. Written `(hover:none) and`
   rather than bare: the bare query is the page's own coarse-pointer block,
   which tests/phone.test.mjs reads by that exact text. */
@media (hover:none) and (pointer:coarse){
  #optv .seg{height:40px}
  #optv .seg button, #optv .wbar .seg button{height:36px; padding:0 12px; font-size:13px}
  #optv .ddb, #optv .num-in{height:40px; font-size:16px}
  #optv .ib{width:40px; height:40px}
  #optv .wbar button.x{width:40px; height:40px}
  #optv .lbtn{height:40px; padding:0 16px; font-size:13px}
  #optv .add{height:40px; padding:0 14px 0 10px; font-size:13px}
  #optv .grid th, #optv .grid td{height:32px}
  #optv .panel .opt{min-height:40px}
  #optv .state .retry{height:40px; padding:0 16px}
  #optv input[type=range]{height:6px}
  #optv input[type=range]::-webkit-slider-thumb{width:24px; height:24px}
}
@container (max-width: 720px){
  /* The title bar wraps and the close button leaves the flow - #cal's own
     answer, for the same reason: the x is last in a nowrap row and is the
     piece that falls off the screen. */
  #optv .wbar{flex-wrap:wrap; height:auto; min-height:46px; padding:7px 46px 7px 14px; row-gap:6px}
  #optv .wbar .x{position:absolute; top:8px; right:10px; margin:0}
  #optv .lower{grid-template-columns:1fr}
  /* The date the slider stands on takes its own line, so the track gets the
     row: 134px for ten dates was a thumb-width a step. */
  #optv .slider{flex-wrap:wrap}
  #optv .slider .dayl{order:-1; flex-basis:100%; text-align:left}
  /* The key goes under the title rather than squeezing beside it. */
  #optv .card h3{flex-wrap:wrap; row-gap:4px}
  #optv .card h3 .key{margin-left:0; flex-basis:100%}
  /* A LEG IS A GRID OF PAIRS HERE, not a row that wraps wherever it runs out:
     side beside type, strike beside expiry, qty beside paid, and the readout
     under them - four even rows instead of five ragged ones. */
  #optv .leg{display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); align-items:end; gap:10px 10px}
  #optv .leg .lno{grid-column:1 / -1; min-width:0}
  #optv .leg .seg{width:100%}
  #optv .leg .seg button{flex:1}
  #optv .leg .ddb, #optv .leg .num-in{width:100%}
  #optv .leg .ddb .chev{margin-left:auto}
  #optv .leg .dd .panel{width:min(260px, calc(100vw - 40px))}
  #optv .leg .dd[data-dd="expiry"] .panel{left:auto; right:0}
  #optv .now{grid-column:1 / -1; justify-content:space-between}
  #optv .now{margin-left:0; width:100%; justify-content:flex-end}
  #optv .basis .ivs{margin-left:0}
  #optv .legend .hint{display:none}
  #optv .wbody{padding:12px 12px 16px}
}

/* ---- the calendar, the chart's other window ----
   Same chrome as the report above - it shares the scrim, the frame, the title
   bar and the buttons, so there is one window style in this file rather than
   two that drift. What is its own is below: a report is a document and reads
   in one column, a calendar is a LIST and reads down a clock, so the row is a
   grid whose first two columns are fixed and whose day headers stick.
   No --doc-* token is redefined here: the calendar has one ground, and the
   Paper half of the report's token set simply goes unused. */
#cal .win{width:min(940px, calc(100% - 44px))}
/* THE TITLE BAR WRAPS; THE CLOSE BUTTON DOES NOT MOVE.
   It is one row in the report because a report's bar carries three buttons.
   This one carries a view switch, a pager, a date range, four filters and
   Copy, and below about 880px - which is an ordinary width once the lists rail
   and the analyst panel are open - it ran 48px over and pushed its own × OUT
   of the window. Wrapping rather than hiding, because every one of those
   controls is a filter whose value is state: a hidden select is a filter the
   reader cannot see is on. The × is lifted out of the flow entirely and
   pinned, so no amount of overflow can ever reach it again. */
#cal .wbar{flex-wrap:wrap; height:auto; min-height:46px; padding:7px 46px 7px 16px; row-gap:7px}
#cal .wbar .x{position:absolute; top:8px; right:12px; margin:0}

/* The list/board switch is a segmented control, so it wears the header's
   timeframe track: a well, a line drawn inside it, the one on-treatment. */
#cal .wbar .views{display:flex; align-items:center; gap:2px; flex:none; padding:2px;
  border-radius:var(--r); background:var(--well); box-shadow:inset 0 0 0 1px var(--line)}
#cal .wbar .views button{width:28px; height:26px; padding:0; justify-content:center}
#cal .wbar .nav{display:flex; align-items:center; gap:4px; flex:none}
#cal .wbar .nav button{height:26px; padding:0 8px; font-size:12px}
#cal .wbar .nav button.back, #cal .wbar .nav button.fwd{width:26px; padding:0; justify-content:center; font-size:15px}
#cal .wbar .span{font:500 12px var(--num); color:var(--doc-accent); white-space:nowrap}
#cal .wbar .ctrls{display:flex; align-items:center; gap:6px; flex:none}
/* The global rule is `select{min-width:190px}`, which is wider than three of
   these side by side - the same trap the indicator panels hit. */
#cal .wbar select{
  min-width:0; height:26px; padding:0 6px; border-radius:var(--r-sm);
  border:1px solid var(--line); background:var(--well); color:var(--doc-text);
  font:500 11.5px var(--sans); cursor:pointer;
}
#cal .wbar select:hover{border-color:var(--line2)}
#cal .wbar select:focus{border-color:var(--gold-line); box-shadow:var(--ring)}
#cal .wbar .movers{height:26px; padding:0 9px; font-size:11.5px}
/* ...a filter toggle among the filters, so it has the faint ground the other
   chips on this page have at rest, or it reads as a label beside them. */
#cal .wbar .movers:not(.on){background:var(--hover)}
#cal .wbar .movers:not(.on):hover{background:var(--press)}
#cal .wbody{overflow:auto; padding:0}
#cal .cdoc{display:flex; flex-direction:column}
#cal .cwarn{
  margin:10px 14px 2px; padding:7px 11px; border-radius:7px; font-size:12px;
  color:var(--doc-warn); background:rgba(176,98,10,.09); border:1px solid rgba(176,98,10,.3);
}
#cal .cload{padding:18px 16px; color:var(--doc-muted)}

#cal .cday{display:flex; flex-direction:column; border-bottom:1px solid var(--doc-line)}
#cal .cday.bare{opacity:.62}
/* The day header sticks, because the answer to "what day is this row" must
   not depend on how far you have scrolled. */
#cal .dhead{
  position:sticky; top:0; z-index:2; display:flex; align-items:baseline; gap:9px;
  padding:6px 16px; background:var(--doc-bg2); border-bottom:1px solid var(--doc-line);
}
#cal .dhead b{font:600 11.5px var(--sans); letter-spacing:.01em; color:var(--doc-muted)}
#cal .dhead .rel{font:11px var(--sans); color:var(--doc-dim)}
#cal .dhead .nil{margin-left:auto; font:11px var(--sans); color:var(--doc-dim)}
#cal .dhead .nil.rep{color:var(--doc-accent-dim)}
#cal .cname .kd.own{color:var(--doc-accent-hi); background:var(--doc-accent-soft)}
#cal .cday.wk .dhead b{color:var(--doc-dim)}
#cal .cday.now .dhead{background:var(--doc-accent-soft)}
#cal .cday.now .dhead b{color:var(--doc-accent-hi)}
#cal .cday.now .dhead .rel{color:var(--doc-accent)}

#cal .crow{
  display:grid; grid-template-columns:72px 34px minmax(0,1fr) 26px; gap:10px;
  align-items:baseline; padding:6px 16px 7px; min-width:0;
}
#cal .crow + .crow{border-top:1px solid var(--doc-line)}
#cal .crow:hover{background:rgba(228,199,120,.035)}
#cal .crow.bare:hover{background:none}
#cal .ct{font:11.5px var(--num); font-variant-numeric:tabular-nums; color:var(--doc-accent-dim); white-space:nowrap}
/* A bucket is words, not a clock, and 'time not supplied' is wider than the
   time column. It takes the importance column too - a company report has no
   importance rating to put there, so the ground is free. */
#cal .crow.earn .ct{grid-column:1 / 3; font-family:var(--sans); font-size:11px; color:var(--doc-muted)}
#cal .crow.earn .cimp{display:none}
/* New York under the reader's own clock: the release is scheduled there and
   every source quotes it there, so it stays checkable without competing. */
#cal .ct .ny{display:block; font:9.5px var(--num); font-style:normal; color:var(--doc-dim); margin-top:1px}
#cal .cimp{font-size:7px; letter-spacing:1px; color:var(--doc-dim); white-space:nowrap}
#cal .cimp.high{color:var(--doc-accent-hi)}
#cal .cimp.medium{color:var(--doc-accent-dim)}
#cal .cmain{display:flex; flex-direction:column; gap:2px; min-width:0}
#cal .cname{font:500 12.5px var(--sans); color:var(--doc-text); overflow-wrap:anywhere}
#cal .cname .tk{font:600 12.5px var(--num); color:var(--doc-accent)}
#cal .cname .kd{
  font:500 10.5px var(--sans); font-style:normal; margin-left:7px; padding:2px 6px;
  border-radius:var(--r-xs); color:var(--doc-muted); border:1px solid transparent; background:var(--hover);
}
#cal .cname .kd.warn{color:var(--doc-warn); background:var(--warn-soft)}
#cal .cmeta{font:11px var(--sans); color:var(--doc-muted)}
#cal .cbs{display:flex; flex-direction:column; gap:1px}
#cal .cb{display:flex; align-items:baseline; gap:4px 8px; flex-wrap:wrap; font-size:11.5px; min-width:0}
/* One line per series when a release has several, each with the qualifier
   that tells it from its neighbour. They wrap onto one line when they fit. */
#cal .cs{display:inline-flex; align-items:baseline; gap:5px; min-width:0}
#cal .cq{font:normal 9.5px var(--sans); font-style:normal; letter-spacing:.06em;
  text-transform:uppercase; color:var(--doc-accent-dim); border:1px solid var(--doc-line);
  border-radius:3px; padding:0 4px}
#cal .cbn{color:var(--doc-muted); overflow-wrap:anywhere}
#cal .cbp{font:10px var(--num); color:var(--doc-dim)}
#cal .cbv{font-family:var(--num); font-variant-numeric:tabular-nums; color:var(--doc-text)}
#cal .cbv b{font-weight:600}
/* The per cent against the forecast, and it is the DIRECTION - above the
   forecast or below it, as a price move is written. The print itself is not
   coloured: it wore the source's better/worse verdict, which disagrees with
   the direction on every series where less is better. */
#cal .cbv b.up{color:var(--doc-up)} #cal .cbv b.dn{color:var(--doc-down)}
#cal .cbv .ex{color:var(--doc-dim)}
#cal .cbv i{font-style:normal; color:var(--doc-dim)}
/* A release past its time with no figure is its own answer, not a blank. */
#cal .cbv .none{font-family:var(--sans); color:var(--doc-warn); font-size:11px}
/* WHAT THE RELEASE IS, AND HOW MARKETS USUALLY TAKE IT (`econ-guide.js`) -
   the calendar window and the chart's dot panel share these rules. The `?`
   opens it under the release; the one dim line is what a level-1 reader sees
   without asking. Neutral ink throughout: a usual reaction is not a verdict
   on the print, so it wears neither the candles' green nor their red. */
#cal .cxp, .macroread .cxp{
  flex:none; width:18px; height:18px; padding:0; margin:0 2px; border-radius:50%;
  border:1px solid transparent; background:var(--hover); color:var(--muted);
  font:600 11px/16px var(--sans); cursor:pointer; vertical-align:middle;
  transition:color var(--dur-1), background-color var(--dur-1);
}
@media (hover:none){ #cal .cxp, .macroread .cxp{width:28px; height:28px; line-height:26px} }
#cal .cxp:hover, .macroread .cxp:hover{color:var(--text); background:var(--press)}
#cal .cxp.on, .macroread .cxp.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#cal .cxs, #cal .cxd{flex-basis:100%; min-width:0; font-family:var(--sans)}
#cal .cxs, .macroread .cxs{font-size:11px; color:var(--doc-dim, var(--dim)); line-height:1.4}
#cal .cxd, .macroread .cxd{
  margin:3px 0 2px; padding:6px 8px; border-left:2px solid var(--line);
  font-size:11.5px; line-height:1.45; color:var(--doc-text, var(--text));
}
#cal .cxd[dir="rtl"], .macroread .cxd[dir="rtl"]{border-left:0; border-right:2px solid var(--line)}
#cal .cxd p, .macroread .cxd p{margin:0 0 3px}
#cal .cxd b, .macroread .cxd b{font-weight:600}
#cal .cxd .cxv, .macroread .cxd .cxv{margin-bottom:5px}
#cal .cxd .cxa, .macroread .cxd .cxa{color:var(--doc-muted, var(--dim))}
#cal .cxd .cxu, .macroread .cxd .cxu, #cal .cxd .cxn, .macroread .cxd .cxn{
  margin:4px 0 0; font-size:10.5px; color:var(--doc-dim, var(--dim))}
.macroread .cxs, .macroread .cxd{margin-top:4px}
/* Opened, the explanation says what it is itself - the one-line version goes. */
#cal .cb:has(> .cxd:not([hidden])) > .cxs, .macroread .mev:has(> .cxd:not([hidden])) > .cxs{display:none}
/* What has not happened yet is the brighter half of the window: it is the
   half you can still do something about. */
#cal .crow.ahead .cname{color:var(--doc-accent-hi)}
#cal .crow.printed .cname{color:var(--doc-text)}
#cal .crow.unreported .cname{color:var(--doc-muted)}
#cal .crow.holiday{background:rgba(228,199,120,.05)}
#cal .crow.holiday .cname{color:var(--doc-accent)}
/* WHERE WE ARE IN THE DAY (Dor, 2026-10-02): the next release to come out
   holds a gold frame, and moves on as each one prints. Only a tapped
   notification pulses: its release beats three times, and if it has already
   printed it lets go afterwards (`.gone`), because the frame that stays is the
   next one's. Opened by hand, the frame just sits there. Gold,
   because the gesture was the user's. Everything is INSET, so neither the
   frame arriving nor the pulse leaving moves a row by a pixel. */
#cal .crow.next, #cal .crow.flash{border-radius:7px; border-top-color:transparent}
#cal .crow.next + .crow, #cal .crow.flash + .crow{border-top-color:transparent}
#cal .crow.next{background:var(--gold-soft); box-shadow:inset 0 0 0 1.5px var(--gold)}
#cal .crow.flash{animation:calhit 1.1s ease-in-out 3}
#cal .crow.flash.gone{animation-name:calhitgo}
@keyframes calhit{
  0%,100%{box-shadow:inset 0 0 0 1.5px var(--gold); background:var(--gold-soft)}
  50%{box-shadow:inset 0 0 0 2.5px var(--gold-hi), inset 0 0 18px 2px rgba(228,199,120,.45);
      background:rgba(228,199,120,.22)}
}
@keyframes calhitgo{
  0%,100%{box-shadow:inset 0 0 0 1.5px rgba(228,199,120,0); background:transparent}
  50%{box-shadow:inset 0 0 0 2.5px var(--gold-hi), inset 0 0 18px 2px rgba(228,199,120,.45);
      background:rgba(228,199,120,.22)}
}
#cal .crow.earn.mine .cname .tk{color:var(--doc-accent-hi); text-decoration:underline; text-underline-offset:3px}
#cal .cgo{
  width:22px; height:22px; padding:0; border-radius:6px; border:1px solid transparent;
  background:transparent; color:var(--doc-dim); font:13px var(--sans); cursor:pointer;
  justify-self:end; align-self:center; opacity:0; transition:opacity .12s;
}
#cal .crow:hover .cgo{opacity:1}
#cal .cgo:hover{color:var(--doc-accent-hi); background:var(--hover)}
/* ---- the board: a month at a glance ----
   The second view. Five market days at full width and the weekend narrower -
   the two columns are there so a week reads as a week, not because anything
   happens in them. A cell is a BUTTON: it is the whole hit target, it takes a
   keyboard, and clicking it hands over to the list at that day. */
#cal .cboard{display:flex; flex-direction:column; min-height:0}
#cal .bhead, #cal .bgrid{display:grid; grid-template-columns:repeat(5, minmax(0,1fr)) .62fr .62fr}
#cal .bhead{position:sticky; top:0; z-index:2; background:var(--doc-bg2); border-bottom:1px solid var(--doc-line)}
#cal .bhead span{
  padding:6px 8px; font:600 11.5px var(--sans); letter-spacing:.01em;
  color:var(--doc-muted);
}
#cal .bhead span:nth-child(n+6){color:var(--doc-dim)}
#cal .bgrid{gap:1px; background:var(--doc-line); border-bottom:1px solid var(--doc-line)}
#cal .bcell{
  display:flex; flex-direction:column; align-items:stretch; gap:2px;
  min-height:92px; min-width:0; padding:5px 7px 7px; border:0; border-radius:0;
  background:var(--doc-bg); color:inherit; font:inherit; text-align:left; cursor:pointer;
}
#cal .bcell:hover{background:rgba(228,199,120,.05)}
#cal .bcell:focus-visible{outline:1px solid var(--doc-accent); outline-offset:-1px}
/* Padding days from the months either side. They are shown rather than left
   blank so a week is always seven days, and dimmed so the month is legible. */
#cal .bcell.out{background:var(--doc-bg2); opacity:.5}
#cal .bcell.wk{opacity:.72}
#cal .bcell.out.wk{opacity:.38}
#cal .bd{font:600 11.5px var(--num); font-variant-numeric:tabular-nums; color:var(--doc-muted)}
#cal .bcell.today{box-shadow:inset 0 0 0 1px var(--doc-accent)}
#cal .bcell.today .bd{color:var(--doc-accent-hi)}
#cal .be{
  display:flex; align-items:baseline; gap:4px; min-width:0;
  font:11px var(--sans); color:var(--doc-text);
}
#cal .be b{font-weight:400; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
#cal .be .cimp{font-size:5.5px; letter-spacing:.6px; flex:none}
#cal .be.ahead b{color:var(--doc-accent-hi)}
#cal .be.unreported b{color:var(--doc-muted)}
#cal .be.holiday b{color:var(--doc-accent); font-weight:500}
/* An all-day row is context for the whole day - a closure, an election, a
   clock change. The tint says "something is true of this whole day", which is
   all the source supports; it does not claim the market is shut. */
#cal .bcell.allday{background:rgba(228,199,120,.055)}
#cal .bcell.allday .bd{color:var(--doc-accent-dim)}
#cal .bmore{margin-top:auto; font:10px var(--sans); color:var(--doc-dim)}
@container (max-width:640px){
  #cal .bcell{min-height:74px; padding:4px 5px 6px}
  #cal .be{font-size:10px}
}

#cal .wfoot{
  display:flex; align-items:center; gap:12px; justify-content:space-between;
  flex:none; padding:7px 16px; background:var(--doc-bg2); border-top:1px solid var(--doc-line);
  font:10.5px var(--sans); color:var(--doc-dim);
}
#cal .wfoot .miss{color:var(--doc-muted); text-align:right}
/* The bar wraps now, so these only reduce HOW OFTEN it wraps - they no longer
   stand between the reader and a control. The Copy label goes because its icon
   is unambiguous; nothing that carries state does. */
@container (max-width:880px){
  #cal .wbar .copy .lbl{display:none}
  #cal .wbar .copy{padding:0 8px}
}
@container (max-width:640px){
  #cal .wbar .span{font-size:11px}
  #cal .wbar .copy .lbl{display:none}
  #cal .crow{grid-template-columns:64px 26px minmax(0,1fr) 22px; gap:8px; padding:6px 12px}
  #cal .dhead{padding:6px 12px}
}

/* ---- reading one bar of the strike map ----
   Its own layer over .ovl, like the halo and the ruler, and for the same
   reason: pointing at a bar must not repaint the chart it is drawn on. Flat
   colours and plain borders throughout - a PINNED reading is composited into
   the snapshot, and snapshot.js paints backgroundColor, borders and text and
   nothing else. Never in the way of a click; the hit test is geometry. */
/* ---- WHAT RANK A READING SITS AT ----
   A reading is OPENED - a press on a bar of the strike map, on a headline
   tick, on a release, on a print, on an armed line - and while it is open it
   is the one thing on the pane you are looking at. So the five reading layers
   share ONE rank, above every piece of the pane's read-only furniture: the
   legend (3), the distance tags (3), the zoom and scale rails (3) and the
   captions inside `.ovl` (2). It was 2 for the strike map alone and 3 for the
   other four, which put the oi box UNDER the legend and under the percentage
   tags and left the other three winning only by being later in the markup -
   reported from the phone on 2026-09-18, the pinned AVGO reading read through
   the legend's O/H/L/C row and through eight distance tags. Below the drawbar
   (6) and the report (6), which are modes rather than furniture; only one
   reading is ever open, so the five never argue with each other. */
.oiread{position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden}
/* The guide sits at the STRIKE, which is not where the bar sits: a call is
   drawn a lane above its own strike and a put a lane below, and that gap is
   what made a 5.00 put wall read as "under five". This is the line that says
   which price the bar belongs to, run out to meet the axis printing it. */
.oiread .oiline{
  /* A flat 1px ground rather than a dashed border: snapshot.js skips a node
     with no height and paints backgroundColor only, and a pinned reading is
     worth nothing in the picture without the line that says which price it
     belongs to. */
  position:absolute; height:1px; background:var(--c); opacity:.5;
}
.oiread .oihalo{
  position:absolute; border:1px solid var(--c); border-radius:3px; opacity:.9;
}
/* THE BOX IS SIZED AGAINST THE ROOM IT HAS, NOT AGAINST A SHARE OF THE PANE.
   This was `max-width: min(280px, 40%)` over `min-width: 196px`, which is two
   rules that disagree the moment the pane is narrow: 40% of a 390px phone is
   156px, UNDER the min, so the min won, the box stood at 196px, and every row
   in it is a nowrap label beside a nowrap figure - `put side, same strike` and
   `— OI · 17,049 vol` want 230px between them. They overflowed the border and
   ran out over the candles (measured on NVDA, 2026-09-14). The desktop never
   showed it because 40% there is the 280px cap and the rows fit inside it.

   `--oi-room` is what paintOiRead() measured between the pane's left edge and
   the strike column, so the cap is the real room and the min can never exceed
   it. `max-content` then lets the box be as wide as its widest row wants to be
   and no wider - a reading box has no business being one width. */
.oiread .oibox{
  position:absolute; width:max-content;
  min-width:min(196px, var(--oi-room, 196px));
  max-width:min(280px, var(--oi-room, 280px));
  padding:7px 9px 6px; border:1px solid var(--line2); border-radius:var(--r);
  background:var(--ovl-box); color:var(--text); box-shadow:var(--shadow-pop);
}
/* Held, rather than passed over. */
.oiread .oibox.pin{border-color:var(--gold-line)}
.oiread .oih{
  font:600 12px/1.3 var(--num); font-variant-numeric:tabular-nums;
  letter-spacing:.01em;
}
.oiread .oisub{margin-top:1px; font:11px/1.4 var(--sans); color:var(--muted)}
.oiread .oisec{
  margin:6px 0 2px; padding-top:5px; border-top:1px solid var(--line);
  font:600 11px/1.3 var(--sans); color:var(--muted);
  letter-spacing:.01em;
}
/* AND WHEN A PAIR STILL DOES NOT FIT ON ONE LINE, THE FIGURE TAKES THE NEXT
   ONE - it is never shortened and never clipped. A number you cannot read is
   not a reading, which is the same call the strip makes when it folds from the
   END and never folds a warning. The label is what gives way: it ellipsizes,
   because `put side, same strike` is still that row with its last word gone,
   while `17,049` with its last digit gone is a different number. The greeks
   row - three figures in one value - is the one that needs the wrap most. */
.oiread .oir{
  display:flex; justify-content:space-between; gap:12px; align-items:baseline;
  padding:1px 0; flex-wrap:wrap;
}
.oiread .oir span{
  font:11px/1.5 var(--sans); color:var(--muted); white-space:nowrap;
  min-width:0; overflow:hidden; text-overflow:ellipsis;
}
.oiread .oir b{
  font:500 11.5px/1.5 var(--num); font-variant-numeric:tabular-nums;
  color:var(--text); white-space:nowrap; margin-left:auto;
}
/* The other side of the same strike, and the working: there to be checked,
   not to be read first. */
.oiread .oir.leg span, .oiread .oir.other span{color:var(--dim)}
.oiread .oir.leg b, .oiread .oir.other b{color:var(--muted); font-weight:400}
/* A figure with no basis cannot be checked, and open interest and volume are
   two different days - so the basis is part of the reading, never a tooltip
   on it. */
.oiread .oifoot{
  margin-top:6px; padding-top:5px; border-top:1px solid var(--line);
  font:10.5px/1.4 var(--sans); color:var(--dim);
}
.oiread .oifoot.pinned{color:var(--gold); border-top:none; padding-top:3px}

/* ---------------- the drawings panel ----------------
   It is not a settings list. Two of the three things in it are VERBS - draw
   one of these, find one of those - and they were rendered as rows of a
   settings list, which is why it read as an afterthought bolted under the
   indicators. Tools get a tool's shape; a finder gets one line. */
#drawPanel{width:330px}
#drawPanel .tools{display:grid; grid-template-columns:repeat(4,1fr); gap:5px; padding:2px 10px 4px}
#drawPanel .tool{
  display:flex; flex-direction:column; align-items:center; gap:5px;
  padding:9px 2px 7px; cursor:pointer;
  background:var(--hover); color:var(--muted);
  border:1px solid transparent; border-radius:var(--r);
  font:500 11px var(--sans); transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1);
}
#drawPanel .tool svg{
  width:20px; height:20px; fill:none; stroke:currentColor;
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round;
}
#drawPanel .tool:hover{color:var(--text); background:var(--press)}
/* Armed is gold, like everything else the user's hand is doing, and it means
   the next click on the chart is spoken for. */
#drawPanel .tool.armed{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
/* ---- the favourites: the star on a tool, and how it gets there ----
   The star is the bar's door on a mouse (shown on hover, TradingView's place
   for it) and its STATUS everywhere: a tool on the bar wears it lit. On a
   finger it is never a target - a 12px corner of a 72px button is where a
   thumb lands when it means the button - so the hold is the door there. */
#drawPanel .tool{position:relative; -webkit-user-select:none; user-select:none;
  -webkit-touch-callout:none; outline:0 solid var(--gold-line); outline-offset:-1px}
#drawPanel .tool .fav{
  position:absolute; top:3px; right:3px; width:16px; height:16px;
  display:grid; place-items:center; border-radius:4px; opacity:0;
  color:var(--muted); transition:opacity var(--dur-1), color var(--dur-1), transform var(--dur-2) var(--ease-out);
}
#drawPanel .tool .fav svg{width:12px; height:12px; stroke-width:1.5; fill:none}
#drawPanel .tool:hover .fav{opacity:.6}
#drawPanel .tool .fav:hover{opacity:1; color:var(--gold-hi); background:var(--gold-soft)}
#drawPanel .tool.faved .fav{opacity:1; color:var(--gold)}
#drawPanel .tool.faved .fav svg{fill:var(--gold)}
/* The hold, shown arriving over exactly its length (HOLD_MS), so a press that
   is really a tap shows a sliver of it and one that is meant shows it land. */
#drawPanel .tool.pinning{outline-width:2px; transition:outline-width .5s linear}
#drawPanel .tool.pinned .fav{animation:favpop .42s var(--ease-out)}
@keyframes favpop{0%{transform:scale(.4)} 55%{transform:scale(1.45)} 100%{transform:scale(1)}}
#drawPanel .favhint{padding:0 12px 6px; color:var(--dim); font:var(--fs-cap) var(--sans); letter-spacing:.01em}

/* ---- THE FAVOURITES BAR (web/favbar.js) ----
   Small on purpose: it stands on the candles for as long as it is there, so it
   is the icons and nothing else - no title, no handle, no label. Black glass
   with a breath of blue (the panels' own ground), a hairline of gold for the
   edge because everything on it is the USER's hand, and the armed tool lit in
   that gold exactly as it is in the D panel. Not blue: on this chart blue is
   the analyst, and a control the analyst never touches must not wear it.
   Placed by left/top from favbar.js; a drag is a transform, so `scale` (the
   independent property) is what lifts it. */
.favbar{
  position:absolute; z-index:5; display:flex; gap:2px; padding:3px;
  border-radius:10px;
  background:linear-gradient(180deg, rgba(22,27,37,.9), rgba(9,11,16,.9));
  border:1px solid rgba(228,199,120,.2);
  box-shadow:inset 0 1px 0 var(--hi), 0 6px 16px -4px rgba(0,0,0,.55);
  outline:0 solid var(--gold-line); outline-offset:2px;
  pointer-events:auto; touch-action:none; -webkit-user-select:none; user-select:none;
  -webkit-touch-callout:none; -webkit-tap-highlight-color:transparent;
  animation:favin .22s var(--ease-out);
  transition:border-color var(--dur-2), box-shadow var(--dur-2), scale var(--dur-2) var(--ease-out);
}
.favbar[hidden]{display:none}
@keyframes favin{from{opacity:0; scale:.92}}
.favbar button{
  width:28px; height:28px; display:grid; place-items:center; padding:0;
  border:0; border-radius:7px; background:transparent; color:var(--muted);
  cursor:pointer; transition:color var(--dur-1), background-color var(--dur-1), box-shadow var(--dur-1);
}
.favbar button svg{width:17px; height:17px; fill:none; stroke:currentColor;
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round}
.favbar button:hover{color:var(--gold-hi); background:var(--hover)}
.favbar button:active{background:var(--gold-soft)}
.favbar button:focus-visible{outline:none; box-shadow:var(--ring)}
.favbar button.armed{color:var(--gold); background:var(--gold-soft);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.32)}
/* The hold, arriving over GRAB_HOLD_MS - the same wait, and the same ring, as
   every other thing on this chart that can be picked up. */
.favbar.holding{outline-width:2px; transition:outline-width var(--grab-hold, 400ms) linear}
/* ...and once it is lifted it is in the hand: a gold edge, a deeper shadow,
   a breath larger, and the cursor says so. */
.favbar.lifted{
  scale:1.06; cursor:grabbing; border-color:var(--gold-line); outline-width:0;
  box-shadow:inset 0 1px 0 var(--hi), 0 16px 34px -6px rgba(0,0,0,.7), 0 0 0 3px var(--gold-soft);
}
.favbar.lifted button{cursor:grabbing}
@media (prefers-reduced-motion:reduce){
  .favbar, #drawPanel .tool.pinned .fav{animation:none}
}
#drawPanel .ctl.state{padding:5px 10px 7px; gap:6px}
#drawPanel .ctl.state .cur{margin-left:0; margin-right:auto; color:var(--muted)}

/* ---- the marks, as a ladder ----
   Where the sentence goes when the chart cannot hold it. The on-chart caption
   is bounded and elided on a phone because the candles are what the chart is
   for; this is the same words, in full, over a panel instead of over price -
   which is what keeps the ellipsis a fold rather than a loss (rule 1).

   Ordered by price, because that is the order they are in on the chart and the
   question is always "which line is that". A row is a link to the mark, not a
   setting: it is flat, it takes the full width, and pressing it hands the mark
   to the halo and gets out of the way.

   It scrolls at ten rows and no further. The budget allows ten claim marks and
   twenty of structure, so a scanned chart can put thirty rows in here, and a
   panel that is taller than the phone it is on has swallowed the chart the way
   the captions did. The ladder is the fix, not a second version of the
   problem. */
#drawPanel .marks{max-height:216px; overflow-y:auto; padding:1px 4px 3px}
#drawPanel .mk{
  display:flex; align-items:flex-start; gap:8px; width:100%; padding:5px 6px;
  background:transparent; border:0; border-radius:var(--r); cursor:pointer;
  color:var(--text); font:12px var(--sans); text-align:left;
}
#drawPanel .mk:hover{background:var(--hover)}
/* Held is the same statement the halo is making on the chart, in the list. */
#drawPanel .mk.on{background:var(--agent-soft); box-shadow:inset 0 0 0 1px rgba(110,169,255,.3)}
/* The mark's own colour, which is the only thing that ties a row to a line
   that carries no words at all. */
#drawPanel .mk i{flex:none; width:3px; height:16px; border-radius:2px}
/* The price is the anchor and it is set in the figures' own face, so a column
   of them lines up. */
#drawPanel .mk em{
  flex:none; min-width:54px; font:500 11px var(--num); font-variant-numeric:tabular-nums;
  font-style:normal; color:var(--muted);
}
/* THE SENTENCE WRAPS HERE, AND THAT IS THE ENTIRE POINT OF THE LIST. Eliding
   it again would make this a second copy of the problem it exists to solve -
   the chart elides because a caption over candles costs the candles, and a row
   in a scrolling panel costs nothing but a row. Two lines is the usual case;
   the scroller takes the rest.

   `dir=auto` on the row, not here: the analyst writes in whatever language the
   user does, and a Hebrew sentence in an LTR box scrambles its own numbers and
   punctuation - the same reason the overlay sets it. */
#drawPanel .mk span{flex:1; min-width:0; line-height:1.4; overflow-wrap:anywhere}
#drawPanel .mk b{
  flex:none; margin-top:1px; font:10.5px var(--sans); font-weight:500; color:var(--dim);
  background:var(--hover); border-radius:var(--r-xs); padding:2px 6px;
}

/* One finder, one line: what it looks for, where to look, and the verb. The
   count of what it left sits under it and only when there is one, so seven
   finders are seven lines rather than fourteen. */
#drawPanel .find{padding:1px 0}
#drawPanel .find > .row{display:flex; align-items:center; gap:6px; padding:4px 10px}
#drawPanel .find:hover > .row{background:var(--hover); border-radius:var(--r)}
#drawPanel .find .nm{flex:1; min-width:0; font-size:12.5px; color:var(--text);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
#drawPanel .find select{
  flex:none; width:62px; min-width:0; height:24px; padding:0 2px 0 6px;
  background:var(--well); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r-sm); font:11.5px var(--num);
}
#drawPanel .find .go{
  flex:none; height:24px; min-width:48px; padding:0 9px; cursor:pointer;
  background:var(--hover); color:var(--muted);
  border:1px solid transparent; border-radius:var(--r-sm); font:500 11.5px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1);
}
#drawPanel .find .go:hover{color:var(--gold); background:var(--gold-soft)}
#drawPanel .find .go:disabled{opacity:.5; cursor:default}
#drawPanel .find .more{
  flex:none; width:24px; height:24px; padding:0; line-height:1; cursor:pointer;
  background:none; color:var(--muted); border:1px solid transparent; border-radius:var(--r-sm);
  font:12px var(--sans); transition:color var(--dur-1), background-color var(--dur-1);
}
#drawPanel .find .more:hover{color:var(--text); background:var(--hover)}
#drawPanel .find .more.armed{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#drawPanel .find.open > .row{background:var(--raise); border-radius:var(--r) var(--r) 0 0}
/* What it left behind, and the one control that takes it back. */
#drawPanel .find .was{
  display:flex; align-items:center; gap:8px; padding:0 10px 5px 10px;
  font:10.5px var(--num); color:var(--dim);
}
#drawPanel .find .was span{margin-right:auto}
#drawPanel .find .was button{
  height:20px; padding:0 7px; cursor:pointer; background:var(--hover); color:var(--muted);
  border:1px solid transparent; border-radius:var(--r-sm); font:500 10.5px var(--sans);
}
#drawPanel .find .was button:hover{color:var(--warn); background:var(--warn-soft)}
#drawPanel .find .ctl{padding-left:10px}

/* THE THREE TICK ROWS, AND WHY THEY ARE IN THIS ORDER.
   Stacked upward from the time axis at 26px, 44px and 62px, nearest the axis
   first, ordered by how much each is about THIS instrument:

     26px  earnings  this company's own prints, four a year
     44px  news      this company's headlines
     62px  macro     the releases that move everything at once

   The earnings row sits on the axis because it is the one a person looks for
   on a single stock and the sparsest of the three - and because a row 62px up,
   where it started, is far enough into the candles to read as floating rather
   than as an axis annotation. The calendar is furthest away for the mirror
   reason: it is the only one of the three that says nothing about the symbol.

   ---------------- the news ticks ----------------
   A row just above the earnings one: one tick per bar that carries a headline.

   Not a library marker, and the position is the reason. A marker hangs off its
   own bar, so it sits on top of the price you are reading and can never be put
   at the axis - and the library never says where it drew one, so it cannot be
   clicked. A tick is a DOM node: it goes where it is told and it is its own hit
   target. */
/* THE THREE ROWS STACK FROM THE AXIS UP, AND THEY STACK ON TWO NUMBERS.
   `--tick-base` is where the LOWEST row sits above the bottom of #wrap - which
   is the date axis, not the bottom of the pane - and `--tick-step` is how far
   apart the rows are, which on a finger is also how big each row's target may
   be before two rows become one. They are variables because the phone wants
   different values of the same two numbers, not a different layout: three rows
   of marks along the axis, whatever the screen.

   AND AN EMPTY ROW RESERVES NOTHING. A row is 24px of a 272px pane on a phone
   in landscape, so a chart with no news held the calendar 24px higher than it
   needed to be for the whole of a coin's life. `#wrap` wears `t-earn` and
   `t-news` for the rows that actually have marks in them (stackTicks(),
   index.html) and each row is placed above the ones BELOW it that exist. Only
   two classes, because nothing is ever stacked on top of the calendar. */
:root{ --tick-base:26px; --tick-step:18px }
.earnticks,.newsticks,.macroticks{ bottom:var(--tick-base) }
#wrap.t-earn .newsticks,
#wrap.t-earn .macroticks,
#wrap.t-news .macroticks{ bottom:calc(var(--tick-base) + var(--tick-step)) }
#wrap.t-earn.t-news .macroticks{ bottom:calc(var(--tick-base) + 2 * var(--tick-step)) }

.newsticks{
  position:absolute; left:0; right:0; height:16px;
  z-index:2; pointer-events:none;
}
.newsticks .nwtick{
  position:absolute; bottom:0; transform:translateX(-50%);
  width:9px; height:9px; border-radius:50%;
  background:var(--gap); border:1px solid var(--gold-lo);
  pointer-events:auto; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
.newsticks .nwtick:hover{background:var(--gold-hi)}
/* The open one keeps its ring, so it is obvious which tick the panel belongs to. */
.newsticks .nwtick.on{
  background:var(--gold-hi); box-shadow:0 0 0 2px var(--gold-line);
}
/* Two or more headlines on one bar: the count rides inside the tick, which
   grows to hold it rather than overlapping its neighbours. */
.newsticks .nwtick b{
  font-family:var(--mono); font-size:8px; font-weight:600; line-height:1;
  color:#1a1408;
}
.newsticks .nwtick:has(b){width:14px; height:14px; border-radius:7px}

/* ---------------- the news reading ----------------
   The headlines sitting on one bar, and the body of whichever is opened.
   Its own layer over the chart, like the strike map's, so a click is answered
   without touching the compute path (rule 5).

   Unlike .oiread this one TAKES the pointer: it is a thing you click into and
   read, and the rows inside it are buttons. The layer stays transparent to the
   pointer and only the box opts back in, so the chart underneath is still
   pannable everywhere else - the same trick .o-board uses in the overlay. */
.newsread{position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden}
.newsread .nwbox{
  position:absolute; pointer-events:auto;
  width:min(420px, 62%); max-height:min(62%, 460px); overflow:auto;
  padding:0 0 4px; border:1px solid var(--line2); border-radius:var(--r);
  /* A flat colour, never a gradient: snapshot.js reads backgroundColor only,
     so anything else exports transparent. */
  background:var(--ovl-box); color:var(--text); box-shadow:var(--shadow-pop);
}
.newsread .nwtop{
  position:sticky; top:0; display:flex; justify-content:space-between;
  align-items:center; gap:8px; padding:6px 9px 5px;
  background:var(--ovl-tag); border-bottom:1px solid var(--line);
  font-size:11.5px; font-weight:600; letter-spacing:.01em; color:var(--muted);
}
.newsread .nwclose{cursor:pointer; font-size:15px; line-height:1; color:var(--dim); padding:0 2px}
.newsread .nwclose:hover{color:var(--text)}
.newsread .nwrow{padding:7px 9px; border-bottom:1px solid var(--line); cursor:pointer}
.newsread .nwrow:last-of-type{border-bottom:0}
.newsread .nwrow:hover{background:var(--ovl-tag)}
.newsread .nwrow.open{background:var(--ovl-tag)}
/* The agent writes in whatever language the user does, and a headline can come
   back in either - so the text direction follows the text, never the layout. */
.newsread .nwhead{font-size:12.5px; line-height:1.35; unicode-bidi:plaintext}
.newsread .nwmeta{
  margin-top:3px; font-family:var(--num); font-size:10.5px; color:var(--dim);
  font-variant-numeric:tabular-nums;
}
.newsread .nwbody{
  margin-top:7px; padding-top:7px; border-top:1px solid var(--line);
  font-size:12px; line-height:1.5; white-space:pre-wrap; color:var(--muted);
  unicode-bidi:plaintext; cursor:default;
}
.newsread .nwbody.load{color:var(--dim); font-style:italic}
.newsread .nwbody.err{color:var(--warn)}
.newsread .nwfoot{
  padding:5px 9px 2px; font-size:10.5px; color:var(--dim); text-align:center;
}

/* ---------------- the economic calendar ----------------
   A second tick row, sitting just above the news one.

   Its own row rather than sharing, because the two rows do not mean the same
   thing: news is only ever a record of what happened, and half of a calendar
   is AHEAD of the last candle. A row that mixed "this happened" with "this is
   due" would need the reader to check each mark before knowing which question
   it answers.

   Same gold family as the gaps and the frozen structure - measured, not the
   analyst's argument - and a diamond rather than a circle so the two rows are
   told apart at a glance and not by their height. */
.macroticks{
  position:absolute; left:0; right:0; height:16px;
  z-index:2; pointer-events:none;
}
.macroticks .mtick{
  position:absolute; bottom:0; transform:translateX(-50%) rotate(45deg);
  width:9px; height:9px; border-radius:2px;
  background:var(--gap); border:1px solid var(--gold-lo);
  pointer-events:auto; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
/* Hollow until it has printed. The one distinction that has to read instantly:
   a filled mark is a number you can go and look at, an empty one is a date. */
.macroticks .mtick.ahead{background:transparent; border-color:var(--gold-hi)}
/* Past its time with no figure published - neither a date nor a number, and it
   must not be dressed as either (rule 6). */
.macroticks .mtick.unreported{
  background:transparent; border-style:dashed; border-color:var(--dim);
}
/* Importance is the SIZE, not another colour: gold is already saying whose
   mark this is and a second hue would be a second meaning on one object. */
.macroticks .mtick.high{width:11px; height:11px}
.macroticks .mtick.low{width:7px; height:7px; opacity:.75}
.macroticks .mtick:hover{background:var(--gold-hi); border-color:var(--gold-hi)}
.macroticks .mtick.on{
  background:var(--gold-hi); box-shadow:0 0 0 2px var(--gold-line);
}
.macroticks .mtick b{
  font-family:var(--mono); font-size:8px; font-weight:600; line-height:1;
  color:#1a1408; transform:rotate(-45deg);
}
.macroticks .mtick:has(b){width:14px; height:14px; border-radius:3px}

/* The reading. Same layer discipline as .newsread - transparent to the
   pointer, only the box opts back in. */
.macroread{position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden}
.macroread .mbox{
  position:absolute; pointer-events:auto;
  width:min(440px, 66%); max-height:min(62%, 460px); overflow:auto;
  padding:0 0 4px; border:1px solid var(--line2); border-radius:var(--r);
  background:var(--ovl-box); color:var(--text); box-shadow:var(--shadow-pop);
}
.macroread .mtop{
  position:sticky; top:0; display:flex; justify-content:space-between;
  align-items:center; gap:8px; padding:6px 9px 5px;
  background:var(--ovl-tag); border-bottom:1px solid var(--line);
  font-size:11.5px; font-weight:600; letter-spacing:.01em; color:var(--muted);
}
.macroread .mclose{cursor:pointer; font-size:15px; line-height:1; color:var(--dim); padding:0 2px}
.macroread .mclose:hover{color:var(--text)}
.macroread .mev{padding:7px 9px; border-bottom:1px solid var(--line)}
.macroread .mev:last-of-type{border-bottom:0}
.macroread .mname{font-size:12.5px; line-height:1.35; unicode-bidi:plaintext}
.macroread .mwhen{
  margin-top:2px; font-family:var(--num); font-size:10.5px; color:var(--dim);
  font-variant-numeric:tabular-nums;
}
.macroread .mwhen .ny{font-style:normal; color:var(--dim)}
.macroread .mwhen .imp{color:var(--warn); letter-spacing:.06em}
/* One line per series: the name, then the figures. Grid rather than a table so
   the numbers stay in a column whatever the names do. */
/* THREE COLUMNS: the series, its figures, and how far the print came in from
   the forecast. The third is a column rather than the end of the second so the
   per cents line up down the panel - the eye finds a column without reading a
   word, which is the whole reason the figure moved out of the sentence. */
.macroread .mnum{
  display:grid; grid-template-columns:1fr auto auto; gap:2px 10px;
  margin-top:6px; padding-top:6px; border-top:1px solid var(--line);
  font-family:var(--num); font-size:11px; font-variant-numeric:tabular-nums;
}
.macroread .mnum .k{color:var(--muted); font-family:var(--sans); font-size:11.5px}
/* The figures line grew a third piece - the print, the forecast, the gap
   between them - so it wraps BETWEEN pieces and never inside one. Nowrap on
   the cell put the comparison off the right edge of a 257px box on a phone. */
.macroread .mnum .v{color:var(--text); text-align:right; white-space:normal}
.macroread .mnum .v > *{white-space:nowrap}
.macroread .mnum .v .cons{color:var(--dim)}
.macroread .mnum .v i{font-style:normal; color:var(--dim)}
/* ABOVE THE FORECAST OR BELOW IT, IN THE CANDLES' OWN TWO TEMPERATURES (Dor,
   2026-09-15) - the direction, written the way a price move is written. The
   PRINT is deliberately not coloured any more: it wore the source's
   better/worse verdict, and on every series where less is better the two
   disagree, so one line would have carried two colours meaning opposite
   things. A gap of nothing is neither colour. */
.macroread .mnum .g{
  text-align:right; white-space:nowrap; color:var(--dim); font-weight:600;
}
.macroread .mnum .g.up{color:var(--up)}
.macroread .mnum .g.dn{color:var(--down)}
/* A release whose time has passed and whose number never came. Said plainly,
   because a blank here reads as zero. */
.macroread .mnone{color:var(--dim); font-style:italic; font-size:11.5px; margin-top:5px}
.macroread .mfoot{
  padding:5px 9px 2px; font-size:10.5px; color:var(--dim); text-align:center;
}

/* ---------------- the earnings schedule ----------------
   A third tick row, above the calendar's, and the reading that opens from it.

   Its own row, and this one earns it on colour alone. The other two rows are
   gold because what they mark is measured rather than argued - a headline
   landed, a release printed. An earnings mark carries a VERDICT, and the
   chart's existing word for better and worse is the candles' own green and
   red. Putting that hue in the calendar's row would give one row two meanings;
   putting a CPI print under a beat/miss legend would be worse.

   Four states, not two, because two of them are neither a beat nor a miss: a
   print nobody published a consensus for - which is most of a decade of them -
   and a date that has not arrived. Both are drawn honestly rather than being
   coloured in or hidden (rule 6).

   A bar rather than a circle or a diamond, so the three rows are told apart at
   a glance by SHAPE and not by having to compare their heights. */
.earnticks{
  position:absolute; left:0; right:0; height:16px;
  z-index:2; pointer-events:none;
}
/* A BADGE WITH A LETTER IN IT, not a bare swatch.
   The first version was an 8x12 sliver, and on a real chart it was invisible:
   it shares this strip with the calendar's diamonds and it is drawn over the
   volume histogram, so a small coloured rectangle reads as one more bar.
   Four a year is too few to find by scanning. The letter is what makes it
   findable at a glance and the colour is what makes it readable - the same
   division of labour the news tick's count already uses. */
.earnticks .etick{
  position:absolute; bottom:1px; transform:translateX(-50%);
  width:15px; height:15px; border-radius:3px;
  background:var(--dim); border:1px solid var(--dim);
  pointer-events:auto; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--mono); font-size:9px; font-weight:700; line-height:1;
  color:#0d1117;
}
/* The two the colour exists for. */
.earnticks .etick.beat{background:var(--up); border-color:var(--up)}
.earnticks .etick.missed{background:var(--down); border-color:var(--down)}
/* Scored, and it landed on the number. Rare and real, so it is neither. */
.earnticks .etick.inline{background:var(--muted); border-color:var(--muted)}
/* Reported, never scored: the source publishes four quarters of consensus and
   the chart marks forty prints. Drawn in the neutral ink at half weight - it
   is a print that happened, and nothing is claimed about it. The two hollow
   states carry the letter in their own border colour, since there is no fill
   behind it to sit on. */
.earnticks .etick.reported{
  background:var(--panel); border-color:var(--muted); color:var(--muted);
}
/* The date still ahead. Hollow, dashed, in the user's gold rather than in a
   verdict colour, because there is no verdict to have: it is a date. */
.earnticks .etick.ahead{
  background:var(--panel); border-color:var(--gold-hi); border-style:dashed;
  color:var(--gold-hi);
}
/* An operational update (deliveries, traffic, a preliminary release): a dated
   fact about the company, NOT a print, and never scored - no consensus is
   published for it. A solid neutral badge with its own letter, U, so it is
   neither a verdict colour nor the hollow "reported" print it would be taken for. */
.earnticks .etick.update{background:var(--dim); border-color:var(--dim); color:var(--text)}
.earnticks .etick:hover{filter:brightness(1.35)}
.earnticks .etick.on{box-shadow:0 0 0 2px var(--gold-line)}

/* The reading. Same layer discipline as .newsread and .macroread - the layer
   is transparent to the pointer and only the box opts back in. */
.earnread{position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden}
.earnread .ebox{
  position:absolute; pointer-events:auto;
  width:min(440px, 66%); max-height:min(62%, 460px); overflow:auto;
  padding:0 0 4px; border:1px solid var(--line2); border-radius:var(--r);
  /* A flat colour, never a gradient: snapshot.js reads backgroundColor only. */
  background:var(--ovl-box); color:var(--text); box-shadow:var(--shadow-pop);
}
.earnread .etop{
  position:sticky; top:0; display:flex; justify-content:space-between;
  align-items:center; gap:8px; padding:6px 9px 5px;
  background:var(--ovl-tag); border-bottom:1px solid var(--line);
  font-size:11.5px; font-weight:600; letter-spacing:.01em; color:var(--muted);
}
/* The verdict, beside the quarter it belongs to. The one word the panel is
   opened for, so it is the one thing in the header that is not dim. */
.earnread .ever{
  margin-left:6px; padding:1px 6px; border-radius:3px;
  background:var(--ovl-tag); border:1px solid var(--line); color:var(--muted);
  font-family:var(--num); font-size:10px; letter-spacing:.03em;
}
.earnread .ever.up{color:var(--up); border-color:rgba(47,208,138,.45)}
.earnread .ever.dn{color:var(--down); border-color:rgba(255,93,115,.45)}
.earnread .eclose{cursor:pointer; font-size:15px; line-height:1; color:var(--dim); padding:0 2px}
.earnread .eclose:hover{color:var(--text)}
.earnread .ewhen{
  padding:7px 9px 0; font-family:var(--num); font-size:10.5px; color:var(--dim);
  font-variant-numeric:tabular-nums; line-height:1.45;
}
/* One line per figure: what it is, then the number.
   `max-content minmax(0,1fr)` and not the calendar's `1fr auto`, which is the
   shape a figure with a QUALIFIER needs. These values are not "0.4%" - they
   are "$1.91 vs $1.88 expected +$0.03 (+1.6%)", and under `1fr auto` with a
   nowrap value the figure grew past the right edge and clipped while the
   label column was squeezed until "EPS (GAAP, filed)" wrapped over three
   lines. The label takes exactly the width of its own text and never wraps;
   the figures take the rest and wrap inside it. */
.earnread .enum{
  display:grid; grid-template-columns:max-content minmax(0, 1fr); gap:4px 12px;
  margin:7px 9px 0; padding-top:7px; border-top:1px solid var(--line);
  font-family:var(--num); font-size:11px; font-variant-numeric:tabular-nums;
}
.earnread .enum .k{
  color:var(--muted); font-family:var(--sans); font-size:11.5px; white-space:nowrap;
}
.earnread .enum .v{
  color:var(--text); text-align:right; min-width:0;
  overflow-wrap:break-word; line-height:1.45;
}
.earnread .enum .v .cons{color:var(--dim)}
.earnread .enum .v .up, .earnread .enum .up{color:var(--up)}
.earnread .enum .v .dn, .earnread .enum .dn{color:var(--down)}
/* A print with no consensus behind it. Said in words and across both columns,
   because an empty figure column here would read as a zero surprise. */
.earnread .enone{
  grid-column:1 / -1; color:var(--dim); font-style:italic;
  font-family:var(--sans); font-size:11.5px; line-height:1.4;
}
/* An operational update, opened: the company's own words and tables. Its own
   block above the figure grid, because a release is prose and a table rather
   than label-value pairs. */
.earnread .eupd{
  margin:0; padding:0;
  white-space:normal; overflow-x:auto;
  /* It sits inside `.enum`'s label/figure grid: across both columns, and
     allowed to be narrower than its content, or the max-content label column
     takes the whole release and nothing wraps. */
  grid-column:1 / -1; min-width:0;
}
.earnread .elead{font-size:11.5px; line-height:1.45; color:var(--text); margin:0 0 6px; overflow-wrap:anywhere}
.earnread .elead a{color:var(--agent)}
.earnread .etab{
  width:100%; border-collapse:collapse; margin:4px 0 8px;
  font-family:var(--num); font-size:11px; font-variant-numeric:tabular-nums;
}
.earnread .etab th{
  font-family:var(--sans); font-weight:600; font-size:10.5px; color:var(--muted);
  text-align:right; padding:3px 4px; border-bottom:1px solid var(--line); vertical-align:bottom;
  white-space:normal; overflow-wrap:break-word;
}
.earnread .etab td{text-align:right; padding:3px 4px; vertical-align:top; border-bottom:1px solid var(--line)}
.earnread .etab td.elab{text-align:left; color:var(--muted); font-family:var(--sans)}
.earnread .etab td{white-space:nowrap}
.earnread .etab .echg{display:block; font-size:10px; color:var(--dim)}
.earnread .etab .echg .up{color:var(--up)}
.earnread .etab .echg .dn{color:var(--down)}
.earnread .eupd .enone{margin:0 0 6px}
.earnread .efoot{
  padding:7px 9px 2px; font-size:10.5px; color:var(--dim); text-align:center;
  line-height:1.4;
}

/* The standing line under the analyst's input. Quiet on purpose: it must be
   readable and must not compete with the reading above it - a notice set as
   loud as the content is one the eye learns to skip. */
.cdisc {
  padding: 4px 10px 7px;
  font-size: 10px;
  line-height: 1.35;
  color: var(--muted, #7b8496);
  text-align: center;
  letter-spacing: .01em;
}

/* ============================================================================
   ALERTS - the window behind the bell
   ============================================================================
   Same frame as #report and #cal (joined to their selector lists above rather
   than copied), so this block is only what is different: a list of rows, two
   arming forms, and the one line that says whether anything is watching.

   THAT LINE IS THE POINT OF THE WHOLE WINDOW. It sits in the title bar, where
   it cannot be scrolled away from, and it has three tones - because "3 armed"
   and "3 armed and nothing has looked at them since 14:02" must not look
   alike (rule 1, with the stakes raised).
   ============================================================================ */
#alerts .win{width:min(720px, calc(100% - 44px)); max-height:min(78vh, 760px)}
#alerts .wbar{flex-wrap:wrap; height:auto; min-height:46px; padding:7px 46px 7px 16px; row-gap:6px}
#alerts .wbar .x{position:absolute; top:8px; right:12px; margin:0}
/* THE TITLE BAR'S TWO READOUTS ARE PILLS WITH A LAMP (the look pass): a dot
   in the tone, the words in a quiet ink, so the bar says whether anything is
   watching without shouting it in a line of orange capitals. */
#alerts .watch, #alerts .dev{
  display:inline-flex; align-items:center; gap:7px; height:24px; padding:0 10px;
  border-radius:999px; background:var(--hover); color:var(--doc-muted);
  font:500 11.5px var(--num); letter-spacing:.005em; white-space:nowrap;
  max-width:100%; overflow:hidden; text-overflow:ellipsis;
}
#alerts .watch::before, #alerts .dev::before{content:""; flex:none; width:6px; height:6px; border-radius:50%; background:var(--doc-dim)}
#alerts .watch.ok::before{background:var(--up); box-shadow:0 0 0 3px var(--up-soft)}
#alerts .watch.warn, #alerts .dev.warn{color:var(--warn); background:var(--warn-soft)}
#alerts .watch.warn::before, #alerts .dev.warn::before{background:var(--warn)}
#alerts .watch.bad{color:var(--down); background:var(--down-soft)}
#alerts .watch.bad::before{background:var(--down)}
#alerts .dev:not(.warn)::before{background:var(--up)}
#alerts .dev:empty{display:none}
#alerts .wbody{overflow:auto; padding:0}
#alerts .adoc{display:flex; flex-direction:column; padding:4px 0 16px}

/* A GROUP IS A HEADING AND A COUNT, sentence case, with a hairline after it -
   the windows' own rule rather than a box around the list. */
#alerts .grp{
  display:flex; align-items:center; gap:9px; padding:20px 16px 10px;
  font:600 13px var(--sans); letter-spacing:-.003em; color:var(--doc-text);
}
#alerts .grp::after{content:""; flex:1; height:1px; background:var(--doc-line); margin-left:4px}
#alerts .grp .slot{font:500 11px var(--num); color:var(--doc-muted)}
#alerts .anil, #alerts .ahint{padding:4px 16px 8px; font:12px/1.6 var(--sans); color:var(--doc-dim)}
#alerts .anil{margin:2px 12px 4px; padding:14px 14px; border-radius:var(--r); background:var(--panel)}
#alerts .ahint b{color:var(--doc-accent); font-weight:600}
#alerts .awarn{
  margin:12px 12px 0; padding:10px 12px 10px 14px; border-radius:var(--r);
  border:0; background:var(--warn-soft);
  box-shadow:inset 2px 0 0 var(--warn);
  font:12.5px/1.55 var(--sans); color:var(--doc-text);
}
#alerts .awarn b{color:var(--warn); font-weight:600}
#alerts .awarn button{
  margin-left:8px; height:24px; padding:0 10px; border-radius:var(--r-sm);
  border:0; background:rgba(240,160,60,.16);
  color:var(--warn); font:500 11.5px var(--sans); cursor:pointer;
  transition:background-color var(--dur-1);
}
#alerts .awarn button:hover{background:rgba(240,160,60,.26)}

/* A ROW IS A CARD: a claim and its state. The leading edge is the state - one
   glance down the list says what is live without reading a word - and the
   card lifts under the pointer, because the whole of it opens its chart. */
#alerts .arow{
  --edge:transparent;
  position:relative; display:flex; flex-direction:column; gap:5px;
  margin:0 12px 6px; padding:11px 14px 10px 16px; border-radius:var(--r);
  background:var(--panel); box-shadow:inset 2px 0 0 var(--edge);
  transition:background-color var(--dur-1);
}
/* THE CURSOR IS THE ONLY THING THAT SAYS A LINE CAN BE PICKED UP. An armed
 * alert is the one mark on this chart that is not a drawing and so is not
 * reachable through the selection halo - without this it is a line that happens
 * to move when you drag it, which nobody discovers. `ns-resize` because the
 * gesture is vertical: an alert is a PRICE, and dragging it sideways would be
 * dragging it through time, which it does not have. */
/* THE COUNT OF WHAT IS ARMED, ON THE HEADER BUTTON.
 *
 * Rule 1 one layer out from the chart: the dashed line on the candles says
 * *this price is watched* only while you are on that instrument, so alerts on
 * three symbols were invisible from all three. The badge is in the alert's own
 * magenta, so the count and the lines it counts are one colour - and it turns
 * the candles' own red for a FIRED alert, which is the one state that is about
 * something that has already happened rather than something being waited for.
 *
 * Absolutely positioned and `pointer-events:none`, so it cannot shift the
 * button's box or take a click meant for it: the header gives way in order
 * (fitHeader()) and a badge that changed its button's width would move every
 * control to its right each time an alert was armed. */
#alertsBtn{position:relative}
#alertsBtn .abadge{
  position:absolute; top:2px; right:2px; min-width:13px; height:13px;
  padding:0 3px; border-radius:7px; pointer-events:none;
  background:var(--alert); color:#12060f;
  font:700 9px/13px var(--sans); font-variant-numeric:tabular-nums;
  text-align:center; font-style:normal;
}
#alertsBtn .abadge.fired{background:var(--down); color:#2a0710}
#alertsBtn .abadge[hidden]{display:none}

/* THE FLAG ON AN ARMED MARK. A tag carries the alert's colour through
   `style.color` already; this gives it a ground and a border so the glyph and
   its price over candles are still a chip rather than floating characters, and
   keeps it narrow - it is one mark's state, not a caption.

   AND IT IS THE SECOND THING IN THIS LAYER THAT TAKES A PRESS, after the
   board, for the same reason the board does: it is not a caption ON something,
   it is a control. Tapping it opens the `A` window, which is the only place a
   mark-backed alert can be read in full or disarmed - the mark's own halo
   arms, and nothing else on the chart leads back to the row. The press is kept
   off the candles by `.aflag` being in PANE_CHROME_BASE, exactly as the board
   is listed at each of the six gestures that hit-test the pane by coordinate. */
.o-label.aflag{
  background:var(--ovl-tag); border:1px solid var(--alert)!important;
  padding:0 4px; font-weight:700;
  pointer-events:auto; cursor:pointer;
}

/* ONE ALERT, AS A READING — the smallest of the boxes on this chart, and that
   is the requirement rather than a side effect. A press on an armed line or on
   a mark's flag used to open `#alerts`: every alert on every instrument, a
   watcher line and a new-alert form, over the candles, to answer *what is this
   one*. It is built like `.earnread` and its neighbours, by SELECTOR LIST
   where it can be and with its own few rules where it cannot, and it holds
   four short lines at most — the price and the side, the state, the note, and
   how far away the price is with what that was measured from. Everything else
   is still one tap away in the `A` window. */
.alertread{position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden}
.alertread .abox{
  position:absolute; pointer-events:auto;
  width:max-content; max-width:min(260px, 62%);
  padding:0 0 6px; border:1px solid var(--alert); border-radius:7px;
  /* A flat colour, never a gradient: snapshot.js reads backgroundColor only. */
  background:var(--ovl-box); color:var(--text);
  box-shadow:0 6px 18px rgba(0,0,0,.5);
}
.alertread .atop{
  display:flex; align-items:center; gap:8px; padding:6px 9px 5px;
  background:var(--ovl-tag); border-bottom:1px solid var(--line);
}
/* The number the box is opened for, in the alert's own colour and the chart's
   own precision — the one thing here that is not dim. */
.alertread .atop b{
  font:600 12.5px var(--num); font-variant-numeric:tabular-nums;
  color:var(--alert); white-space:nowrap;
}
.alertread .ast{
  margin-left:auto; padding:1px 5px; border-radius:3px;
  border:1px solid var(--line); background:var(--ovl-box);
  font:10px var(--sans); letter-spacing:.05em; text-transform:uppercase;
  color:var(--dim);
}
/* Something that has ALREADY happened is not the same kind of fact as
   something being waited for, and the candles' own green is this chart's word
   for it (the earnings row makes the same exception). */
.alertread .ast.fired{color:var(--up); border-color:rgba(47,208,138,.45)}
.alertread .aclose{cursor:pointer; font-size:15px; line-height:1; color:var(--dim); padding:0 2px}
.alertread .aclose:hover{color:var(--text)}
.alertread .anote2{padding:7px 9px 0; font:11.5px/1.45 var(--sans); color:var(--text)}
/* The distance, and what it was measured from and when (rule 3): a distance
   with no basis cannot be reconciled with the price on the axis above it. */
.alertread .ameta2{
  padding:6px 9px 0; font:10.5px/1.45 var(--num); color:var(--dim);
  font-variant-numeric:tabular-nums;
}
.alertread .aby{padding:5px 9px 0; font:10px var(--sans); color:var(--muted)}

#wrap.onalert{cursor:ns-resize}

#alerts .arow.on{--edge:var(--alert)}
#alerts .arow.fired{--edge:var(--up)}
#alerts .arow.off .amain, #alerts .arow.off .anote, #alerts .arow.off .ameta{opacity:.55}
#alerts .arow[data-go]{cursor:pointer}
#alerts .arow[data-go]:hover{background:var(--raise)}
#alerts .amain{display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; min-width:0}
#alerts .amain b{font:600 14px var(--sans); letter-spacing:-.005em; color:var(--doc-text)}
#alerts .amain .what{font:600 14px var(--num); color:var(--doc-accent)}
#alerts .amain .side{font:12.5px var(--sans); color:var(--doc-muted)}
#alerts .amain .by{
  align-self:center; font:500 10.5px var(--sans); font-style:normal;
  color:var(--agent); background:var(--agent-soft); border-radius:999px; padding:1px 8px;
}
/* The card's pill: how far from firing, or the state when it is not armed. */
#alerts .apill{
  align-self:center; margin-left:auto; flex:none;
  font:500 11.5px var(--num); font-variant-numeric:tabular-nums;
  padding:2px 9px; border-radius:999px; background:var(--hover); color:var(--doc-muted);
}
#alerts .apill.away{color:var(--alert); background:var(--alert-soft)}
#alerts .apill.away.near{font-weight:650; box-shadow:inset 0 0 0 1px var(--alert-line)}
#alerts .arow.fired .apill{color:var(--up); background:var(--up-soft)}
#alerts .anote{font:12.5px/1.5 var(--sans); color:var(--doc-muted); unicode-bidi:plaintext; text-align:start}
#alerts .afoot{display:flex; align-items:center; gap:6px 12px; flex-wrap:wrap; margin-top:2px}
#alerts .ameta{display:flex; flex-wrap:wrap; gap:4px 12px; flex:1 1 220px; min-width:0; font:11.5px var(--num); color:var(--doc-dim)}
#alerts .ameta .fired{color:var(--up)}
#alerts .ameta .bad{color:var(--down); font-style:normal}
/* HOW FAR IT IS FROM FIRING, which is the number the window is usually being
   asked for. Set in the alert's own colour so it reads as belonging to the
   mark on the chart rather than to the row's chrome, and `near` is a weight
   change rather than a second colour: the threshold is a hint about where the
   eye should go, and inventing a red for it would be the window making a claim
   about what price is going to do. */
/* A SUB-HEADING INSIDE `Other instruments`, which is a group of groups. Lighter
   than `.grp` on purpose - it is a level down, and two headings of equal weight
   read as two lists rather than one list with parts. */
#alerts .sgrp{
  display:flex; align-items:baseline; gap:8px; padding:12px 16px 6px 18px;
  font:600 12px var(--sans); color:var(--doc-accent);
}
#alerts .sgrp .slot{font:500 11px var(--num); color:var(--doc-dim)}
/* RESTING CONTROLS (the look pass): no fill until the hand is on one. */
#alerts .aacts{display:flex; gap:2px; margin-left:auto}
#alerts .aacts button{
  height:26px; padding:0 9px; border-radius:var(--r-sm); cursor:pointer;
  border:0; background:transparent;
  color:var(--doc-muted); font:500 11.5px var(--sans);
  transition:color var(--dur-1), background-color var(--dur-1);
}
#alerts .aacts button:hover{color:var(--doc-text); background:var(--press)}
#alerts .aacts button.rm:hover{color:var(--down); background:var(--down-soft)}

/* The two arming forms. One row that wraps, because at 390px it has to. */
#alerts .anew{display:flex; flex-wrap:wrap; gap:6px; padding:2px 16px 4px; align-items:center}
#alerts .anew input[type=number], #alerts .anew input[type=text], #alerts .anew .nn{
  height:30px; padding:0 9px; border-radius:var(--r); min-width:0;
  border:1px solid var(--line); background:var(--well);
  color:var(--doc-text); font:12.5px var(--num); outline:none;
  transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
#alerts .anew input:hover, #alerts .anew .nn:hover{border-color:var(--line2)}
#alerts .anew input:focus, #alerts .anew .nn:focus{border-color:var(--gold-line); box-shadow:var(--ring)}
#alerts .anew .np{width:120px}
#alerts .anew .nn{flex:1 1 160px; font-family:var(--sans)}
/* The global rule is `select{min-width:190px}`, which is wider than three of
   these side by side - the calendar's title bar learned this the hard way. */
#alerts .anew select{
  height:30px; min-width:0; padding:0 8px; border-radius:var(--r);
  border:1px solid var(--line); background:var(--well);
  color:var(--doc-text); font:12px var(--sans); cursor:pointer;
}
#alerts .anew .nr{display:flex; align-items:center; gap:5px; font:11.5px var(--sans); color:var(--doc-muted)}
#alerts .anew .go{
  height:30px; padding:0 14px; border-radius:var(--r-sm); cursor:pointer;
  border:1px solid transparent; background:var(--gold-soft);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
  color:var(--gold); font:600 12px var(--sans);
  transition:background-color var(--dur-1);
}
#alerts .anew .go:hover:not(:disabled){background:rgba(228,199,120,.2)}
#alerts .anew .go:disabled{opacity:.5; cursor:default}

/* THE HALO'S SECOND CHIP. Blue, not the halo's warn colour: removing a mark is
   destructive and arming one is not, and two identical chips a thumb apart is
   how the wrong one gets pressed. */
.sel button.arm{
  position:absolute; transform:translate(-50%,-50%);
  width:30px; height:30px; padding:0; border-radius:50%;
  display:grid; place-items:center; cursor:pointer; pointer-events:auto;
  font:15px/1 var(--sans); color:var(--agent);
  background:linear-gradient(var(--agent-soft),var(--agent-soft)), var(--bg);
  border:1px solid var(--agent-line);
}
.sel button.arm:active{background:var(--agent-line)}


/* ============================================================================
   WHAT THIS DOES, AND HOW TO REACH A PERSON
   ============================================================================
   The first screen a brand-new account lands on, and the place the support
   address and the bug report live. It borrows #report/#cal/#alerts' frame by
   SELECTOR LIST (joined above, never copied), so there is one window style on
   this page and one set of --doc-* tokens behind it; what is here is only what
   is different.

   IT IS `fixed` AND ABOVE THE WHOLE SPINE, which the other three deliberately
   are not. They are windows ABOUT the chart and cover the pane; this one is
   about the product, and for an account with nothing in it there is no chart
   worth looking at behind it yet. 8000 sits under #signin's 9000 - who you are
   is asked before what this is. */
#help{position:fixed; z-index:8000}
#help[hidden]{display:none}
#help .win{font:13px/1.5 var(--sans)}
#help .wbody{overflow:auto; padding:24px 28px 28px}
#help .hpane{display:flex; flex-direction:column; gap:16px}

/* ---- inside the window: ONE VOICE, THREE WEIGHTS ----
   A window that explains a product is a page of type, and the hierarchy is
   made with type: the promise at the title size, the working at body size, a
   section named by a small sentence-case heading - and rules between things
   rather than boxes round them. Gold is kept for what the user does here (the
   one primary button, the lit choice, focus) and for nothing that is merely
   read. The tabs in the title bar are this window's own places: resting
   controls, and the one that is open wears the page's single "on". */
#help .wbar .htab{
  border:0; background:transparent; color:var(--doc-muted); border-radius:var(--r-sm);
  font-weight:500; transition:color var(--dur-1), background var(--dur-1);
}
#help .wbar .htab:hover{color:var(--doc-text); background:var(--hover); border:0}
#help .wbar .htab:active{background:var(--press)}
#help .wbar .htab.on{
  color:var(--gold); background:var(--gold-soft); border:0;
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
}
#help .wbar .htab:focus-visible{outline:1px solid var(--gold-line); outline-offset:2px}

#help .hlead{margin:0; font-size:15px; line-height:1.6; color:var(--doc-text); max-width:60ch; text-wrap:pretty}
#help .hlead b{color:var(--doc-text); font-weight:600}
#help .hlead2{margin:0; font-size:13px; line-height:1.6; color:var(--doc-muted); max-width:68ch}
#help .hlead2 b{color:var(--doc-text); font-weight:600}
/* A section rule that reads as a label rather than as a heading: the window
   already has a title bar, and two levels of heading in 400 words is one.
   Sentence case, which is how its text is written; it sits close to what it
   names and away from what came before. */
#help .hh{
  margin:10px 0 -8px; font:600 var(--fs-meta)/1.3 var(--sans); letter-spacing:.01em;
  color:var(--doc-muted);
}

/* WHAT IT DOES, IN THREE COLUMNS OF TYPE rather than three cards: a hairline
   above and below the set, a hairline between, and the titles in the page's
   white. They are three claims of equal weight, so nothing ranks them. */
#help .hgrid{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:0;
  margin:4px 0 2px; border-top:1px solid var(--doc-line); border-bottom:1px solid var(--doc-line);
}
#help .hcard{
  border:0; border-radius:0; padding:16px 20px 18px 0;
  background:transparent; min-width:0;
}
#help .hcard + .hcard{padding-left:20px; border-left:1px solid var(--doc-line)}
#help .hcard h3{margin:0 0 6px; font:600 13px/1.35 var(--sans); color:var(--doc-text)}
#help .hcard p{margin:0; font-size:12.5px; line-height:1.6; color:var(--doc-muted)}

/* The suggested questions are BUTTONS: reading what you could ask and asking it
   are one gesture away from each other, and the window is where a stranger is
   standing when the thought arrives. Set as the user's own words - the
   opening quote in the user's gold - on rows divided by hairlines, lit under
   the pointer rather than boxed at rest. */
#help .hask{
  margin:0; padding:0; list-style:none; display:flex; flex-direction:column; gap:0;
  border-top:1px solid var(--doc-line);
}
#help .hask li{border-bottom:1px solid var(--doc-line)}
#help .hask button{
  width:100%; text-align:left; padding:10px 12px 10px 4px; margin:2px 0; border-radius:var(--r); cursor:pointer;
  border:0; background:transparent; color:var(--doc-text);
  font:13px/1.45 var(--sans); display:flex; align-items:baseline; gap:10px;
  transition:background var(--dur-1), color var(--dur-1);
}
#help .hask button::before{
  content:"\201C"; flex:none; width:12px; text-align:center;
  color:var(--gold); opacity:.55; font-size:17px; line-height:1; transform:translateY(3px);
  transition:opacity var(--dur-1);
}
#help .hask button:hover{background:var(--hover)}
#help .hask button:hover::before{opacity:1}
#help .hask button:active{background:var(--press)}

#help .hnote{
  margin:0; padding-top:14px; border-top:1px solid var(--doc-line);
  font-size:var(--fs-meta); line-height:1.5; color:var(--doc-dim);
}
/* The question list already ends on a rule; a second one under it is two
   lines saying one thing. */
#help .hask + .hnote{border-top:0; padding-top:0}

#help .haddr{margin:0; display:flex; align-items:center; gap:12px; flex-wrap:wrap}
#help .haddr a{
  font:500 15px var(--sans); letter-spacing:.005em; color:var(--doc-text); text-decoration:none;
  word-break:break-all; user-select:all;
  border-bottom:1px solid var(--gold-line); padding-bottom:1px;
  transition:border-color var(--dur-1), color var(--dur-1);
}
#help .haddr a:hover{color:var(--gold-hi); border-bottom-color:var(--gold)}
/* A small verb beside what it acts on - `copy` by the address, `sign out` by
   the account. A breath of fill says it is pressable without drawing a box. */
#help .hmini{
  height:24px; padding:0 9px; border-radius:var(--r-sm); cursor:pointer; flex:none;
  border:0; background:var(--hover); color:var(--doc-muted);
  font:500 11px var(--sans); transition:color var(--dur-1), background var(--dur-1);
}
#help .hmini:hover{color:var(--doc-text); background:var(--press)}
/* Which of the two a report is - a bug, or a wish. The chosen one takes the
   user's gold, as a field with the caret does; the other stays a breath of fill. */
#help .hkind{display:flex; gap:6px; margin:0 0 8px}
#help .hkind .hmini.on{color:var(--gold-hi); background:var(--press)}

/* A FIELD IS A WELL: a step below the window, a hairline round it, and the
   user's gold only once it has the caret. */
#help textarea{
  width:100%; box-sizing:border-box; resize:vertical; min-height:74px;
  background:var(--well); color:var(--doc-text); border:1px solid var(--line);
  border-radius:var(--r); padding:10px 12px; font:13px/1.5 var(--sans); outline:none;
  unicode-bidi:plaintext; transition:border-color var(--dur-1), box-shadow var(--dur-1);
}
#help textarea:hover{border-color:var(--line2)}
#help textarea:focus{border-color:var(--gold-line); box-shadow:var(--ring)}
#help textarea::placeholder{color:var(--doc-dim)}

/* What travels with the report, shown before anything is sent. Folded, because
   it is eight lines of machine facts under a box somebody is typing into - and
   open one click away, because a report that attaches what the reporter cannot
   read is a report they cannot agree to. */
#help .hctx summary{cursor:pointer; font-size:var(--fs-meta); color:var(--doc-muted); padding:2px 0}
#help .hctx summary:hover{color:var(--doc-text)}
#help .hctx pre{
  margin:8px 0 0; padding:10px 12px; border-radius:var(--r); overflow-x:auto;
  background:var(--well); border:1px solid var(--line);
  font:10.5px/1.6 var(--mono); color:var(--doc-muted); white-space:pre; max-height:210px;
}

/* ONE PRIMARY, AND IT IS GOLD WITHOUT SHOUTING: flat gold with the lit top
   edge a raised thing wears - no sheen, no glow - beside a secondary that is a
   breath of fill. The same two the composer uses, so a gold button means one
   thing across the page. */
#help .hacts{display:flex; align-items:center; gap:10px; flex-wrap:wrap}
#help .hacts button{
  height:34px; padding:0 14px; border-radius:var(--r-sm); cursor:pointer; flex:none;
  border:0; background:var(--hover); color:var(--doc-text);
  font:500 12.5px var(--sans); transition:background var(--dur-1), color var(--dur-1);
}
#help .hacts button:hover{background:var(--press)}
#help .hacts button.gold, #help .hfoot button.gold{
  background:var(--gold); color:#1a1408; border:0; font-weight:600;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3);
}
#help .hacts button.gold:hover, #help .hfoot button.gold:hover{background:var(--gold-hi)}
#help .hacts button.gold:disabled, #help .hfoot button.gold:disabled{opacity:.45; cursor:default; background:var(--gold)}
#help .hsaid{font-size:var(--fs-meta); color:var(--doc-accent-dim); min-height:14px}

/* YOUR ANALYST: three subjects, three levels each, and the levels are ONE
   SEGMENTED CONTROL per subject - a well with a hairline, the three words
   inside it without borders, and the chosen one wearing the page's single
   "on" (gold word, a breath of gold, the inset). A subject name is the thing
   being read, so the control wraps under it rather than squeezing it. */
#help .hread{display:flex; flex-direction:column; gap:14px}
#help .hrow{display:flex; align-items:center; gap:10px 16px; flex-wrap:wrap}
#help .hrname{flex:1 1 180px; min-width:0; font:500 13px/1.4 var(--sans); color:var(--doc-text)}
#help .hrby{font-size:11px; font-weight:400; color:var(--doc-dim); white-space:nowrap}
#help .hchips{
  display:flex; gap:2px; flex-wrap:wrap; padding:2px;
  background:var(--well); border-radius:var(--r); box-shadow:inset 0 0 0 1px var(--line);
}
#help .hchip{
  height:28px; padding:0 12px; border-radius:var(--r-sm); cursor:pointer; flex:none;
  border:0; background:transparent; color:var(--doc-muted);
  font:500 12px var(--sans); white-space:nowrap;
  transition:color var(--dur-1), background var(--dur-1);
}
#help .hchip:hover:not(:disabled){color:var(--doc-text); background:var(--hover)}
#help .hchip.on{
  color:var(--gold); background:var(--gold-soft);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
}
#help .hchip:disabled{cursor:default; opacity:.5}
#help .hchip.on:disabled{opacity:1}
#help .hchip:focus-visible{outline:2px solid var(--doc-accent); outline-offset:2px}
/* THE WAY OUT OF A LOCK, and it is a link rather than a chip on purpose: the
   chips answer "which level", and a fourth thing shaped like them would read as
   a fourth level. It only exists while the subject IS locked. */
#help .hropen{
  border:0; background:transparent; padding:0; cursor:pointer;
  font:400 11px var(--sans); color:var(--doc-accent); text-decoration:underline;
  text-decoration-color:var(--gold-line); text-underline-offset:3px; white-space:nowrap;
}
#help .hropen:hover:not(:disabled){color:var(--doc-accent-hi); text-decoration-color:currentColor}
#help .hropen:disabled{cursor:default; opacity:.5}
#help .hropen:focus-visible{outline:2px solid var(--doc-accent); outline-offset:2px}
/* WHEN AND WHY IT LAST MOVED (§5, the product's own metric). Its own line under
   the name, because it carries a date and a reason and would push the chips off
   the row if it shared one. */
#help .hrmove{
  margin-top:3px; font:400 11px/1.45 var(--sans); color:var(--doc-dim);
  overflow-wrap:anywhere;
}

/* WHAT THE ANALYST LEARNED: one line per row, and an erase button that is the
   whole point of the section - a memory nobody can correct is worse than none.
   Rows between hairlines rather than cards: the delete sits at the end of its
   own line, which is edge enough for it. The button keeps 30px on a desktop
   and the phone block below takes it to the dock's own 40px. */
#help .hlines{display:flex; flex-direction:column; gap:0; border-top:1px solid var(--doc-line)}
#help .hline{
  display:flex; align-items:center; gap:12px; min-width:0;
  border:0; border-bottom:1px solid var(--doc-line); border-radius:0; padding:10px 0 10px 2px;
  background:transparent;
}
#help .hltext{flex:1 1 auto; min-width:0; font:400 13px/1.5 var(--sans); color:var(--doc-text)}
/* WHICH OF THE TWO KINDS (§8). `asked` is the user's own word for it and
   nothing learned may displace it, so it says so rather than reading alike.
   Its words are written lower-case; the first letter is raised here, which is
   sentence case without touching the text. */
#help .hlkind{
  flex:none; font:600 var(--fs-meta) var(--sans); letter-spacing:.01em;
  color:var(--doc-dim); white-space:nowrap;
}
#help .hlkind::first-letter{text-transform:uppercase}
#help .hlgone{
  height:30px; padding:0 11px; border-radius:var(--r-sm); cursor:pointer; flex:none;
  border:0; background:var(--hover); color:var(--doc-muted);
  font:500 12px var(--sans); white-space:nowrap;
  transition:color var(--dur-1), background var(--dur-1), box-shadow var(--dur-1);
}
#help .hlgone:hover:not(:disabled){color:var(--stop); background:var(--stop-soft); box-shadow:inset 0 0 0 1px var(--stop-line)}
#help .hlgone:disabled{cursor:default; opacity:.5}
#help .hlgone:focus-visible{outline:2px solid var(--doc-accent); outline-offset:2px}
#help .hlnone{font-size:12.5px; color:var(--doc-dim)}

/* ---- THE QUESTIONNAIRE, TWO SCREENS (docs/פרופיל-הקורא.md §4) ----
   It borrows the window's own frame, the `.hpane` column, the `.hrow`/`.hchip`
   rows of the `Your analyst` tab and the `.hacts` button row. Nothing here is
   a second skin: what is new is the option card, the picture and the language
   switch, and each of those is a thing the other panes do not have. */

/* The tab row is three PLACES in a window somebody opened on purpose. The
   questionnaire is a sequence with its own way forward and its own way out, so
   while it is up the tabs are not a way to wander out of it half-answered. */
#help.asking .htab{display:none}

/* The switch. Small, and above both screens rather than inside either - one
   control holding one state. It reads in the language it names, always, so
   `English` is never written in Hebrew letters and the other way round. */
#help .qlang{display:flex; align-items:center; gap:6px; margin:0 0 16px; flex-wrap:wrap}
#help .qlang[hidden]{display:none}
#help .qlangl{
  font:600 var(--fs-meta) var(--sans); letter-spacing:.01em;
  color:var(--doc-dim); margin-inline-end:4px;
}
/* ⚠ THE RESTING COLOUR CARRIES A LITERAL FALLBACK, and it is the only place in
   this file that does. Every other `--doc-*` reader is inside a document whose
   own text is the window; here an unresolved token means the ONE control that
   is on screen before the reader has chosen anything paints its word in the
   inherited colour, which over this background is unreadable. Dor's phone,
   2026-09-19: black chips with nothing written on them until the first tap lit
   one. The word itself is in the markup for the same reason. */
#help .qlangb{
  height:26px; padding:0 10px; border-radius:var(--r-sm); cursor:pointer; flex:none;
  border:0; background:var(--hover, rgba(214,222,235,.055));
  color:var(--doc-muted, #9a9789);
  font:500 12px var(--sans, system-ui, sans-serif); white-space:nowrap;
  transition:color var(--dur-1), background var(--dur-1);
}
#help .qlangb:hover{color:var(--doc-text); background:var(--press)}
#help .qlangb.on{color:var(--gold); background:var(--gold-soft); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
#help .qlangb:focus-visible{outline:2px solid var(--doc-accent); outline-offset:2px}

#help .qq{margin:0; font:600 15px/1.4 var(--sans); color:var(--doc-text)}
/* THE PICTURE CARRIES ITS OWN DATE IN THE CAPTION (rule 3), so nobody has to
   guess what the three readings are of or how old it is. */
#help .qfig{margin:0; display:flex; flex-direction:column; gap:6px}
#help .qfig img{
  /* CAPPED, BECAUSE THE READINGS ARE THE QUESTION AND THE PICTURE IS THE
     CONTEXT. At the pane's full width the chart is 886x427 against a body
     586px tall, so the first screen was the picture and nothing else, and a
     person had to scroll to find out what they were being asked. */
  display:block; width:auto; max-width:100%; max-height:min(300px, 36vh);
  height:auto; margin:0 auto; border-radius:var(--r);
  border:1px solid var(--line); background:var(--well);
}
#help .qfig figcaption{font:400 11.5px/1.45 var(--sans); color:var(--doc-dim)}

/* A READING IS PICKED AS A WHOLE, so the whole of it is the button - a card
   the width of the pane, not a radio dot beside a paragraph. There is no level
   written on it anywhere: the three are deliberately unranked on screen. */
#help .qopts{display:flex; flex-direction:column; gap:10px}
#help .qopt{
  position:relative; display:block; width:100%; text-align:start; cursor:pointer;
  border:1px solid var(--line); border-radius:var(--r-lg); padding:14px 16px;
  background:var(--well); color:var(--doc-text); font:inherit;
  transition:border-color var(--dur-1), background var(--dur-1);
}
#help .qopt:hover{border-color:var(--line2)}
#help .qopt.on{
  border-color:rgba(228,199,120,.45); background:var(--gold-soft);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
}
#help .qopt:focus-visible{outline:2px solid var(--doc-accent); outline-offset:2px}
#help .qoptw{display:flex; flex-direction:column; gap:8px}
#help .qoptw p{margin:0; font:400 12.5px/1.65 var(--sans); color:var(--doc-muted)}
#help .qopt.on .qoptw p{color:var(--doc-text)}
#help .qoptw b{color:var(--doc-text); font-weight:600}
/* WHICH ONE IS PICKED, IN A WORD - the border says it too, but a border is a
   thing you compare and a word is a thing you read. Absent until it is picked,
   so three cards do not carry three labels saying nothing. */
#help .qoptm{
  display:none; margin-top:10px;
  font:600 var(--fs-meta) var(--sans); letter-spacing:.01em;
  color:var(--gold);
}
#help .qopt.on .qoptm{display:block}
/* The way on and the way out on one row, the way on first. */
#help .qacts{gap:10px; margin-top:4px}

/* The way out of the FIRST screen, and only of that: hidden for anybody who
   opened this window on purpose, for whom the x is the way back. */
#help .hfoot{
  display:flex; align-items:center; gap:16px; flex:none;
  padding:14px 28px calc(14px + var(--sab,0px));
  border-top:1px solid var(--doc-line); background:var(--doc-bg2);
}
#help .hfoot span{font-size:12.5px; line-height:1.5; color:var(--doc-muted); flex:1; min-width:0}
#help .hfoot button{height:36px; padding:0 18px; border-radius:var(--r-sm); cursor:pointer;
  font:600 13px var(--sans); flex:none}

/* THE BRAND IS THE DOOR TO IT, so it is a button now - the one place a stranger
   reaches for "what is this", present on every layout because chrome.js never
   moves it. A reset and nothing else: .brand above still owns the look. */
button.brand{
  -webkit-appearance:none; appearance:none;
  background:none; border:0; padding:0; color:inherit; cursor:pointer; text-align:left;
}
button.brand:focus-visible{outline:2px solid var(--accent); outline-offset:3px; border-radius:4px}

/* ---- the suggested first questions, above the composer ----
   The demo state's whole content. Shown to an account with nothing in it, gone
   the moment the transcript has anything in it, and dismissable - a suggestion
   that cannot be put away is an instruction. */
.cstart{display:flex; align-items:center; gap:8px; padding:10px 12px 0 16px; flex:none; min-width:0}
.cstart .lbl{
  font:600 var(--fs-meta) var(--sans); letter-spacing:.01em;
  color:var(--dim); flex:none;
}
/* Sideways rather than wrapped: the panel is 360-460px on a desktop (as
   narrow as 320 by hand) and 390 on a phone, and a second line here pushes
   the composer off a sheet at `peek`.
   Each is a question the user could put, so each is the user's gold - but
   said the way the transcript says the user's words, a hairline and a breath,
   so three of them in a row do not outshine the send button they feed. */
.cstart .chips{display:flex; gap:6px; overflow-x:auto; min-width:0; scrollbar-width:none}
.cstart .chips::-webkit-scrollbar{display:none}
.cstart .chips button{
  flex:none; height:28px; padding:0 12px; border-radius:999px; cursor:pointer;
  border:1px solid rgba(228,199,120,.26); background:rgba(228,199,120,.04); color:var(--text);
  font:12px var(--sans); white-space:nowrap;
  transition:border-color var(--dur-1), background var(--dur-1), color var(--dur-1);
}
.cstart .chips button:hover{border-color:var(--gold-line); background:var(--gold-soft); color:var(--gold-hi)}
.cstart .dismiss{
  flex:none; background:none; border:0; padding:6px 4px; margin:-6px 0; border-radius:var(--r-sm);
  color:var(--dim); font:12px var(--sans); cursor:pointer; transition:color var(--dur-1);
}
.cstart .dismiss:hover{color:var(--text)}

/* ============================================================================
   THE PHONE
   ============================================================================
   Everything below is additive and lives in one block on purpose: the desktop
   is the primary surface and must not regress, so the rules that reshape the
   page are quarantined behind two queries rather than threaded through the
   sheet above. Deleting this block returns the page to what it was.

   Two queries, and they ask different questions:
     (max-width:700px)  - is there ROOM. Layout.
     (pointer:coarse)   - is there a FINGER. Targets, and the hovers that
                          reveal rather than decorate. True on a tablet at
                          1024px, false in a narrow desktop window - which is
                          exactly right, and why the two are not one query.

   The measurements that motivated it, taken at 390x844 on the real page:
     header   scrollWidth 894 against clientWidth 390 - over by 504px, with
              every yield already spent, so 7 of 9 buttons and the whole
              timeframe row were off the edge and unreachable.
     #wrap    0px wide. #watch 216 + #chat 360 in a 390 box, and the chart is
              the flex one. There was no chart on a phone.
     footer   425 in 390 - the TradingView credit, which is a LICENCE
              condition, was clipped mid-word.
     #cal     .win 2px, because it is a child of a 0px #wrap.
     #legend  264px tall. .o-board 22px wide.
   ========================================================================= */

:root{
  /* The notch and the home indicator. Zero everywhere that has neither, so
     these are safe to add unconditionally - and they are only ever non-zero
     because index.html asks for viewport-fit=cover. The two are one change. */
  --sat:env(safe-area-inset-top,0px);
  --sab:env(safe-area-inset-bottom,0px);
  --sal:env(safe-area-inset-left,0px);
  --sar:env(safe-area-inset-right,0px);
  /* How much of the visual viewport the on-screen keyboard is covering.
     Written by chrome.js; 0 until it is. */
  --kb:0px;
  /* The sheet's height, written per-frame while it is dragged. The value here
     is the fallback for a browser with no `@property`: the registration just
     below this block is what makes the per-frame write cheap. */
  --sheet:52vh;
  /* The bottom band of the dock: the home indicator's inset, less 12px. It
     was the whole inset with a 15px floor while the TradingView credit was
     written into it; the credit left for the "what is this" window
     (2026-10-06), so the band is only the gesture's room now and the buttons
     come 12px down into it, with 4px left where there is no indicator. Dor's
     call, measured in WebKit and on the Android emulator. The warning line a
     broken IBKR writes there takes its 15px back (`body.phone.ibkr-bad`). */
  --footband:max(calc(var(--sab) - 12px), 4px);
}

/* A CUSTOM PROPERTY IS INHERITED, AND THAT IS WHAT MADE THE SHEET'S DRAG
   EXPENSIVE. `--sheet` is written on `#chat` once a frame while the head is
   dragged, and an unregistered custom property inherits - so every write
   invalidated the style of the whole subtree under it, which is the
   TRANSCRIPT: hundreds of nodes restyled per frame to move one box. Registered
   with `inherits:false` the write reaches `#chat` and stops there, which is the
   only element that reads it (`height:var(--sheet)`).
   The `:root` declaration above stays as the fallback for a browser that does
   not know `@property` - there it inherits as before and nothing is worse than
   it was. The initial value here cannot be a `vh` (a registration's initial
   value has to be computable without a viewport), and it is never seen: the
   layout writes a real height as it arms. */
@property --sheet { syntax: "<length>"; inherits: false; initial-value: 320px; }

/* ...AND THE SAME LESSON, UNLEARNED ONE SURFACE ALONG. `--swipe` is the window's
   own offset (`armSwipeClose`) and `--drop` the panel's (`armDragClose`), each
   written on the node once a frame while a finger pushes it - and each read by
   exactly ONE element, the one it is written on. Unregistered they inherited,
   so a push on the CALENDAR restyled the ~5,400 nodes of ninety days of
   releases on every frame of the gesture. Measured at 390x844 with 450 rows and
   4x CPU, a 300px push: **median 83.2ms a frame and a worst of 108.6, against
   8.3 and 9.3 registered** - and the same gesture on `#help`, which holds two
   paragraphs, was 8.3/9.3 either way, which is the whole proof that the cost was
   the SUBTREE and not the gesture. Reported by Dor from the phone, 2026-09-18:
   *"there are blinks now and then while dragging and it does not run fluently"*.
   `--drop` is a `<length-percentage>` and not a `<length>`, because the closing
   state is `calc(100% + 80px)` - the panel's own height plus the dock - and a
   registration whose syntax the value does not match makes it invalid at
   computed-value time, i.e. a panel that stops closing. The initial values are
   the resting ones, so removing the property still means "home". */
@property --swipe { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --drop { syntax: "<length-percentage>"; inherits: false; initial-value: 0px; }

/* ROOM IS TWO DIMENSIONS, AND A PHONE ON ITS SIDE HAS ONLY ONE OF THEM.
   This read `(max-width:700px)` alone, chosen "to sit above every phone in
   landscape" - which is backwards: a phone on its side is 844px WIDE, so it
   sailed over the query and took the desktop layout into a 390px-tall window.
   Measured at 844x390: `#wrap` was 268 x 290, because #watch's 216 and #chat's
   360 are still columns there, and the header overflowed by 86px with every
   yield spent. Exactly the failure this block was written to end, one rotation
   away from it.

   The height clause is gated on a FINGER because short is only an emergency on
   something you hold: a desktop window dragged to 400px tall is 1,600px wide
   and its columns fit. 500px is above every phone on its side (430 is the
   tallest) and below every tablet (768 is the shortest). The string is
   duplicated in chrome.js's PHONE and tests/phone.test.mjs asserts they are the
   same - one of them alone is a layout that half-applies. */
@media (max-width:700px), (max-height:500px) and (pointer:coarse){
  /* The board's sentence is written ON the chart now, so it is sized like
     handwriting on a small pane rather than like a caption bar. Here and not
     beside the rule itself: a media query threaded through the file is what
     `tests/phone.test.mjs` exists to refuse (web/CLAUDE.md, the quarantine). */
  .sketch-say{ font-size:15.5px; max-width:calc(100% - 20px) }
  .sketch-head{ font-size:18px }

  /* The keyboard shortens the page rather than sliding over it, so the dock,
     the credit and the composer all stay above it. The layout viewport does
     not shrink on iOS - this is the only way down.

     AND THE FRAME ITSELF DOES NOT SCROLL. `body{overflow-x:hidden}` - the
     guarantee behind fitHeader() - makes the OTHER axis compute to `auto`, so
     body was a scroll container; it overflowed by 8px at 390x844, and that is
     enough for the browser to claim any vertical drag that is not on something
     declaring `touch-action:none`. The phone layout is a fixed frame: the
     header, the chart, the dock and the credit each own their band and the two
     asides scroll inside themselves. Nothing here is meant to move. */
  body{padding-bottom:var(--kb); overflow-y:hidden}
  /* THE FRAME GIVES BACK WHAT AN iPHONE'S INSTALLED APP WITHHOLDS - `--vgap`,
     the status bar's height, measured by chrome.js (syncFrame) and zero
     wherever the viewport already reaches the bottom of the glass. What is
     fixed to the viewport moves by `--vgap-fixed`, measured on its own - on
     Dor's iPhone moving it by `--vgap` put every panel off the dock 47pt
     under the dock, so there it is evidently 0. */
  html.vgap{height:calc(100% + var(--vgap)); overflow:hidden}
  html.vgap #sheets, html.vgap #optv, html.vgap #report, html.vgap #cal,
  html.vgap #alerts, html.vgap #help{bottom:calc(0px - var(--vgap-fixed, 0px))}
  html.vgap #sheets .panel, html.vgap .tfs.compact.open{
    bottom:calc(60px + var(--sab) + var(--kb) - var(--vgap-fixed, 0px))}
  html.vgap .backnote{bottom:calc(54px + var(--footband, 0px) + var(--kb, 0px) - var(--vgap-fixed, 0px))}
  /* ...AND A DRAG THAT REACHES THE FRAME MUST NOT RELOAD IT. Chrome on Android
     answers a downward pull on anything that does not scroll - the header, the
     strip, a list already at its top - with PULL-TO-REFRESH, even on a page
     that cannot scroll at all, and a reload here drops the conversation, the
     sheet and the drawer mid-gesture. `overflow` does not stop it; this does.
     Both elements, because which of the two Chrome reads for the root has
     changed between versions. Safari only loses the rubber band of a frame
     that was never meant to move - `#watch` gave up its own for the same
     reason. */
  html,body{overscroll-behavior:none}

  /* ---- the header: a name, a search box, and whether it is alive ---- */
  header{
    height:auto; min-height:44px; gap:7px;
    padding:var(--sat) calc(10px + var(--sar)) 0 calc(10px + var(--sal));
  }
  header::after{left:calc(10px + var(--sal)); right:calc(10px + var(--sar))}
  /* The controls have gone to the dock; these are the ones that stay.
     ⚠ 64 AND NOT 90, FOR THE ONE PILL THAT MAY NOT YIELD. With `not saving`
     up and every readout already given way, the row was 21px over at 360
     (half of Android) and 6 at 375 (the SE), and the bell went off the edge.
     The pill is a failure being reported (rule 6), so the BOX gives way: it
     is flex, so this floor binds only while the row is actually short, and
     the results sheet below is fixed to the viewport, not to the box. */
  .finder{flex:1 1 auto; min-width:64px}
  #q{height:32px}
  /* THE DROPDOWN IS ANCHORED TO THE BOX, AND THE BOX IS NOT AT THE EDGE.
     `left:0` is the finder's left, which on a phone is x=41 after the brand and
     the search icon - so a 370px panel ran to 411, 21px off the screen, and the
     exchange tag on every row went with it. Fixed to the viewport instead: the
     results are a full-width sheet under the header, which is what a phone
     wants anyway. `top` is the header's own height plus the notch. */
  #results{
    position:fixed; left:10px; right:10px; width:auto;
    top:calc(var(--sat) + 50px); max-height:min(52dvh,440px);
  }
  .spacer{display:none}
  /* The status word already yields by measurement; on a phone the dot alone
     is the whole message and it must never be the thing that gives way. */
  .status{margin-left:auto}
  /* Folded into the dock's ⋯ menu. Not display:none on the BUTTON - the menu
     clicks it, so it has to stay a live control. */
  .ind.folded{position:absolute; width:0; height:0; overflow:hidden; opacity:0; pointer-events:none}
  /* The three that stay up here - the lists, the calendar and the alerts - get
     a finger-sized
     box. GROWN, not haloed: fitHeader() measures `header.scrollWidth`, and a
     negative-inset ::after joins that scrollable overflow and would make the row
     shed pieces it has room for. Same trap as the strip's `+n` chip. */
  header .ind>button{width:34px; height:34px}
  header .ind>button svg{width:17px; height:17px}

  /* ---- the spine: chart underneath, everything else over it ---- */
  #main{position:relative; display:block}
  #wrap{width:100%; height:100%}

  /* The lists, as a drawer. Navigation: opened, used, gone.

     ⚠ AND IT IS THE WHOLE SCREEN NOW (2026-09-18, Dor's call). It was
     `min(300px,86%)`, which on a 390px phone left a 90px sliver of chart that
     is neither readable nor tappable - "the chart behind it is not really
     visible and not useful" - while costing every row 90px it now wants: a
     folder line carries a name, a count, its loudest mover, a percentage and
     a chevron.

     THE ARCHITECTURE IS UNTOUCHED BY THIS and it is worth saying which rule is
     which. "The chart is the base layer and never leaves" is about the
     ANALYST: a layout that hides the chart to show the analyst hides the mark
     being drawn. The drawer was always the opposite kind of thing - opened,
     used, gone, with a scrim over the chart for its whole life - and going
     from 77% covered to 100% covered is a change of degree in something that
     is already a curtain. Landscape keeps its 42%, because there the chart
     beside it genuinely is usable.

     The push-away is unchanged and needs nothing: `swipeAway` measures the
     node's own width, so the travel scales by itself. What it DID need is a
     way out that is not a gesture - the ✕ on the home screen, above. */
  #watch{
    position:absolute; left:0; top:0; bottom:0; z-index:10;
    flex:0 0 auto; width:100%;
    /* ---- A CARD IS BIGGER HERE, AND ALL OF IT IS BIGGER TOGETHER ----
       Dor, 2026-09-18: *make the cards on the watchlist a touch bigger, and
       all the writing with them, in proportion.* The rail was drawn for a
       216px COLUMN beside a chart; here it is a drawer the width of the
       screen and the only thing on it, and type sized for a column read as
       small print on 390px of glass - the same mistake the strip made before
       it learned to scroll.

       ONE NUMBER, because "in proportion" is a property and not a set of
       fifteen agreeing edits (rule 2). Every size on the four cards is
       `calc(base * --card-scale)`, so the name, the percentage, the meta
       line, the chips, the tags, the chevron and the padding all move
       together and none of them can be forgotten. 1.18 is *a touch*: the name
       goes 12 -> 14.2px, the figure 11 -> 13, the meta line 9.5 -> 11.2.

       It is on `#watch` and not on `:root`, so it reaches the cards and
       nothing else on the page - and the desktop rail, which has no width to
       spend, is byte-for-byte unchanged. */
    --card-scale:1.18;
    padding-left:var(--sal);
    box-shadow:0 0 40px rgba(0,0,0,.6);
    animation:slideIn .16s var(--ease);
    /* PROMOTED WHILE IT IS OPEN, NOT WHEN THE FINGER ARRIVES. This hint used
       to ride on `.swiping`, i.e. on the first move - so the first frame of
       every swipe paid for building the layer, which is the frame the eye is
       most on. `#watch` is in the DOM only while the drawer is open, which is
       exactly when it might be dragged, so it costs nothing the rest of the
       time. */
    will-change:transform;
  }
  @keyframes slideIn{from{transform:translateX(-100%)}to{transform:none}}
  /* ...AND IT GOES OUT THE WAY IT CAME IN. While a finger is pushing it, the
     transform is written per frame and there must be no transition racing it;
     when it is let go past the threshold, the same transform is handed to one
     and the drawer finishes the journey by itself. `animation:none` in both,
     or the 160ms slideIn restarts from -100% the moment a class lands on the
     node. See swipeAway() in chrome.js. */
  #watch.swiping,#scrim.swiping{animation:none; transition:none}
  /* THE BLUR COMES OFF WHILE A FINGER IS ON IT - the same cost, on the same
     kind of path, as the one stated under `#cal .scrim.swiping` below, and
     found the same way: the drawer stuttered while it was perfectly promoted.
     Reported from the device as "the closing is not completely smooth". */
  #scrim.swiping{backdrop-filter:none; -webkit-backdrop-filter:none}
  #watch.closing{animation:none; transition:transform .15s var(--ease);
                 transform:translateX(-100%)}
  /* ...AND IT FINISHES ARRIVING THE SAME WAY, when a pull is let go past the
     threshold. TWO CLASSES, AND THAT IS NOT TIDINESS - it is the same split
     `.dragging` and `.held` make one surface along, for exactly the same
     reason.

     `.settling` carries the TRANSITION and lasts as long as the arrival takes.
     `.pulled` carries `animation:none` and lasts as long as the drawer is UP,
     because **`animation:none` coming off is not the animation staying
     finished - it is a fresh `slideIn` from -100%**. Reported by Dor on the
     round this was built: "the moment the whole screen is open there is a
     little blink for a second." Measured at 390x844 - the rail arrived at
     edge -2 at 201ms and was back at -135 at 243ms, sliding in a SECOND time.
     `syncScrim()` takes the class off when the drawer is hidden, where
     `display:none` means a restarted entrance can be seen by nobody. */
  #watch.settling{transition:transform .15s var(--ease)}
  #watch.pulled,#scrim.pulled{animation:none}
  /* ...AND THE SAME `animation:none`, FOR THE DRAWER NOBODY OPENED. At boot on
     a phone the rail is up in the first painted frame (the block above
     `whoAmI()` in index.html), and an entrance is a claim that something just
     arrived: played there it is the chart for 160ms and then a slide over it,
     which is exactly the bug that block exists to end. Its lifetime is
     `.pulled`'s and it is dropped in the same line of `syncScrim()` - a
     restarted entrance behind `display:none` can be seen by nobody. */
  #watch.atload,#scrim.atload{animation:none}

  /* ---- THE ARROW AT THE EDGE ----
     A SMALL TARGET THAT IS NOT A SMALL TAP. 16px of arrow, 44px of height and
     the hit area of the dock's own buttons - the visible tab is deliberately
     the smallest thing on the chart, because it sits over the candles for the
     whole life of every chart and answers a question most charts never ask.

     IT IS INSET FROM THE GLASS, which is the one place the ask was not taken
     literally. iOS defers a touch that starts in the screen-edge gesture
     region while it decides whether it is a back-swipe - and a rightward drag
     from the very left edge is exactly that gesture - so the tab starts a few
     pixels in, on top of the safe-area inset it already pays. It reads as the
     leftmost thing on the chart either way, and the alternative is a handle
     whose one gesture the browser sometimes takes.

     IT OUTRANKS THE ANALYST'S SHEET, AND THAT WAS A BUG FOR A DAY. At 9 it
     sat under `#chat` (10), which is `half` 52% and `full` 88% of the pane
     anchored to the bottom - so at either of those heights the tab was UNDER
     the analyst at exactly the height it lives at, and the only door left into
     the lists was `#watchBtn` at the top of an 844px screen. It is a door: it
     may not be buried by the thing it is a way out of. 11 costs nothing
     against the scrim, which is also 11 - the two are never on screen
     together, because the drawer being open takes the tab away with a SIBLING
     RULE rather than a flag (`#railTab` is after `#watch` in the markup
     precisely so that this needs no state), and the dock (13) still outranks
     it. */
  /* IT IS A TAB LAID OVER WHATEVER IS UNDER IT, SO IT IS OPAQUE. At .82 the
     sheet's own name showed through it, and a door that sits over the word
     "Analyst" at `half` read as that word clipped - `›nalyst` - rather than
     as a door. The popover ground, the floating hairline on its three outer
     edges and a shadow that lifts it off the page; the gold is the chevron,
     the user's way in, and nothing else. */
  #railTab{
    display:flex; align-items:center; justify-content:center;
    position:absolute; z-index:12;
    left:calc(var(--sal, 0px) + 4px); top:50%; transform:translateY(-50%);
    width:18px; height:54px; padding:0;
    border:1px solid var(--line2); border-left:none;
    border-radius:0 var(--r) var(--r) 0;
    background:var(--panel2);
    color:var(--gold); font:600 14px/1 var(--sans); text-indent:1px;
    box-shadow:inset 0 1px 0 var(--hi), 3px 0 12px rgba(0,0,0,.55);
    /* The gesture is horizontal and the chart under it is not: a finger that
       starts here is never panning the candles. */
    touch-action:none;
  }
  /* The target is bigger than the tab, the way every coarse-pointer target on
     this page is - 32x66 against 18x54. What it costs is a strip of chart that
     cannot be panned FROM, halfway down the left edge, and that is the same
     trade the board's own node makes: a pan that does nothing is a pan you
     make again two centimetres to the right. */
  #railTab::after{content:''; position:absolute; top:-6px; bottom:-6px; left:-4px; right:-10px}
  #railTab:active{background:var(--raise); color:var(--gold-hi)}
  #watch:not([hidden]) ~ #railTab{display:none}
  #scrim{
    position:absolute; inset:0; z-index:9;
    background:rgba(8,10,14,.5); backdrop-filter:blur(1px);
    animation:pop .14s var(--ease);
    transition:opacity .15s var(--ease);
    will-change:opacity;
  }
  /* ⚠ NO BLUR UPRIGHT, WHERE NOTHING CAN SEE IT. The drawer is the whole
     screen here and opaque, so at rest the scrim is entirely under it - and a
     `backdrop-filter` is computed whether it is covered or not: the chart
     behind it re-blurred on every tick for as long as the lists were open
     (BTC ticking, x4 CPU, 4s: GPU 245-254ms -> 171-190ms, compositor 98-105 ->
     64-69ms). It shows only for the 160ms the drawer slides, at a fading
     opacity, where one pixel of blur is not a thing an eye can find. On its
     side the drawer is 42% and the blurred chart beside it IS the look, so
     there it stays. */
  body:not(.wide) #scrim{backdrop-filter:none; -webkit-backdrop-filter:none}

  /* The analyst, as a sheet. Conversation: it has to coexist with the chart,
     because the product is an agent drawing on a chart WHILE you talk to it.
     So it does NOT dim the chart behind it - that is the drawer's job, and
     this is the one surface that must never suggest the chart is out of play. */
  /* ⚠ THE SHEET IS ABOVE THE DRAWER, AND THAT IS THE WHOLE OF WHAT A SHEET
     IS (Dor, 2026-09-21: *I am on the favourites page, I pull the chat up,
     the background behind it should be the favourites list and it should rise
     smoothly, the way it rises everywhere else*). It sat at 10 under a
     drawer at 12, so with the lists up the analyst rose BEHIND them; the
     answer taken on 2026-09-18 was to put the drawer away instead, which
     reads as being moved to another page mid-gesture. A bottom sheet does not
     replace what is under it - it covers it and gives it back. So the order
     is scrim 9, drawer 10, sheet 11, and `#railTab` goes to 12 below because
     a door may not be buried by the thing it is a way out of. */
  #chat{
    position:absolute; left:0; right:0; bottom:0; top:auto; z-index:11;
    flex:0 0 auto; height:var(--sheet);
    padding-left:var(--sal); padding-right:var(--sar);
    border-top:1px solid var(--gold-line);
    border-radius:var(--r-xl) var(--r-xl) 0 0;
    box-shadow:0 -18px 44px -12px rgba(0,0,0,.66), 0 -2px 10px rgba(0,0,0,.3);
    transition:border-top-color .9s var(--ease-out);
  }
  #chat::before{display:none}                    /* the hairline is on top now */
  #chatGrip{display:none}                        /* ...and so is the handle: the head */
  /* ...AND SO IS THE ANALYST'S LIGHT. The sheet's seam with the chart is its
     top edge, so that is the edge that changes hands while a turn runs: the
     gold hairline goes ice, and a breath of ice falls from it over the head -
     the desktop's seam turned on its side, the same one light. */
  #chat::after{
    top:0; right:0; bottom:auto; width:auto; height:72px;
    border-radius:var(--r-xl) var(--r-xl) 0 0;
    background:radial-gradient(62% 72px at 50% 0,rgba(110,169,255,.15),rgba(110,169,255,0));
  }
  #chat:has(#chatDot.work){border-top-color:rgba(110,169,255,.72); transition-duration:.35s}
  /* ---- ...AND THE ONE OPENED LAST IS THE ONE IN FRONT ----
     Dor, 2026-09-22: *when the chat is open and I open the favourites rail, it
     opens BEHIND the chat; opening the rail has to bring it over the chat.*
     The rule above is right and half a rule: a bottom sheet rises over what
     you were looking at, and a drawer you have just asked for is what you are
     looking at NOW. A FIXED order can only ever answer one of the two
     gestures, and the other one is then a press that does nothing visible -
     which is exactly the defect the 2026-09-18 round was reported as, with
     the surfaces swapped.

     So the order is not fixed, it is the ORDER THEY WERE ASKED FOR:
     `showWatch(true)` puts the lists in front, `showChat(true)` puts the
     analyst back in front, and neither closes the other - whatever is behind
     is still there and comes back when the thing over it goes away.

     IT IS WRITTEN BY LOWERING THE SHEET RATHER THAN BY RAISING THE DRAWER,
     and that is not arbitrary. The spine above the drawer is spoken for -
     `#railTab` 12, `#sheets` 12, `#dock` 13 - so raising the drawer into it
     would put the lists over the dock's own panels to answer a question about
     the analyst. Below the drawer there is one free rung and nothing else uses
     it. The sheet lands at 8, UNDER the scrim (9), so the drawer's curtain
     dims the analyst exactly as it dims the chart: what is behind the lists is
     behind them, and a tap on it closes the drawer rather than reaching a
     panel nobody can see (rule 1, and `hitDrawing()`'s own lesson one surface
     along). Everything else in the spine keeps the number it had. */
  body.lists-front #chat{z-index:8}
  /* The head is the handle: drag to resize, tap to cycle the three heights.
     A grab affordance, because nothing else says it can be moved. */
  .chead{cursor:grab; touch-action:none; position:relative; padding-top:10px}
  /* The edge tab sits halfway down the pane, which is exactly where the head
     is at `half`, so upright the name starts clear of the tab's 23px. */
  body:not(.wide) #chat .chead{padding-left:30px}
  .chead::before{
    content:""; position:absolute; top:4px; left:50%; width:36px; height:4px;
    margin-left:-18px; border-radius:2px; background:rgba(148,163,184,.3);
  }
  #chat.at-peek .clog{display:none}              /* the composer, and nothing else */
  /* NO HOME-INDICATOR ROOM UNDER THE COMPOSER (2026-10-06, Dor: the gap
     between the field and the "Written by AI" line). The sheet ends on top of
     the dock and the dock pays `var(--sab)` itself, so this was the same ~34px
     reserved twice - and paid BETWEEN the field and the line under it, since
     the line comes after the composer. */
  .cbar{padding-bottom:8px}
  #chat.at-peek .cbar{padding-bottom:10px}
  #chat.at-peek .cdisc{display:none}
  /* ALL THE WAY UP (chrome.js, `measureTop`): a sheet pulled to the top of the
     glass covers the header too, so it loses the rounded corners it only had
     for sitting on the chart, and its head moves down out of the status bar
     and the notch. The top edge of the glass is the phone's own swipe (the
     notification centre), so the way down is not only the head: a pull down
     on the transcript while it is at its start folds the sheet too
     (chrome.js, "the transcript is a handle"). */
  #chat.at-top{border-radius:0}
  #chat.at-top .chead{padding-top:calc(10px + var(--sat))}
  #chat.at-top .chead::before{top:calc(4px + var(--sat))}
  #chat.at-top::after{border-radius:0}

  /* ---- the dock: the controls, where the thumb is ---- */
  /* AND ITS FOOT, WHICH IS WHAT IS LEFT OF THE BAND THAT USED TO BE UNDER IT.
     `flex-wrap` with a 100%-wide last child, rather than a column of two rows,
     so the controls keep the exact line they had: same `space-around`, same
     gap, same measured x positions for the seven of them. `row-gap:0` because
     the 4px is the gap BETWEEN CONTROLS and a second line would otherwise
     inherit it as a fifth of the room the credit gets. */
  #dock{
    display:flex; align-items:center; justify-content:space-around;
    flex-wrap:wrap; align-content:center; gap:4px; row-gap:0;
    /* 40 of buttons, 2 of breathing room, and then the foot - which is not
       ADDED to the home indicator's inset, it is written INSIDE it. See
       --footband below: that is where the last of the empty space went. */
    height:calc(42px + var(--footband));
    padding:0 calc(8px + var(--sar)) 0 calc(8px + var(--sal));
    background:var(--panel); border-top:1px solid var(--line); flex:none;
    position:relative; z-index:13;
    /* THE WHOLE BAR IS A HANDLE FOR THE SHEET ABOVE IT, so it owns its own
       vertical touches. `touch-action` is latched by the browser at touch
       SEQUENCE START - the lesson `#watch.dragging` paid for - so it has to be
       declared here rather than written when the drag arms, and `none` is
       honest: nothing in this bar pans, scrolls or zooms. It costs the buttons
       nothing; they are taps, and `dockPull` does not take one until the
       finger has travelled. */
    touch-action:none;
  }
  /* ...AND THE ARITHMETIC THAT CAPS THE ROW AT EIGHT IS 5px SHORT AT 360,
     WHICH IS HALF OF ANDROID. Eight targets are 321px (the timeframe is 41) and
     their seven 4px gaps 28 - 349 against the 344 a 360px screen leaves inside
     the padding, so the ANALYST, the last of the eight, wrapped onto a second
     line in the credit's band. No iPhone is under 375, which is why it never
     showed. Below that the minimum gap goes and `space-around` alone spaces
     the row - 23px spread over eight, ~3px apart; the targets keep their 40. */
  @media (max-width:374px){ #dock{gap:0} #dock .dname{--sym-w:min(104px, calc(100vw - 262px))} }
  /* THE FOOT, AND IT LIVES IN THE HOME INDICATOR'S OWN BAND.
     Dor, on the first version of this line: shrink the empty space further,
     and put the two things next to each other properly. Both are the same
     answer. The empty space was `var(--sab)` - ~34px on an iPhone that the
     dock reserves so the home indicator has nothing under it - and a line of
     9.5px type is not "something under it": the indicator is a 5px pill at the
     very bottom of that band, and text at the TOP of it clears the pill by
     twenty pixels. So the foot IS the band rather than sitting above it, and
     the 14px it used to cost comes back to the chart.
     `max(--sab, 15px)` because a device with no indicator still has to be able
     to read the line, and that is the only reason for the floor.

     THE TWO SIT TOGETHER IN THE RIGHT-HAND CORNER, NOT AT OPPOSITE EDGES.
     Pushed apart by `margin-left:auto` they read as two unrelated scraps of
     chrome at the two bottom corners; together they are one quiet footnote,
     which is what they are - a device setting and a credit, neither of them
     about the chart. Centred for one round and then moved to the corner, Dor's
     call: the middle of that line is directly under the two or three buttons a
     thumb travels through most, so a footnote there is in the way of nothing
     and yet reads as a caption to them. The corner is where a footnote goes,
     it is where the credit already sat in the footer, and it leaves the left
     of the line empty for the one thing that ever needs it - a longer bell
     label on a narrow phone. The separator is the footer's own `·`, drawn by
     CSS rather than by a third node, because chrome.js moves nodes and does
     not invent them. */
  #dockfoot{
    flex:0 0 100%; display:flex; align-items:flex-start; justify-content:flex-end;
    gap:7px; height:var(--footband); font-size:9.5px; line-height:15px;
    color:var(--dim); white-space:nowrap; overflow:hidden;
  }

  /* ---- the bell, at the far end of the header ----
     It spent one round on the line above and Dor moved it up: "it should not
     be there - put it next to the IBKR light." That corner is already where
     the DEVICE's state is said (the provider, the feed's dot, whether anything
     is ticking), and *can this device be told about an alert* is the same kind
     of statement as *is the data alive*.

     A DOT AND NOT A WORD, HERE ONLY. The label is the readout everywhere else,
     but this row has 370px and a search box in it, and the dot IS the state -
     hollow means nothing is subscribed, filled means something is, which is
     exactly the idiom of the dot it is standing beside. `font-size:0` hides
     the words without hiding the ::before, whose size is in pixels. What the
     words used to explain a TAP now explains: alerts.js answers `blocked`,
     `unconfigured` and `unsupported` with the sentence in a panel instead of
     doing nothing, or an orange dot on a phone would be a failure that cannot
     be told from being switched off (rule 6). */
  /* 26x24 of target on a control the width of a dot, and it is in the corner
     the thumb can reach without aiming (the row is 44px tall, so -9px of
     margin costs the row nothing). */
  header .bell{font-size:0; gap:0; padding:9px 8px; margin:-9px 0}
  header .bell::before{width:8px; height:8px}
  /* The hint follows the bell. It is fixed to the viewport and was anchored to
     the bottom, where the bell used to be; from the top-right corner it hangs
     under the header instead. */
  #bellHint{top:calc(var(--sat) + 48px); bottom:auto}
  /* The same hairline the header wears, mirrored - the two ends of the page
     are a pair. */
  #dock::before{
    content:""; position:absolute; left:0; right:0; top:0; height:1px; pointer-events:none;
    background:linear-gradient(90deg,rgba(228,199,120,0),rgba(228,199,120,.45) 14%,rgba(228,199,120,.45) 86%,rgba(228,199,120,0));
  }
  /* THE DESKTOP'S RESTING CONTROL, AT THE THUMB'S SIZE. No edge at rest - the
     glyph on the bar - the fill under a press, and `.on` the one inset line a
     control ever gets: gold for a layer that is on, --warn for the eye, the
     analyst's own ice for the analyst (all three from the base rules). */
  #dock .ind>button{width:40px; height:40px; border-radius:var(--r)}
  #dock .ind>button svg{width:19px; height:19px}
  /* The analyst is what this product is FOR, and it sits under the thumb:
     its glyph is its own ice at rest, with no frame - the frame is `.on`'s. */
  #dock .ind:has(#chatBtn)>button{color:var(--agent)}
  /* The header's "agent" pill is 51px the phone does not have, and the
     analyst's own button is already here. Same fact, no row. */
  #agentPill{display:none}
  #dock .ind.live>button::before{
    content:""; position:absolute; top:5px; right:5px; width:6px; height:6px;
    border-radius:50%; background:var(--agent); box-shadow:0 0 0 2px var(--panel);
  }
  #dock .ind>button{position:relative}
  /* A TOOL THAT IS ON LIGHTS THE DOCK'S EDGE (2026-09-28) - the desktop's
     active timeframe standing on the header's hairline, mirrored onto the bar
     at the other end of the page. The glyph takes the colour, a 2px line
     lights the top edge where the dock's own gold hairline runs, and a soft
     fall of the same colour hangs under it: a lamp over the tool rather than
     a box around it. Gold for a layer, the analyst's ice for the analyst, the
     warning's orange for the eye. The tap target is untouched - the ::after
     that grows it is not used here. The fall is painted once, from the BORDER
     box: sized to the padding box and repeated, it came back as a bright 1px
     line along the button's transparent border. */
  #dock .ind>button.on{
    border-radius:0 0 var(--r) var(--r);
    background:linear-gradient(180deg,rgba(228,199,120,.17),rgba(228,199,120,0) 72%) no-repeat border-box;
    box-shadow:inset 0 2px 0 var(--gold);
  }
  #dock #chatBtn.on{
    background:linear-gradient(180deg,rgba(110,169,255,.2),rgba(110,169,255,0) 72%) no-repeat border-box;
    box-shadow:inset 0 2px 0 var(--agent);
  }
  #dock #marksBtn.on{
    background:linear-gradient(180deg,rgba(240,160,60,.17),rgba(240,160,60,0) 72%) no-repeat border-box;
    box-shadow:inset 0 2px 0 var(--warn);
  }

  /* ---- and they stand on the dock rather than ON TOP OF IT ----
     A SHEET CANNOT GO BEHIND ITS OWN PARENT, AT ANY Z-INDEX. `#dock` is
     `z-index:13`, which makes it a stacking context, and an element's
     background paints under every positioned descendant it has - `fixed`
     included, negative z-index included. So a panel dragged down rode over the
     bar and over its own button, while the analyst's sheet (`z-index:10`,
     outside the dock) has always gone behind it. Dor, 2026-09-17: *I want it to
     come from behind the bar, like the chat window.*
     The fix is parentage, not a number: chrome.js moves the three panels and
     the open timeframe grid into `#sheets` while the phone layout is
     installed. Over the chart and over the analyst, under the dock.
     `pointer-events` transparent, because it is a full-screen box that exists
     only to hold a z-index - and its children take their own back. */
  #sheets{position:fixed; inset:0; z-index:12; pointer-events:none}
  #sheets>*{pointer-events:auto}

  /* Both panels open UPWARD from the dock and are sheets rather than 304px
     popovers anchored to a button that has moved. */
  #sheets .panel{
    position:fixed; left:8px; right:8px; bottom:calc(60px + var(--sab) + var(--kb));
    top:auto; width:auto; max-height:min(62dvh,560px);
    padding-bottom:8px;
    /* ---- ...AND A SHEET IS PUT DOWN BY DRAGGING IT DOWN ----
       The only way out of one here was a tap on the chart behind it, which
       lands on the chart you were reading. The gesture is the window's
       (armSwipeClose) turned ninety degrees, so the machinery is the window's
       too: while a finger is pushing, the offset is written per frame and
       there must be no transition racing it; let go past the threshold and the
       same offset is handed to one and the sheet finishes the journey itself.
       A VARIABLE rather than a bare transform, so the closing state can be
       carried by a class without the inline one beating it.
       `100% + 80px` is its own height plus the dock it stands on, or it stops
       with a hairline of itself still showing. chrome.js, armDragClose(). */
    transform:translateY(var(--drop,0px));
    transition:transform .15s var(--ease), opacity .15s var(--ease);
    /* The list is the thing being dragged, so its overscroll may not become
       the page's - a rubber-banding body underneath is how the browser takes
       the gesture away mid-travel. */
    overscroll-behavior:contain;
  }
  /* `.dragging` lasts as long as the FINGER and turns off the transition that
     would race it; `.held` lasts until the panel is down or back and turns off
     the ENTRANCE - because `animation:none` coming off is not the animation
     staying finished, it is a fresh `pop` from `opacity:0; transform:none`, so
     with one class the panel played itself back in whole for 140ms on its way
     out. See armDragClose(). */
  #sheets .panel.dragging{transition:none; will-change:transform}
  #sheets .panel.held,#sheets .panel.closing{animation:none}
  #sheets .panel.closing{--drop:calc(100% + 80px); opacity:0}
  /* A GESTURE IS DISCOVERABLE ONLY ONCE YOU KNOW IT IS THERE, so the sheet
     wears the grab bar every sheet on the device wears. Sticky rather than
     static: the panel is its own scroller, and a handle that scrolls away
     takes the only sign of the gesture with it. The tap outside still closes
     it - this is a second way out, never a replacement. */
  /* An OPAQUE band and not a floating pill: the panel scrolls under it, and a
     36px bar with a list running behind it reads as a row that has gone wrong.
     The negative margins reach back over the panel's own 6px of padding so the
     band is flush, and `top:-6px` is the same 6px again, so it sticks to the
     true top edge rather than 6px down it. */
  #sheets .panel::before{
    content:""; position:sticky; top:-6px; z-index:3; display:block;
    height:15px; margin:-6px -6px 1px; background:var(--panel2);
    background-image:linear-gradient(rgba(148,163,184,.3),rgba(148,163,184,.3));
    background-repeat:no-repeat; background-position:center 7px; background-size:36px 4px;
  }
  /* ---- the timeframe: nine buttons are 285px of a 366px budget ---- */
  .tfs{position:relative; flex:none}
  .tfs.compact{background:none; border:0; padding:0; height:40px}
  /* Folded, the one button showing IS the on-item, so it wears the page's
     single on-treatment and nothing else - no box round it. */
  .tfs.compact button{
    height:40px; min-width:40px; border-radius:var(--r);
    background:transparent; border:0; font-size:13px;
  }
  .tfs.compact button:not(.on){display:none}
  .tfs.compact button.on{background:var(--gold-soft); color:var(--gold); box-shadow:inset 0 0 0 1px rgba(228,199,120,.3)}
  /* ...AND UNDER 350 THE TARGETS THEMSELVES GIVE WAY, or the analyst's key
     wraps onto the credit line again. That width is not only a small phone:
     Android Chrome can zoom a page by the system's text size, which is a
     412px Pixel reading as ~317 at 130%, and a pinch-zoomed Safari is the
     same. Eight equal shares of what is left; 36px at 300, 33 at 277. Here
     and not beside the 374px rule, because both rules it beats are below
     that one and equal in specificity. */
  @media (max-width:349px){
    #dock{padding-left:calc(4px + var(--sal)); padding-right:calc(4px + var(--sar))}
    #dock .ind>button, #dock .tfs.compact button{width:min(40px, calc((100vw - 8px) / 8.2)); min-width:0; padding:0}
    /* The name takes what five of those keys and the timeframe leave. */
    #dock .dname{--sym-w:calc(100vw - 8px - 5 * min(40px, calc((100vw - 8px) / 8.2)) - 38px - 10px)}
    #dock .dname .tfs.compact:not(.open) button.on{width:auto; min-width:34px; padding:0 7px}
  }
  /* Folded, the timeframe is the dock's first key and wears the same lamp -
     and its container sheds the desktop track's inset line, or the key sits
     in a faint frame the other tools do not have. */
  .tfs.compact:not(.open){box-shadow:none}
  .tfs.compact:not(.open) button.on{
    border-radius:0 0 var(--r) var(--r);
    background:linear-gradient(180deg,rgba(228,199,120,.17),rgba(228,199,120,0) 72%) no-repeat border-box;
    box-shadow:inset 0 2px 0 var(--gold);
  }
  /* ---- THE NAME AND THE TIMEFRAME, ONE PILL (web/switcher.js) ----
     What am I looking at, in the place the timeframe already was: the charted
     name on the left, a tap away from the rest of its list, and the timeframe
     on the right, a tap away from the other eight. One raised surface with a
     lit top edge - the depth-by-elevation the chrome is built on - and a
     hairline between the halves, so it reads as one object with two doors.
     `.dname` is made by chrome.js (joinName) because `space-around` spreads
     room round every child of the dock and two children could never touch. */
  #dock .dname{
    display:flex; align-items:stretch; flex:none; height:40px; min-width:0;
    border-radius:11px;
    background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.018));
    box-shadow:inset 0 1px 0 var(--hi), inset 0 0 0 1px var(--line);
  }
  #dock .dname .ind.sym{display:flex}
  #dock .dname #symBtn{
    /* ONE WIDTH, WHATEVER THE NAME. Sized to its label, the pill changed width
       with every step through the list and every key to its right shifted
       under the thumb - a row that moves when you use it (rule 5). The room is
       what the row can give at this width (five 40px keys and their gaps), and
       a longer name ends in an ellipsis, the whole of it in the tray. */
    width:var(--sym-w,min(104px, calc(100vw - 274px))); flex:none;
    height:100%; min-width:0;
    display:flex; align-items:center; justify-content:space-between; gap:5px; padding:0 8px 0 11px;
    border:0; border-radius:11px 0 0 11px; background:none; box-shadow:none;
    color:var(--text); font:600 13.5px/1 var(--sans); letter-spacing:.005em;
    overflow:hidden;
  }
  /* The name over its place in the list. The name is a window (`.tkw`) the
     current name and the next one slide through during a stroke along the
     dock; the place is the quiet line under it, and on no list there is no
     line and the name sits in the middle. */
  /* The charted symbol's mark beside its name - the rail's own face. */
  #symBtn .tlogo{flex:none; display:grid}
  #symBtn .tlogo:empty{display:none}
  #symBtn .tlogo .wlogo{width:18px; height:18px}
  #symBtn .tlogo .wlogo b{font-size:9px}
  #symBtn .tcol{display:flex; flex-direction:column; justify-content:center; gap:3px; min-width:0; flex:1;
               text-align:left}
  #symBtn .tkw{position:relative; overflow:hidden; display:block; line-height:1.15}
  #symBtn .tk,#symBtn .tk2{display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  #symBtn .tk2{position:absolute; left:0; top:0; right:0; opacity:0; color:var(--gold-hi)}
  #symBtn.settling .tk,#symBtn.settling .tk2{transition:transform .18s var(--ease-out), opacity .18s var(--ease-out)}
  #symBtn .tpos{display:none; font:500 9.5px/1 var(--num); font-variant-numeric:tabular-nums;
                letter-spacing:.04em; color:var(--dim)}
  #symBtn.placed .tpos{display:block}
  /* The pick that flew here lands with one breath of gold on the pill. */
  #symBtn.landing .tk{opacity:0}
  #dock .dname:has(#symBtn.landed){animation:pillLand .55s var(--ease-out)}
  @keyframes pillLand{
    0%{box-shadow:inset 0 1px 0 var(--hi), inset 0 0 0 1px var(--gold), 0 0 18px -4px rgba(228,199,120,.7)}
    100%{box-shadow:inset 0 1px 0 var(--hi), inset 0 0 0 1px var(--line), 0 0 0 0 rgba(228,199,120,0)}
  }
  .swfly{position:fixed; z-index:40; pointer-events:none; white-space:nowrap;
         font:600 13px/1.15 var(--sans); color:var(--gold-hi);
         text-shadow:0 0 10px rgba(228,199,120,.55); will-change:transform}
  /* `#dock #symBtn`, or the dock's own 19px icon rule wins and the chevron
     takes ten pixels from the name. */
  #dock #symBtn .chev{
    width:10px; height:10px; flex:none; fill:none; stroke:var(--gold); stroke-width:1.9;
    stroke-linecap:round; stroke-linejoin:round; opacity:.75;
    transition:transform .2s var(--ease), opacity .2s;
  }
  #symBtn.on .chev{transform:rotate(180deg); opacity:1}
  #dock .dname #symBtn:active{background:var(--press)}
  #dock .dname #symBtn.on{
    background:linear-gradient(180deg,rgba(228,199,120,.16),rgba(228,199,120,0) 80%);
    box-shadow:inset 0 2px 0 var(--gold);
  }
  /* The timeframe keeps its gold and gives up its lamp: inside the pill the
     surface is the frame, and a second one round half of it is noise. */
  #dock .dname .tfs{position:relative; height:100%}
  #dock .dname .ind.sym + .tfs::before{
    content:""; position:absolute; left:0; top:10px; bottom:10px; width:1px;
    background:var(--line2); pointer-events:none;
  }
  #dock .dname .tfs.compact:not(.open) button.on{
    height:100%; min-width:38px; padding:0 9px 0 8px;
    border-radius:0 11px 11px 0; background:none; box-shadow:none;
    color:var(--gold); font:600 13px/1 var(--num);
  }
  #dock .dname .tfs.compact:not(.open) button.on:active{background:var(--press)}
  /* With the ladder open the timeframe is away in #sheets, and the name is the
     whole pill for as long as it is. */
  #dock .dname .ind.sym:last-child #symBtn{border-radius:11px}

  /* ---- ...AND THE TRAY THAT RISES OFF IT ----
     One of the dock's sheets (`#sheets .panel` above gives it the place, the
     grab bar and the drag-down), holding the whole list as tiles in the
     list's OWN order - so a name keeps its place and the hand learns where it
     is. Each tile is the heat map's (`heatOf`, the rail's five steps) at a
     thumb's size: the tint says how far it moved before the figure is read,
     so the tray is also the list's day at a glance. The one being charted
     wears the page's gold, the colour of the user's own hand. */
  /* A column, so the hint stands at the foot of a tray held tall by a longer
     list rather than hanging under a short one. */
  .swpanel{padding:6px 6px 10px; display:flex; flex-direction:column}
  .swpanel>*{flex:none}
  .swpanel .swhint{margin-top:auto; padding-top:12px}
  /* THE STRIP - every list on one sideways road, the folders as labels on it
     (web/switcher.js). Chips the height of a thumb's aim, the list on show in
     the page's gold, the list the CHART is in marked with a dot. The road
     fades out at both ends so it reads as going on past the edge, which is
     the only sign a row can give that it scrolls. `pan-x` so the browser
     scrolls it and nothing else claims the stroke. */
  .swstrip{
    display:flex; align-items:center; gap:4px; overflow-x:auto; overflow-y:hidden;
    margin:0 -6px; padding:4px 12px 10px; scrollbar-width:none; touch-action:pan-x;
    overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch;
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 14px,#000 calc(100% - 22px),transparent);
            mask-image:linear-gradient(90deg,transparent 0,#000 14px,#000 calc(100% - 22px),transparent);
  }
  .swstrip::-webkit-scrollbar{display:none}
  .swsec{display:flex; align-items:center; gap:4px; flex:none}
  .swsec + .swsec{margin-left:8px; padding-left:10px; border-left:1px solid var(--line)}
  .swlab{
    flex:none; height:32px; padding:0 4px; border:0; background:none; cursor:pointer;
    font:600 9.5px/1 var(--sans); letter-spacing:.1em; text-transform:uppercase; color:var(--dim);
  }
  .swlab:active{color:var(--muted)}
  .swchip{
    flex:none; display:flex; align-items:center; gap:6px; height:32px; padding:0 12px;
    border-radius:999px; cursor:pointer; white-space:nowrap;
    background:var(--well); color:var(--muted); border:1px solid var(--line);
    font:500 12.5px/1 var(--sans); transition:background-color .15s, color .15s, border-color .15s;
  }
  .swchip span{max-width:140px; overflow:hidden; text-overflow:ellipsis}
  .swchip i{font:500 10.5px/1 var(--num); font-style:normal; color:var(--dim);
            font-variant-numeric:tabular-nums}
  .swchip:active{background:var(--press)}
  .swchip.here{position:relative}
  .swchip.here::after{content:""; position:absolute; top:4px; right:5px; width:4px; height:4px;
                      border-radius:50%; background:var(--gold); opacity:.8}
  /* The chip on show gives its ground to `.swind` - ONE piece of gold that
     slides along the road to whichever list is on show. */
  .swstrip{position:relative}
  .swchip,.swlab{position:relative; z-index:1}
  .swind{
    position:absolute; left:0; top:0; z-index:0; pointer-events:none; border-radius:999px;
    background:linear-gradient(180deg,rgba(228,199,120,.2),rgba(228,199,120,.1));
    box-shadow:inset 0 0 0 1px var(--gold-line), inset 0 1px 0 rgba(255,255,255,.08),
               0 0 14px -6px rgba(228,199,120,.7);
  }
  .swind.glide{transition:transform .36s cubic-bezier(.2,.7,.2,1), width .36s cubic-bezier(.2,.7,.2,1)}
  .swchip.on{color:var(--gold-hi); background:transparent; border-color:transparent}
  .swchip.on i{color:var(--gold)}

  .swhead{display:flex; align-items:center; justify-content:space-between; gap:10px;
          padding:2px 8px 11px}
  .swname{display:flex; align-items:baseline; gap:9px; min-width:0}
  .swname b{font:600 16px/1.2 var(--sans); color:var(--text);
            white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
  .swpos{flex:none; font:500 11.5px/1 var(--num); font-variant-numeric:tabular-nums; color:var(--muted)}
  /* THE `+` - the header's `+` beside the charted name, here because the dock
     is full (web/addlist.js). A real target: 40x32 drawn, the coarse ::after
     makes it more. The header's own is not shown on a phone. */
  .ind.addl{display:none}
  .swadd{flex:none; position:relative; display:grid; place-items:center; width:40px; height:32px;
         padding:0; border-radius:9px; cursor:pointer; background:var(--well); color:var(--gold);
         border:1px solid var(--line); font:400 22px/1 var(--sans)}
  .swadd:active{background:var(--press)}
  .swadd::after{content:""; position:absolute; inset:-6px -4px}
  /* ...and the body it opens, in the tray: rows a finger can land on, and a
     field at 16px or iOS zooms the page on focus. */
  .swpanel .alhead{padding:2px 4px 10px}
  .swpanel .alhead b{font-size:16px}
  .swpanel .alback{width:40px; height:36px; font-size:26px}
  .swpanel .alsec{padding:12px 8px 6px; font-size:10.5px}
  .swpanel .alrow{min-height:44px; padding:8px 10px; font-size:15px; gap:12px}
  .swpanel .alck{width:20px; height:20px; font-size:13px}
  .swpanel .alcount{font-size:12px}
  .swpanel .alnew input,.swpanel .alok{height:40px}
  .swpanel .alnew input{font-size:16px}
  .swpanel .alok{font-size:14px; padding:0 16px}
  .swpos i{font-style:normal; color:var(--dim); margin:0 2px}
  /* THE ORDER - two words in one track, the page's segmented control: the
     choice sits on a raised face, the rest on the well. */
  .swsort{flex:none; display:flex; padding:2px; gap:2px; border-radius:9px;
          background:var(--well); box-shadow:inset 0 0 0 1px var(--line)}
  .swsort button{
    display:flex; align-items:center; gap:4px; height:28px; padding:0 11px; border:0;
    border-radius:7px; background:none; color:var(--muted); cursor:pointer;
    font:500 12px/1 var(--sans); transition:background-color .15s, color .15s;
  }
  .swsort button svg{width:10px; height:10px; fill:none; stroke:currentColor; stroke-width:1.6;
                     stroke-linecap:round; stroke-linejoin:round}
  .swsort button.on{background:var(--raise); color:var(--gold); box-shadow:inset 0 1px 0 var(--hi)}
  .swpanel{overflow-x:hidden; position:relative}
  /* THE TRAIL - the lists drawer's own light, on the tiles: the frame goes
     gold in 70ms and takes 700ms to let go, so every tile a finger crossed is
     mid-fade at once. The frame, never a light over the figures. */
  .swt{transition:transform .12s var(--ease), background-color .2s,
                  border-color 700ms cubic-bezier(.22,.61,.36,1), box-shadow 700ms cubic-bezier(.22,.61,.36,1)}
  .swt.lit{border-color:var(--gold);
           box-shadow:inset 0 1px 0 var(--hi), 0 0 0 .5px rgba(228,199,120,.45), 0 0 12px -5px var(--gold);
           transition-duration:.12s, .2s, 70ms, 70ms}
  /* A figure that CHANGED breathes its tile's own colour once - an overlay on
     opacity, the rail's tick flash, so the tile is not repainted to say it. */
  .swt::before{content:""; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
               opacity:0; background:var(--up)}
  .swt.td::before{background:var(--down)}
  .swt.tu::before,.swt.td::before{animation:swTick .7s ease-out}
  @keyframes swTick{0%{opacity:.22} 100%{opacity:0}}
  /* A change of list slides the tiles in from the side the strip moved to. */
  .swgrid.nav .swt{animation:none}
  .swgrid.from-r{animation:gridFromR .24s var(--ease-out)}
  .swgrid.from-l{animation:gridFromL .24s var(--ease-out)}
  .swgrid.from-fade{animation:gridFade .2s var(--ease-out)}
  @keyframes gridFromR{from{transform:translateX(28px); opacity:0} to{transform:none; opacity:1}}
  @keyframes gridFromL{from{transform:translateX(-28px); opacity:0} to{transform:none; opacity:1}}
  @keyframes gridFade{from{opacity:.3} to{opacity:1}}
  .swnote{margin:-3px 8px 10px; font-size:11.5px; line-height:1.35; color:var(--muted)}
  .swgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(78px,1fr)); gap:5px;
          padding:0 2px}
  .swt{
    position:relative; display:flex; flex-direction:column; align-items:flex-start;
    justify-content:center; gap:3px; min-width:0; min-height:47px; padding:6px 10px;
    border:1px solid var(--line); border-radius:10px; cursor:pointer; text-align:left;
    background-color:rgba(255,255,255,.012);
    background-image:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,0));
    box-shadow:inset 0 1px 0 var(--hi); color:var(--text); overflow:hidden;
    animation:swin .24s var(--ease-out) backwards; animation-delay:calc(var(--i,0) * 12ms);
  }
  /* The top line: the rail's round mark, then the name. A step smaller than
     on a list row - the tile is the tighter room - and the name keeps what
     is left, ending in an ellipsis rather than pushing the mark out. */
  .swtop{display:flex; align-items:center; gap:5px; min-width:0; max-width:100%}
  .swt .wlogo{width:16px; height:16px}
  .swt .wlogo b{font-size:8.5px}
  .swt i{font:600 13px/1.1 var(--sans); font-style:normal; min-width:0; max-width:100%;
         overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .swt>b{font:500 11.5px/1 var(--num); font-variant-numeric:tabular-nums; color:var(--dim)}
  .swt.up>b{color:var(--up)} .swt.down>b{color:var(--down)}
  .swt.up.s1{background-color:rgba(47,208,138,.06)}  .swt.up.s2{background-color:rgba(47,208,138,.12)}
  .swt.up.s3{background-color:rgba(47,208,138,.2)}   .swt.up.s4{background-color:rgba(47,208,138,.3)}
  .swt.up.s5{background-color:rgba(47,208,138,.42)}
  .swt.down.s1{background-color:rgba(255,107,124,.06)} .swt.down.s2{background-color:rgba(255,107,124,.12)}
  .swt.down.s3{background-color:rgba(255,107,124,.2)}  .swt.down.s4{background-color:rgba(255,107,124,.3)}
  .swt.down.s5{background-color:rgba(255,107,124,.42)}
  /* Past the third step the ink under the figure is its own colour, so the
     figure comes up to the text's to stay readable - the heat map's rule. */
  .swt.s4>b,.swt.s5>b{color:var(--text)}
  .swt.cur{
    border-color:var(--gold);
    box-shadow:inset 0 1px 0 var(--hi), 0 0 0 1px rgba(228,199,120,.28), 0 0 16px -5px rgba(228,199,120,.6);
  }
  .swt.cur::after{content:""; position:absolute; top:7px; right:7px; width:5px; height:5px;
                  border-radius:50%; background:var(--gold); box-shadow:0 0 6px rgba(228,199,120,.8)}
  .swt:active{transform:scale(.95)}
  .swt.hit{border-color:var(--gold-hi); background-color:rgba(228,199,120,.2)}
  .swt.dead{opacity:.4; cursor:default}
  @keyframes swin{from{opacity:0; transform:translateY(6px)} to{opacity:1; transform:none}}
  .swhint{display:flex; justify-content:center; align-items:center; gap:7px;
          font-size:10.5px; letter-spacing:.02em; color:var(--dim)}
  .swhint span{color:var(--gold); opacity:.7; font-size:12px; letter-spacing:.1em}
  .swhint b{font-weight:400; margin:0 4px; opacity:.6}
  .swempty{padding:16px 10px 10px; color:var(--muted); font-size:12.5px; line-height:1.55}
  .swempty b{color:var(--text); font-weight:600}
  @media (prefers-reduced-motion:reduce){
    .swt,.swgrid.from-r,.swgrid.from-l,.swgrid.from-fade{animation:none}
    .swind.glide{transition:none}
    #dock .dname:has(#symBtn.landed){animation:none}
  }

  /* Open, the whole ladder comes back as a grid over the dock - nine buttons
     in a block, rather than the 285px row there is no room for. The element
     itself becomes the popover, so the page's own click handlers on the nine
     are untouched. */
  .tfs.compact.open{
    display:grid; grid-template-columns:repeat(5,1fr); gap:6px; height:auto;
    position:fixed; left:8px; right:8px; bottom:calc(60px + var(--sab) + var(--kb));
    z-index:20; padding:8px; background:var(--panel2);
    border:1px solid var(--line2); border-radius:var(--r-lg);
    box-shadow:var(--shadow-pop); animation:pop var(--dur-2) var(--ease-out);
    /* ...AND IT IS PUT DOWN THE SAME WAY THE THREE BESIDE IT ARE. It opens off
       the same bar, in the same place, so a gesture that worked on the panel
       and not on this one would be a gesture to remember per control (rule 2).
       Same machinery, same classes, same `--drop` - armDragClose() does not
       know what it is holding. `touch-action:none` because nothing in a grid of
       nine buttons pans or scrolls, and it is latched at touch-sequence start,
       so it has to be declared rather than written when the drag arms. */
    transform:translateY(var(--drop,0px));
    transition:transform .15s var(--ease), opacity .15s var(--ease);
    touch-action:none;
  }
  .tfs.compact.open.dragging{transition:none; will-change:transform}
  .tfs.compact.open.held,.tfs.compact.open.closing{animation:none}
  .tfs.compact.open.closing{--drop:calc(100% + 80px); opacity:0}
  /* The grab bar the three sheets wear, so the gesture is said here too. A
     grid ITEM, spanning the row - not sticky, because this sheet is nine
     buttons and never scrolls. */
  .tfs.compact.open::before{
    content:""; grid-column:1/-1; height:4px; width:36px; margin:-2px auto 2px;
    border-radius:2px; background:rgba(148,163,184,.3);
  }
  /* Nine keys, each a well with no edge of its own - the segmented track's
     items, laid out as a grid - and the one that is on in the page's gold. */
  .tfs.compact.open button{display:grid; place-items:center; width:auto}
  .tfs.compact.open button:not(.on){background:var(--well); color:var(--muted)}

  /* ---- the strip ----
     ALL ELEVEN TILES WERE FOLDED, AND THE WAY BACK DOES NOT EXIST ON A PHONE.
     fitStrip() was working exactly as designed: the price tile is ~355px of a
     390px row on a five-figure quote, so #layers was left 33px and every tile
     gave way. What is folded goes into the `+n` chip's TITLE - a tooltip, and
     a phone has no pointer to hover one with - so eleven numbers were not
     merely hidden, they were unreadable.

     So the whole row scrolls instead, vital tiles included. A sideways swipe
     is the one gesture a phone never has to be taught, it keeps the row ONE
     LINE (a second row is a row taken off the chart, which was the original
     reason for folding), and nothing is lost. `+n` and S are untouched and
     still fold on demand; fitStrip() simply finds nothing to drop, because
     #layers is now sized to its content rather than to what is left over. */
  #strip{
    padding-left:calc(10px + var(--sal)); padding-right:calc(10px + var(--sar));
    overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;
    overscroll-behavior-x:contain;
  }
  #strip::-webkit-scrollbar{display:none}
  #layers{flex:0 0 auto; overflow:visible}
  #strip .t{flex:none}
  /* The extended hours' short word (`extHtml`): with the close in the big
     slot, the price now and its percent have to fit beside it unswiped. */
  #strip .t small .lw{display:none}
  #strip .t small .sw{display:inline}
  /* ...and the caption gives way: "pre" beside the second price already says
     which of the two is the close, and 45px of "CLOSE" was what pushed the
     percent off the glass. */
  #strip #tLast em{display:none}

  /* ---- the view's three keys: a CAPSULE at the END OF THE QUOTE BAR ----
     Snap, fullscreen and the eye left the price axis on a phone (Dor,
     2026-10-03: *"in the quarter of the chart they sit on there are no
     numbers, and when I want to read the level there I cannot"*). Upright the
     axis is as narrow as its labels, so they could only stand there as a
     COLUMN, and the column hid the top quarter of the scale - 151px of a 535px
     pane, the prices of the highest candles on screen.

     chrome.js moves `#zoom` into `#chrome` (QUOTE_END) and the grid lays it
     beside the strip as the end of ONE row - the header stays above the bar,
     the order the eye already knows. The bar scrolls sideways, so the tiles it
     shortens are a swipe away, never gone; the fade says the row runs on.
     NOT gated on `body.phone` (an empty second column costs nothing).

     THE THREE ARE ONE CONTROL, SO THEY ARE DRAWN AS ONE: a segmented capsule
     a step below the bar (`--well`), the axis's gold in the icons, a press
     that lights gold and gives a little under the thumb, and the state of each
     key said by the key itself - the camera rings when the picture is taken,
     the fullscreen corners turn inward while it is the way OUT, the eye shuts.
     22px of capsule in a 28px bar; the `::after` makes each target 38 x 44,
     and that reach is why the rail is the strip's SIBLING, not its child: the
     strip scrolls, and a scroller clips. */
  #chrome{display:grid; grid-template-columns:minmax(0,1fr) auto}
  #chrome>header{grid-column:1/-1; grid-row:1}
  #chrome>#strip{grid-column:1; grid-row:2; padding-right:12px}
  :root{--cap-key:38px}
  #chrome>#zoom{
    position:relative; top:auto; right:auto; z-index:5;
    grid-column:2; grid-row:2;
    width:auto; height:auto; padding:0 calc(8px + var(--sar)) 0 2px;
    flex-direction:row; align-items:center; justify-content:flex-end; gap:0;
    background:var(--panel); border-bottom:1px solid var(--line);
  }
  #chrome>#zoom::before{
    content:''; position:absolute; top:0; bottom:1px; right:100%; width:18px;
    background:linear-gradient(to right, transparent, var(--panel));
    pointer-events:none;
  }
  #chrome>#zoom .ind{flex:none}
  #chrome>#zoom .ind>button{
    position:relative; width:var(--cap-key); height:22px;
    background:var(--well); border:1px solid var(--line2); border-left-width:0;
    border-radius:0; color:var(--axis-text); opacity:1;
    transition:background .18s, color .18s;
  }
  #chrome>#zoom .ind:first-of-type>button{border-left-width:1px; border-radius:11px 0 0 11px}
  #chrome>#zoom .ind:last-of-type>button{border-radius:0 11px 11px 0}
  #chrome>#zoom .ind>button::after{content:''; position:absolute; inset:-11px 0}
  #chrome>#zoom .ind>button svg{transition:transform .26s cubic-bezier(.3,1.6,.5,1), opacity .2s}
  #chrome>#zoom .ind>button:active{background:var(--gold-soft); color:var(--gold-hi)}
  #chrome>#zoom .ind>button:active svg{transform:scale(.84)}
  /* A key that is ON says so in its own colour: the exit in gold while it is
     the way out, the eye in the warning orange while the marks are hidden. */
  #chrome>#zoom #fsBtn.on,#chrome>#zoom #snapBtn.on{background:var(--gold-soft); color:var(--gold-hi); box-shadow:none}
  #chrome>#zoom #marksBtn.on{background:var(--warn-soft); color:var(--warn)}
  /* The shutter: a ring that leaves the camera the moment the picture is
     taken - the press is answered before the work, the tick after it. */
  #chrome>#zoom #snapBtn.on{animation:capshot .55s ease-out}
  @keyframes capshot{
    from{box-shadow:0 0 0 0 rgba(248,231,176,.6)}
    to{box-shadow:0 0 0 9px rgba(248,231,176,0)}
  }

  /* ...AND IN FULLSCREEN THE BARS LEAVE TOGETHER AND THE CAPSULE DOES NOT.
     The header and the bar fade as one and come back as one on an edge tap,
     in their own order - exactly as before. The capsule is the way out of the
     mode (the fourth device round: visible and hittable from INSIDE it), so it
     stays: it glides from the end of the bar to the top of the chart, LEFT of
     the price axis, where it covers no number - and the price scale keeps a
     band under it clear of candles (`hudBand()` in layoutScales), so it covers
     no candle either. While the bars are peeked it glides back into the end
     of its own bar, so the peek looks like the page outside the mode. The
     glide is chrome.js's (`glideRail`); this is only where it lands. */
  body.phone.fs #chrome{opacity:1}
  body.phone.fs #chrome>header,body.phone.fs #chrome>#strip{opacity:0; transition:opacity .15s}
  body.phone.fs.peek #chrome>header,body.phone.fs.peek #chrome>#strip{opacity:1}
  body.phone.fs:not(.peek) #chrome{box-shadow:none}
  body.phone.fs:not(.peek) #chrome>#zoom{
    position:fixed; z-index:31;
    top:max(calc(var(--sat) + 6px), var(--rail-edge,12px));
    right:calc(var(--pad-right,54px) + 8px);
    padding:0; background:none; border:0; border-radius:11px;
    box-shadow:0 6px 18px rgba(0,0,0,.55), 0 0 0 1px rgba(0,0,0,.4);
  }
  body.phone.fs:not(.peek) #chrome>#zoom::before{display:none}
  body.phone.fs:not(.peek) #chrome>#zoom .ind>button{background:var(--panel2)}
  body.phone.fs #legend{
    max-width:calc(100% - var(--pad-left,0px) - var(--pad-right,74px) - 3 * var(--cap-key,38px) - 36px);
  }

  /* ---- the chart's own furniture ---- */
  /* 264px tall at this width - a third of the pane, over the candles, because
     it wraps. One line, and what does not fit ends in an ellipsis rather than
     being cut through the middle of a digit: a half-rendered price is worse
     than an absent one. The whole row is in the strip above anyway, which on a
     phone now scrolls, so nothing here is the only copy. */
  #legend{
    /* The bound that stops it at the price axis is in the BASE rule now - the
       desktop needed the identical one (a legend with no right bound runs
       under the scale and under the rail there too), and two copies of one
       formula is how the two screens start disagreeing about where the axis
       is. What is phone-only is the size and the clamp below. */
    font-size:10.5px; line-height:1.5;
    /* A little more of the pane than the base rule leaves it, so the bar's
       numbers make the one line on a five- or six-digit price. */
    max-width:calc(100% - var(--pad-left,0px) - var(--pad-right,74px) - 14px);
    /* ONE LINE, AND WHAT IS ON IT IS THE BAR. It was two lines for a while,
       because the full row is ~515px and one line ellipsised away the Close
       and the Volume; with the name and the timeframe taken off (they are in
       the dock - `.lid` below) the bar's own numbers fit one. Clamped rather
       than free: 264px of wrapped legend over the candles is what this
       started as. */
    display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical;
    white-space:normal; overflow:hidden;
    /* AND IT STOPS CUTTING WHATEVER PASSES UNDER IT.
       Those two lines are a 312px-wide slab on a 390px pane - a ground, a
       border AND an 8px backdrop blur - sitting in the upper third, which is
       where the price action usually is. On a desktop the legend is a small box
       in a corner; here it covers 80% of the width, so a line drawn across the
       chart comes out chopped in half and reads as a broken drawing. Reported
       exactly that way (Dor, 2026-09-17): *"I drew an arrow, but it is cut, it
       opens in the middle - you cannot see it to the end."* It was not the
       drawing: hiding the legend for one frame showed the ray continuous from
       end to end, and the candles behind it were being eaten the same way.
       THE BLUR IS THE PART THAT CANNOT BE ARGUED DOWN - at any alpha it erases
       a 1px line underneath - so the ground, the border and the blur all go
       here, and the text carries its own legibility in a shadow instead. The
       desktop is byte-for-byte unchanged: there the box is in a corner, and the
       ground is what makes it readable over candles. */
    background:none; border-color:transparent; padding:4px 6px;
    backdrop-filter:none; -webkit-backdrop-filter:none;
    text-shadow:0 1px 2px var(--bg-deep), 0 0 5px var(--bg-deep), 0 0 9px var(--bg-deep);
  }
  #legend .row2,#legend .avg{display:none}
  /* ...AND ONE LINE NOW, BECAUSE IT SAYS ONLY THE BAR (2026-10-06, Dor, a
     phone chart circled: the second line was the Volume cut to "54..."). The
     name stands in the dock and in the search box, the timeframe beside the
     name in the dock, so the line drops both (`.lid`) and the contract when it
     is the one the chart is on; what is left - O H L C and the
     Volume - fits one line, and a deferred contract under the crosshair is
     still named. */
  #legend .lid,#legend .lcode.same,#legend .lmark{display:none}
  /* The chevron fits the line's own height here, or the clamp's clip cuts it.
     Its own size is the desktop's less a little; what is pulled in is the
     EMPTY room either side of it (Dor, 2026-10-06: the gap between it and the
     figures read as a hole in the line) - 2px before, 3px after. */
  #legend:has(.tuck:not([hidden])){padding-left:2px}
  #legend .tuck{height:18px; min-width:22px; padding:0 4px; margin-right:3px; top:-1.3px}
  /* The press is bigger than the circle: the rest of the legend's height - none
     of which lengthens the line. */
  #legend .tuck::after{content:''; position:absolute; inset:-4px -3px -4px -2px}
  /* ...and the bar's own move, which is what buys the Volume its place on the
     one line beside the chevron: the day's move stands in the quote bar right
     above, and the move of one candle is the figure a phone can spare. */
  #legend .bmv{display:none}
  /* ...AND THE CHIP UNDER IT MOVES DOWN WITH IT. Two clamped lines at 11/1.5
     is 33px of text, 8px of padding and a top of 10: the legend ends at 51px
     here, where on a desktop it ends around 38. The stage chip is placed from
     that bottom edge and not from a constant, or it stands on the legend's
     second line - which is where the Close and the Volume are on this screen,
     the two the phone's legend was widened to two lines to keep. */
  #lights{--lights-top:40px}

  /* A TAG IS A CLAIM ABOUT LENGTH, AND AT 390px THE LENGTH THAT QUALIFIES IS
     SMALLER. `nowrap` with no max-width is right on a desktop and is argued
     for in the base rule: a tag sizes to its own text so its ground ends where
     its words do, and one that wraps has become a box again. But the analyst
     writes the level's reasoning into the level's text, and measured here the
     context tag on SOFI - "כניסה בתמיכה: 6× ברמה 17.26 + רצפת הטריז" - came
     out 344px wide in a 330px usable pane: off the left edge, under the price
     scale on the right, and centred on the wrong bar, because place() centres
     on offsetWidth and offsetWidth was wider than the chart.

     So at this width a tag is bounded - 42% of the pane, and never past the
     price scale - and what does not fit ends in an ellipsis. Short tags are
     untouched: they still size to their own text, and a price with a touch
     count is what the rule was written for.

     ONE LINE AND AN ELLIPSIS RATHER THAN TWO LINES AND ALL THE WORDS, because
     the brief here is that the CANDLES stay readable: a wrapped caption is
     twice the chart covered, and covering the chart is the complaint. It is a
     fold and not a loss, which is the difference the board's `more` chip is
     held to - the ellipsis says there is more and the Marks list in the D
     panel is the more, one tap away, in full, with no chart under it (rule 1).

     The ground is the box's rather than the tag's: `--ovl-tag` is 72% and a
     clipped caption over candles has to be read at a glance. It also keeps
     offsetWidth honest - place() centres a tag on its own width, and an
     unbounded one is centred on a width wider than the chart. */
  .o-note.tag,.o-label.tag{
    /* THE PRICE SCALE IS NO LONGER RESERVED. It was `100% - --pad-right`, which
       held 60px - 18% of a 330px pane - back from the one thing on this screen
       that is short of room, to protect numbers the captions mostly carry
       themselves ("18.88 · 4x" over the `18.9` it has just covered). Dor's
       call: the words may sit on the axis numbers. overlay.js's `textRight`
       is the other half, and it keeps every PICTURE off the scale - a drawing
       over the axis is a drawing at the wrong price.
       The cap and the clip are in the base rule now, and so is the 44% - it
       is computed in capTagPx() with everything else that decides a caption's
       width, because two places deciding one width is how they drift. What is
       left here is the ground and the padding. */
    background:var(--ovl-box); padding:2px 5px; border-radius:4px;
  }

  /* ...AND THE ARMED FLAG IS BOTH THE READOUT AND THE TARGET, so it is the one
     caption here that gets BIGGER rather than smaller. At the tag's 9.5-10px it
     was a chip a thumb cannot land on and, before it carried its price, a
     single glyph nothing on this device could interrogate - there is no hover
     on a phone, which is the whole of *on the phone you see nothing*. The
     padding is what buys the target: it is the only control on the candles
     besides the board, and everything else that a finger presses on this
     layout is 40px or more. */
  .o-label.aflag{
    font-size:10.5px; padding:5px 7px; border-radius:6px;
    background:var(--ovl-box);
  }

  /* The board is prose, and at 34% of 390px it was a 133px column - one or two
     words a line - truncating SILENTLY against a 400-character cap, which is
     text stored and not shown (rule 1). A floor in px, because this is the one
     mark that is measured in screen space by design and the only one entitled
     to be. Capped at a third of the pane so it cannot bury the candles it is
     about, and it SCROLLS at that cap rather than clipping: the words are
     always all reachable. */
  .o-board{
    min-width:min(230px,calc(100% - var(--pad-right,0px) - 24px));
    max-width:calc(100% - var(--pad-right,0px) - 24px);
    max-height:34%; overflow:auto; overscroll-behavior:contain;
  }
  /* AND IT OPENS FOLDED HERE, WHICH IS NOT THE SAME AS OWNING THE FOLD. Seven
     lines of prose over the candles they are about is most of a phone's chart -
     "the text is almost the entire screen", on the device - so entering this
     layout forces it on. The RULES that fold it are in the base stylesheet
     beside `.o-board`, because the fold is a switch on every screen now and it
     is true of the state rather than of the width; chrome.js asks the page to
     set it rather than writing the class. A tap opens it, and so do B and the
     `Full board` row in the D panel. */

  /* THE CARD A SYMBOL OPENS TAKES THE WIDTH, axis and all - it is read, not
     aimed with, and on 390px the scale's 60px is a sixth of the sentence. */
  .pinread{left:10px; right:10px; max-height:min(58%, 440px, calc(var(--pr-room, 100vh) - 20px))}
  .pr-body{font-size:17px; line-height:1.55}
  .pr-nav button{width:56px; height:40px}

  /* ---- the transients ---- */
  #msg .box{max-width:min(520px,100%)}
  #msg code{word-break:break-all; white-space:pre-wrap}
  .r-tag{max-width:calc(100vw - 32px)}
  .newsread .nwbox,.macroread .mbox,.earnread .ebox{width:min(420px,calc(100% - 24px))}
  /* The alert reading is the one of the four that is deliberately NOT widened
     to the screen: it is four short lines, and a box the width of the phone to
     hold "2,600.00, armed, 1.8% away" is the `A` window again by another road,
     which is the thing that was wrong with pressing an alert in the first
     place. It only stops being narrower than a thumb. */
  .alertread .abox{max-width:min(300px, calc(100% - 24px)); min-width:160px}

  /* ---- fullscreen, on a phone ----
     THE TOOL BAR STAYS. The first version hid the dock along with the header
     and floated it back on a `peek`, on the reasoning that "the whole screen"
     should mean the whole screen. That made fullscreen a ONE-WAY DOOR: the
     button that leaves it is IN the dock, so once the 1.8s peek timed out the
     way out was invisible and `pointer-events:none`, and the only escape was a
     tap near an edge that nothing on screen mentions. Dor could not get out of
     it, and he wrote the app.

     A mode you cannot see how to leave is broken however much screen it wins,
     and the arithmetic was never on its side: the dock is 48px of 844, 5.7%,
     against the header and strip's 72px, which is 8.5% and costs nothing to
     hide because nothing in them is a way back. So fullscreen here hides the
     chrome and keeps the bar. (`#fsBtn` sat IN the dock when that was written,
     wearing `.on`; it is in the rail now, and the block below is what keeps it
     reachable. The dock stays because the chart tools in it are worth 48px,
     not because the exit is there any more.)

     The footer stays for the same money: 22px, and it carries the TradingView
     credit, which is a LICENCE condition (web/vendor/NOTICE) rather than
     decoration. Taking it off the screen for the whole of a mode that is about
     to be the normal way to look at a chart is not a trade worth making. */
  body.fs #dock{position:relative; opacity:1; pointer-events:auto; box-shadow:none}

  /* ...AND THE FOOTER GOES WITH THE REST OF THE CHROME. Dor, from the phone:
     "all this strip is not needed in full screen, the chart should replace
     that location". It is the last band standing between the dock and the
     bottom of the device, and everything in it is a fact about the DEVICE
     rather than about the chart - reachable the instant you leave the mode.

     IT WAS KEPT HERE UNTIL NOW BECAUSE IT CARRIES THE TRADINGVIEW CREDIT,
     which web/vendor/NOTICE records as how the linking requirement is met with
     the library's own logo switched off. Two things settle it. The DESKTOP has
     hidden this whole row in fullscreen since the mode was built
     (`body.fs footer{display:none}`, outside every media query), so the rule
     here was the exception rather than the guarantee - a licence condition
     honoured on one layout and not the other is not being honoured. And the
     NOTICE's own test is that the link is seen by every user of the page: the
     default view carries it, and fullscreen is a mode you enter and leave.
     Dor's call, and the thing to change if it is ever challenged is the
     NOTICE's wording, not this rule quietly coming back. */
  body.fs footer{display:none}

  /* ...AND IT BUYS ITS REMAINING ROOM FROM THE CHROME, NOT FROM THE CREDIT.
     Dor, on the phone: the bar can be smaller in fullscreen and the device's
     own state should not be on screen at all. Both are right, and they are the
     same argument - fullscreen is a MODE FOR READING THE CHART, so every pixel
     in it should be either chart or a way back to one.

     The dock keeps its job and loses its padding: 40px targets instead of 48
     is still comfortably over the 44px Apple asks for once the row's own gaps
     are counted, and it is worth 8px of an 844px screen.

     The footer keeps ONLY the credit. "IBKR connected" and the notifications
     link are facts about the DEVICE - true whatever is on the chart, reachable
     the moment you leave the mode - while `Charting by TradingView` is a
     LICENCE condition (web/vendor/NOTICE) and is the one thing in the row that
     is not allowed to go. So the row survives at 16px carrying the one item
     that has to be there, which is both what Dor asked for and the most that
     can be given. */
  body.fs #dock{height:calc(40px + var(--sab))}
  /* AND THE FOOT OF THE DOCK GOES WITH IT, which is the same call as the one
     that took the whole footer band out of fullscreen on 2026-09-13: the mode
     is entered deliberately and left the same way, and web/vendor/NOTICE's
     test is that the link is in the DEFAULT view. Nothing changes about that -
     the line the credit is on has moved, not the rule. */
  body.fs #dockfoot{display:none}
  body.fs #dock .ind>button{width:34px; height:34px}
  body.fs #dock .ind>button svg{width:17px; height:17px}
  body.fs .tfs.compact button{height:34px; min-width:34px}
  body.fs #dock .dname{height:34px}
  /* The dock is the bottom of the device now, so it owns the home indicator's
     inset rather than borrowing it from the row that used to be under it. */
  body.fs #dock{padding-bottom:var(--sab)}


  /* ---- the footer: the band is gone, and it took a doubled inset with it ----
     Dor, from the phone, with this row circled: "I want to free up more room in
     the phone screen for the chart - the IBKR advert is not needed at all, it
     is already at the top. Put the TradingView credit and the notifications
     switch on the bar above, where the timeframe and the indicators are."

     Measured at 390x844 before: header 44, strip 28, chart 702, dock 48,
     footer 22. The row is worth more than its 22px, because on a real iPhone
     it ALSO pays `var(--sab)` - and so does the dock directly above it, which
     is the same ~34px of home indicator reserved twice for the sake of one
     22px line. The credit and the bell go up to the dock's own foot (14px),
     everything else in the row is already said elsewhere or says nothing, and
     the band goes.

     IT IS GATED ON `body.phone`, WHICH IS CHROME.JS'S OWN CLASS, and that is
     the licence talking rather than tidiness: the same file that writes the
     class is the one that moves the credit into the dock, so there is no state
     of the world where this rule hides the link and nothing has carried it
     anywhere. With scripting off the footer is exactly what it always was. */
  body.phone footer{display:none}
  /* ...UNLESS SOMETHING IS BROKEN, and then it is back for as long as it is.
     `IBKR connected` is an advert; `IBKR bridge offline`, `signed out` and
     `session taken by another login` are three different failures with three
     different fixes, and this row is the only place they are told apart (rule
     6). It is written by paintIbkr() on every check, so the band appears the
     moment the state goes bad and leaves the moment it is good again. */
  body.phone.ibkr-bad footer{display:flex}
  footer{
    height:auto; min-height:22px; padding:2px calc(10px + var(--sar)) calc(2px + var(--sab)) calc(10px + var(--sal));
    font-size:9.5px; gap:8px; flex:none; color:var(--dim);
  }
  /* A FOOTNOTE, SO IT IS SET AS ONE: the caption size, the dim ink, the ways
     out a step brighter, and the command in its mono well a size smaller than
     the words around it. The line is longer than a phone - it names the
     fault, the command and the way out - so its end FADES rather than being
     cut through the middle of a word. */
  footer a,footer .lnk{color:var(--muted)}
  footer code{font-size:9px; padding:0 5px; line-height:15px}
  footer #ibkr{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent);
               mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent)}
  footer .dot{width:6px; height:6px}
  /* The timezone note is the one line here that is standing documentation
     rather than state - it says the same thing on every chart, every day. The
     IBKR state and the credit both stay: one is whether the data is real, the
     other is a licence condition.
     nth-of-type counts among SPANS, and the separators are spans too: the row
     is Binance(1) sep(2) ibkr(3) sep(4) timezone(5) credit(6). */
  footer>span:nth-of-type(5),footer>span:nth-of-type(4){display:none}
  /* And "Binance direct" goes with it - the same kind of line, true on every
     chart, every day. Its separator (2) goes too, or the row opens on a dot.
     Both rules now only ever fire in the broken state above, where what is
     left is the one span that is saying something: the IBKR state and its way
     out. The credit is not in this row on a phone at all. */
  footer>span:nth-of-type(1),footer>span:nth-of-type(2){display:none}
  /* ...AND THE WARNING RIDES IN THE BAND THE DOCK ALREADY PAYS FOR, INSTEAD OF
     BUYING A ROW UNDER IT (2026-09-28). As a row it cost 22px plus the home
     indicator's inset a SECOND time, and it pushed the dock - and everything
     that opens off the dock - up by all of it: Dor, from the phone, "it pushed
     everything up", with the timeframe ladder opening its second row under the
     dock. The band above the home indicator is paid for already and its left
     side is empty, because the credit sits in the right-hand corner; so the
     warning is laid into that band as one line, and the chart keeps its height
     whether the connection is good or bad. Out of the flow, so nothing under
     it moves, and the right-hand padding leaves the credit its corner.
     ANCHORED TO THE DOCK, NOT TO THE SCREEN: it stays in the flow directly
     under the dock and its margins pull it up into the band's first line for
     a net height of zero, so it lands on the credit's own line whatever the
     viewport is doing - fixed to the viewport it sat 8px low the moment the
     page was laid out a hair wider than the glass. By its id, because
     `#foot{position:relative}` near the board's pace rules outranks a class. */
  /* The warning needs a readable line in the band, which the band no longer
     keeps for anything else. */
  body.phone.ibkr-bad{--footband:max(var(--sab), 15px)}
  body.phone.ibkr-bad #foot{
    position:relative; z-index:13;
    height:15px; min-height:0; overflow:hidden;
    margin:calc(-1 * var(--footband)) 0 calc(var(--footband) - 15px);
    padding:0 calc(124px + var(--sar)) 0 calc(10px + var(--sal));
    line-height:15px; align-items:flex-start;
    background:none; border-top:0;
  }
  /* Fullscreen takes the band away (`body.fs #dockfoot{display:none}`, and
     the credit with it), so there is no band for the warning to ride in: it
     goes with the band rather than landing on the dock's buttons, and it is
     back the moment the mode is left. */
  body.phone.fs.ibkr-bad #foot{display:none}

  /* ---- the three windows ----
     Every breakpoint in them already fires; the smallest authored branch was
     640px (#cal) and 600px (#report) against windows that compute to 346px and
     374px, so they were tuned for twice the width they get. */
  /* A WINDOW IS MODAL, SO IT IS THE TOP OF THE SPINE - AND IT WAS THE BOTTOM.
     All three declare `aria-modal`, all three are mounted on #wrap, and #wrap
     is `position:relative; z-index:auto` with no clipping - so their z-index:6
     competed directly against #wrap's own SIBLINGS and lost to every one of
     them: the analyst sheet (10), the drawer's scrim (11), the drawer (12) and
     the dock (13). On a desktop those are columns beside the chart and nothing
     overlaps, which is why this survived; on a phone the sheet is standing
     furniture over the chart at whatever height it was left, so the window
     opened UNDERNEATH it. Measured at 390x844 with the sheet at `full`: 4 of
     35 sampled rows of the window reachable, the whole list, every arm/disarm
     button and the arming form dead - and in landscape the analyst is a column
     down the RIGHT, which is the edge the x sits on. Reported from the phone:
     "the bell alert group button is not escapable once I open him."

     `fixed`, not a bigger z-index, because the z-index alone leaves the header
     and the dock live in front of a modal - press the analyst button and the
     sheet opens behind it, the same bug mirrored. Fixed to the VIEWPORT makes
     the scrim the whole screen, so every tap outside the window closes it and
     the way out is the entire rest of the phone.

     The desktop is untouched: there #watch and #chat are columns in flow, the
     windows genuinely are about the chart, and covering the pane alone is the
     right behaviour. */
  #optv,#report,#cal,#alerts{position:fixed; z-index:14}
  /* AND THE INSET IS COUNTED ONCE. `top:calc(6px + var(--sat))` was measured
     against #wrap, which already starts below a header carrying the inset - so
     the window was pushed down by the notch twice and hung 53px below the pane,
     under the dock. Against the viewport it is counted where it is real, and
     the same goes for the bottom: `100% - 12px` is the SCREEN now, so the
     window has to give back what the notch and the home indicator take. */
  #optv .win,#help .win,#report .win,#cal .win,#alerts .win{
    width:calc(100% - 12px - var(--sal) - var(--sar));
    top:calc(6px + var(--sat));
    max-height:calc(100% - 12px - var(--sat) - var(--sab));
  }
  /* A WINDOW HERE IS A SHEET, SO IT IS THE HEIGHT OF ONE whatever it holds: a
     short window floating in the middle of the screen with the chart's top
     and bottom showing round it read as a dialog, and these are pages. */
  #optv .win,#report .win,#cal .win,#alerts .win,#help .win{min-height:calc(100% - 12px - var(--sat) - var(--sab))}

  /* ---- what this does, on a phone ----
     Three cards side by side is three columns of 110px; the same three stacked
     is the same three. The window keeps its own z-index (8000) rather than
     joining the spine at 14: it is not about the chart, so nothing on the page
     may be in front of it. */
  /* THE TITLE BAR WRAPS AND THE CLOSE BUTTON COMES OUT OF THE FLOW, which is
     #cal's own answer and the fix #report had to be given twice: a nowrap row
     holding a title, two tabs and an x wants ~430px and has 378, so the x - last
     in source, flex:none - is the piece that leaves the screen, and it is the
     way out. Measured clipped at 390px before this. */
  #help .wbar{flex-wrap:wrap; height:auto; min-height:46px; padding:7px 46px 7px 14px; row-gap:6px}
  #help .wbar .x{position:absolute; top:8px; right:10px; margin:0}
  #help .wbar h2{font-size:13px}
  #help .wbar .htab{padding:0 10px}
  #help .wbody{padding:16px 14px 18px}
  /* The three columns of type stack, and the rule between them turns from
     standing to lying down. */
  #help .hgrid{grid-template-columns:1fr; gap:0}
  #help .hcard{padding:14px 0}
  #help .hcard + .hcard{padding-left:0; border-left:0; border-top:1px solid var(--doc-line)}
  #help .hlead{font-size:14.5px}
  #help .hfoot{padding:11px 14px calc(11px + var(--sab)); gap:10px; flex-wrap:wrap}
  #help .hfoot span{flex:1 1 100%}
  #help .hfoot button{width:100%}
  #help .hacts button{flex:1 1 auto; min-width:0}
  /* Three chips share the row the width of the sheet, so each is a third of
     ~350px - wider than any thumb - and 40px tall, the dock's own target. */
  #help .hchips{flex:1 1 100%; flex-wrap:nowrap}
  #help .hchip{flex:1 1 0; min-width:0; height:40px; padding:0 6px; font-size:12.5px; border-radius:var(--r-sm)}
  /* The one destructive control in this window pays for a thumb, at the
     dock's own 40px - and the kind tag gives way rather than the line, which
     is the thing being read before deciding. */
  #help .hlgone{height:40px; padding:0 14px}
  #help .hlkind{display:none}
  /* The release is a link and a link is not a tap target: on a phone it gets
     its own line under the name and a thumb's worth of height around it,
     rather than sitting 11px tall between two other pieces of text. */
  #help .hropen{display:block; margin-top:6px; padding:6px 0; font-size:12.5px}
  /* THE QUESTIONNAIRE ON A PHONE. The window is already a bottom sheet here;
     what has to change is that the two ways on are thumb-sized and that a
     250-word option card does not become a page of its own. */
  #help .qq{font-size:14.5px}
  #help .qopt{padding:12px 13px; border-radius:var(--r-lg)}
  #help .qoptw p{font-size:12.5px; line-height:1.6}
  #help .qacts button{height:40px; flex:1 1 auto}
  #help .qacts button.gold{flex:1 1 100%}
  /* The switch is a thumb target like everything else down here - the dock's
     own 40px. It is the one control on this screen a person reaches for
     BEFORE they can read anything, so it is the last one to be fiddly. */
  #help .qlangb{height:40px; padding:0 16px}
  /* ...and the undo on the level notice pays for a thumb too, at the dock's
     own 40px. It is the way back from a change nobody asked for, so it is
     exactly the control that must not be fiddly (§9). */
  .clog .rnotice .rnundo{height:40px; padding:0 16px; font-size:12.5px}

  /* ---- the offer of a fresh start, on a phone ----
     Two buttons that decide whether a conversation lives: they pay for a thumb
     (44px, the dock's own rule) and they stack, because side by side at 390px
     the one that throws the thread away is the one under the thumb's natural
     rest. Reload leads all the same - it is the recommendation, and reading
     order is not reach order.

     THE SHEET IS THE BOUNDS, AND THE SHEET CAN BE 100px TALL. At `at-peek`
     there is room for the composer and nothing else, so the layer scrolls
     rather than clipping the answer off its own question - `align-items` gives
     way to the top, because a card taller than its scroller centred is a card
     cropped at BOTH ends. The safe area at the foot is the sheet's own and is
     already paid by `.cbar`; what this owes is the room the home indicator
     takes under a layer that covers it. */
  /* ⚠ `#chat #newchat`, NOT `#newchat`, AND THAT IS THE WHOLE FIX. The base
     rules sit at the END of this file, after this block, at the same weight -
     so every line here lost to them by source order and none of this was ever
     on a phone: the card stayed centred (cropped at both ends at `peek`) and
     the two answers were thin strips, because `flex:1 1 0` is a zero BASIS on
     a column and the 44px is never reached. One id heavier, and `flex:none`.
     The layer starts under the sheet's own head (it covers the whole panel,
     the head is drawn over it), so its top padding clears the head; and it is
     `safe center` - centred where the card fits, from the top where it does
     not, which is the "give way to the top" asked for above without putting
     the card's title under the head at `half`. */
  #chat #newchat{align-items:flex-start; align-items:safe center; padding:52px 10px calc(10px + var(--sab))}
  #chat #newchat .card{max-width:none; padding:16px 16px 14px}
  #chat #newchat .nacts{flex-direction:column; gap:8px; margin-top:14px}
  #chat #newchat .nacts button{height:44px; font-size:13.5px; flex:none}
  /* THE BRAND IS THE ONLY DOOR TO IT HERE - the footer band is gone on a phone
     - so it pays for a thumb. Grown by padding, like the bell, so the 44px row
     does not grow with it. */
  /* `button.brand` and not `.brand`: a media query adds no specificity, and the
     reset above is element+class, so a bare class here loses to it. Measured at
     20x20 before, which is a quarter of the thumb aiming at it. */
  button.brand{padding:12px 8px; margin:-12px 0 -12px -8px}
  /* A suggestion is a thing you tap, so it takes a tappable height. */
  .cstart .chips button{height:32px; padding:0 13px}

  /* ---- ...AND A WINDOW IS PUSHED AWAY THE WAY THE DRAWER IS ----
     The calendar and the alerts fill the screen here, so the ✕ in the corner
     and the sliver of scrim around them were the whole way out - both of them
     at the top of an 844px screen, with the thumb at the bottom of a list.
     The gesture is the drawer's, so the machinery is the drawer's too: while a
     finger is pushing, the transform is written per frame and there must be no
     transition racing it; let go past the threshold and the same transform is
     handed to one and the window finishes the journey by itself.
     `-100vw`, not `-100%`: the window is centred rather than flush left, so a
     hundred percent of its own width leaves it on screen. See armSwipeClose()
     in chrome.js. The ✕ is untouched - the swipe is a second way out. */
  /* THE OFFSET IS A VARIABLE BECAUSE THE TRANSFORM IS ALREADY SPOKEN FOR:
     `.win` is centred with `left:50%; translateX(-50%)`, so writing a
     transform per frame would throw the centring away and the window would
     jump half its own width to the right on the first pixel of the gesture. */
  #optv .win,#help .win,#cal .win,#alerts .win{
    transform:translateX(calc(-50% + var(--swipe, 0px)));
    transition:transform .15s var(--ease); touch-action:pan-y;
  }
  #optv .win.swiping,#help .win.swiping,#cal .win.swiping,#alerts .win.swiping{transition:none; will-change:transform}
  /* AND THE BLUR COMES OFF WHILE IT MOVES. `backdrop-filter` re-blurs the whole
     screen behind the scrim on every frame its opacity changes, which is the
     one thing on this path that costs a full-screen composite - measured as the
     drag stuttering while the window itself was perfectly promoted. It is back
     the moment the finger lets go, and the eye never sees it leave under a
     window that is travelling over it (rule 5). */
  #optv .scrim.swiping,#help .scrim.swiping,#cal .scrim.swiping,#alerts .scrim.swiping{
    transition:none; backdrop-filter:none; -webkit-backdrop-filter:none;
  }
  #optv .win.closing,#help .win.closing,#cal .win.closing,#alerts .win.closing{--swipe:-100vw}
  #optv .scrim,#help .scrim,#cal .scrim,#alerts .scrim{transition:opacity .15s var(--ease)}
  /* The window leaves the screen and nothing follows it - and the whole
     overlay hands us the horizontal while keeping the list's own scroll,
     because the gesture starts anywhere on it and not only on the window.
     ⚠ AND `#help` WAS LEFT OUT OF IT, which is the one line that made the
     gesture answer differently on the three windows it is armed on: the
     what-this-does window is armed by `index.html` exactly as the other two
     are, and measured `touch-action:auto` at the root against their `pan-y`,
     so a press that started anywhere but on `.win` itself was the BROWSER'S
     to take - which it does readily, and what it hands back mid-drag is a
     `pointercancel`. Three windows, one gesture, one rule. */
  #optv,#help,#cal,#alerts{overflow:hidden; touch-action:pan-y}
  /* #cal wraps its title bar and pins its ×; #report never got either, so at
     374px its × - last in source, flex:none, in a nowrap row that wants 475px
     - was clipped, leaving an 8px strip of scrim as the only way out. */
  #report .wbar{flex-wrap:wrap; height:auto; min-height:46px; padding:6px 46px 6px 14px}
  #report .wbar .x,#cal .wbar .x{position:absolute; top:8px; right:10px; margin:0}
  #report .wbar h2{font-size:14px}
  /* The container query at 720 sets .who{flex:none}, taking away the very
     ellipsis escape that would have saved the row. Give it back. */
  #report .wbar .who{flex:0 1 auto; min-width:0}
  /* Paper and Copy lose their labels at 720 and Save PDF does not. */
  #report .wbar .pdf .lbl{display:none}
  #cal .wbar{padding-right:44px; gap:6px}
  /* .ctrls was one unshrinkable item, so flex-wrap could not break it up -
     wrapping only happens BETWEEN items, and `flex:none` made it an item that
     could not be broken. Let it take a row and wrap inside itself. */
  #cal .wbar .ctrls{flex:1 1 100%; flex-wrap:wrap; min-width:0; gap:6px}
  #cal .wbar select{flex:1 1 calc(50% - 3px); min-width:0}
  /* The bar wrapped to four rows and 180px of a 690px window. The title is the
     one thing in it carrying no state - the × and the contents both say what
     this is - and the flexible spacer exists only to push things apart on a
     row wide enough to have a middle. */
  #cal .wbar h2{display:none}
  #cal .wbar .sp{flex:0 0 0; display:none}
  #cal .wbar .nav,#cal .wbar .span,#cal .wbar .views{flex:0 0 auto}
  /* Copy is an action on the window, like the ×, not a filter - so it rides
     beside it instead of taking a 36px row of its own. */
  #cal .wbar .copy{position:absolute; top:8px; right:46px; margin:0}
  #cal .wbar{padding-right:84px}

  /* #alerts was authored to wrap from the start, so it needs only what the
     others needed: the title out of the way of a bar that is already two rows,
     and the arming forms as full-width fields rather than a row of stubs. A
     120px price input beside three selects and a note is 470px of 366. */
  #alerts .wbar h2{display:none}
  #alerts .watch{white-space:normal; flex:1 1 100%; line-height:1.5}
  #alerts .device{flex:1 1 100%}
  #alerts .anew .np{flex:1 1 100%; width:auto}
  #alerts .anew select{flex:1 1 calc(50% - 3px)}
  #alerts .anew .nn{flex:1 1 100%}
  #alerts .anew .go{flex:1 1 100%}
  /* The row's actions are the only targets in this window and they are 24px.
     A list you can read and cannot press is not a list. */
  #alerts .aacts button{height:34px; padding:0 12px; font-size:12px}
  #alerts .aacts{margin-left:-12px}

  /* ---- the glossary's definition ----
     ANCHORED TO A WORD IS NOT A PLACE HERE. The strip is a sideways scroller
     at this width and the analyst's sheet is the bottom half of the screen, so
     a 320px box beside the word it explains is a box half off the edge, or
     under the thumb that opened it. It sits above the dock instead, the width
     of the screen, and index.html writes no inline coordinates while
     `body.phone` - the same rule the rail's inline `right` is cleared by.
     Bounded at 45vh and scrollable, because a definition is a paragraph and a
     paragraph over the chart is the caption problem again. */
  #glpop{
    left:8px; right:8px; top:auto; width:auto;
    bottom:calc(42px + var(--footband) + 10px);
    max-height:45vh; overflow:auto;
    font-size:14px; line-height:1.6; padding:14px 18px 16px;
  }
  #glpop .gname{font-size:17px}
  /* ...AND A WORD IN THE ANALYST'S ANSWER OPENS IT INSIDE THE SHEET, under its
     head and over the transcript, so the sheet carries it up and down and a
     sheet lowered to `peek` takes it out of sight with the conversation.
     `--gl-top` and `--gl-bar` are measured once at opening (index.html,
     `placeGlossary`); the height in between is the sheet's, so a drag needs no
     write. One step more specific than the rule above, which it replaces. */
  #chat > #glpop{
    position:absolute; left:8px; right:8px; bottom:auto; top:var(--gl-top, 48px);
    max-height:min(45vh, calc(100% - var(--gl-top, 48px) - var(--gl-bar, 64px)));
    z-index:4;
  }
  #chat.at-peek > #glpop{display:none}
  /* The formation's drawing gives way first: at the screen's width it would
     stand ~150px tall and push the words past the 45vh bound, and the words
     are the definition - the picture illustrates them. */
  #glpop .gshape{--gs-max:104px}
  /* Nothing to change on a phone: the word is the target, and it is already
     the same gold at the same weight. */
}

/* ============================================================================
   A FINGER, WHICH IS A DIFFERENT QUESTION FROM A NARROW WINDOW
   ========================================================================= */
@media (pointer:coarse){

  /* iOS zooms the WHOLE PAGE when a control under 16px takes focus, and there
     is no way back out of that zoom except a pinch. Every one of these was
     under it: #q 13, #chatIn 13, .wadd input 12, the panel's 23 number and
     select controls 11-11.5, the calendar's four selects 11.5. */
  #q,.cbar textarea,.wadd input,.wnewform input,
  .panel .ctl input[type=number],.panel .ctl input[type=text],
  .panel select,#cal .wbar select,select{font-size:16px}
  .panel .ctl input[type=number]{width:76px; height:30px}
  .panel .ctl input[type=text]{height:32px}
  .panel select,#cal .wbar select{height:32px; min-width:0}
  .wadd input,.wnewform input{height:34px}
  #q{height:34px; padding:0 10px 0 31px}
  #q:not(:focus){font-size:16px}
  .finder::before{left:11px}
  /* ...and the settings inside the list of indicators, which are the same
     controls in a second place and zoomed the page on the first press inside
     Pearson's settings (Dor, 2026-10-05). Spelled out at the list's own weight,
     or its 11.5px rule outranks this one. */
  #lights .layrow .lbox .ctl input[type=number],#lights .layrow .lbox .ctl input[type=text],
  #lights .layrow .lbox .ctl select{font-size:16px}

  /* THE STAGE CHIP: A FINGER'S TARGETS, AND NO BLUR OVER THE CANDLES.

     The blur is the half that is not a preference. `#legend` gave up its
     ground, its border and its 8px backdrop blur on a phone because **at any
     alpha a backdrop blur erases a 1px line underneath it** - Dor drew a ray
     and it came out chopped in half, and it was the legend eating it, not the
     drawing. This chip sits in the same corner over the same candles and takes
     the same rule. What it KEEPS is an opaque ground, which the legend could
     not: a legend is a 312px slab and this is a 24px pill, and a pill that
     occludes a few pixels honestly is readable where one that smears the line
     across its whole width is both unreadable and a lie about the chart.

     The targets are the dock's, not the rail's. These are rows in a disclosure
     list rather than controls sitting alone on an axis, and eight of them at
     44px is 352px - the panel-over-the-candles this whole feature exists to
     prevent, arriving by the back door. 34px with an `::after` to 44 is the
     shape the tick rows already use: the target is bigger than the ink.

     ⚠ NO `position:relative` ON `#lights` ITSELF. It is already `absolute`,
     and re-declaring it static-relative un-places it - the trap the tick rows
     and `.wrow .x` fell into. The buttons inside are static flex children, so
     they are the ones that take it. */
  #lights button{height:34px; padding:0 12px; position:relative}
  #lights button::after{content:''; position:absolute; inset:-5px -2px}
  #lights .head{-webkit-backdrop-filter:none; backdrop-filter:none;
                background:rgba(16,20,27,.94)}
  #lights .lt{-webkit-backdrop-filter:none; backdrop-filter:none;
              background:rgba(16,20,27,.94)}
  #lights .all button{height:28px; padding:0 10px; font-size:11px}
  /* The cross is a tap target like everything else here, and it does not wait
     for a hover that a finger never produces. */
  #lights .del{width:34px; opacity:1}
  #lights .lt.sub{height:30px; font-size:11px}
  /* The list of indicators, for a finger: rows 30px with the `::after` above
     taking each control to ~40, the gear and the cross always there - a finger
     has no hover to find them with. Solid ground like the head: the blur
     smears the lines passing under it on this screen. */
  #lights .layrow .lrow{height:32px; -webkit-backdrop-filter:none; backdrop-filter:none; background:rgba(16,20,27,.94)}
  #lights .layrow .lrow button{height:28px}
  #lights .layrow .lhead{-webkit-backdrop-filter:none; backdrop-filter:none; background:rgba(16,20,27,.94)}
  #lights .layrow .lrow button.ll{padding:0 6px}
  #lights .layrow .lrow button.ledit,#lights .layrow .lrow button.ldel{width:30px; opacity:1}
  #lights .layrow .lbox{max-width:calc(100vw - 90px)}
  #lights .layrow .lbox .ctl button,#lights .layrow .lbox .ctl input[type=number],
  #lights .layrow .lbox .ctl select{height:30px}

  /* ...AND THE STALENESS PILL, which is the same sentence one day later:
     "the reload button does not press well, and sometimes it presses the
     things behind it" (Dor, 2026-09-16). The second half was PANE_CHROME -
     the pill sits over the pane, so the press reached the chart underneath.
     This is the first half: `Reload` was 28px tall and the dismiss 24px,
     against the 40px this app is built on everywhere else. */
  /* `body #fresh`, one step heavier than the base rules, which sit at the END
     of this file and so won every line of this by source order - the dismiss
     stayed a 24px target on the phone all along. */
  /* AND ON A PHONE IT IS OVER EVERYTHING, THE ONE NOTICE THAT IS (Dor,
     2026-10-06): inside the pane it was under the analyst's sheet (11) and the
     lists' drawer (12), so on a phone - where those two are most of the
     screen - the offer to reload was mostly invisible, and a page nobody
     reloads runs yesterday's code. Fixed to the viewport, under the header
     and the quote bar, and above EVERY layer - the dock, the windows, the
     help window (8000) and the glossary's card (8500). It still only exists
     while there IS a newer copy, and its ✕ still puts it away. */
  body.phone #fresh{
    position:fixed; z-index:9000; bottom:auto;
    top:calc(var(--sat) + 84px);
  }
  body.phone.fs #fresh{top:calc(var(--sat) + 12px)}
  body #fresh{padding:6px 6px 6px 14px}
  body #fresh button.gold{min-height:40px; padding:0 16px}
  body #fresh button.x{width:40px; height:40px; font-size:14px}

  /* THE COLLAPSED TRACE IS A THING YOU TAP, so it takes a tappable height.
     It is also the one row in the transcript with no hover to say it is a
     control, so the chevron is the whole signal and it does not dim. */
  /* A PHONE READS THE ANSWER AT ARM'S LENGTH: 13px was the desktop's margin
     note and was hard to read on the device. The trace stays small - it is
     glanced at - and only the conversation itself grows. */
  .clog{font-size:16px; line-height:1.7; gap:16px; padding:16px 14px}
  .clog .note{font-size:13px}
  .clog .run .rbar{padding:10px 10px; min-height:40px}
  .clog .run.live .rchev{opacity:.55}
  .clog .run .rstep{padding:5px 10px}

  /* THE TWO RAILS ARE 23px WIDE ON A FINGER, WHICH IS THE WHOLE BUG.
     "The button for taking a picture of the chart, with the phone on its side,
     does not press properly and does not respond well" (Dor, 2026-09-15).
     Measured at 932x390 in landscape: `--pad-right` is 48px, the rail shares it
     between snap and fullscreen, and each one comes out **23 x 32** with 2px
     between them. So a tap is not merely hard - the miss has somewhere to land,
     and that somewhere is the OTHER control: aiming at the camera and leaving
     fullscreen is the same gesture two pixels apart.

     The rail's PLACEMENT is not the answer and does not move (tests/phone.test.mjs
     holds it as one global rule): it is right that the controls acting on the
     view sit on the axis. What changes is the size of the thing under the
     finger, and there are only three ways to buy it - fewer buttons, a taller
     rail, or a wider one. A COLUMN was the first answer and it is the wrong one
     here: two buttons at 40px would cover 83px of a 272px price scale in
     landscape, and #scale's own column would take another 80 - 60% of the axis
     labels gone to reach two controls. So WIDTH, which is the dimension a phone
     on its side has plenty of: 96px, an 8px gap, and each button is 44 x 40.
     It reaches ~48px into the pane at each corner, under a gradient that is
     already opaque there, and the corners it reaches into are the two the
     chart draws in least - above the 6% top margin, and the empty right-hand
     end of the date row.

     AND A TAP THAT DOES NOTHING VISIBLE READS AS A TAP THAT DID NOT LAND. The
     rail was built for a mouse, where the hover already says the pointer is on
     the button; a finger gets nothing back until the snapshot's tick flashes,
     which is after the work. `:active` is the only frame a finger can see. */
  :root{ --rail-min:96px; --rail-gap:8px; --rail-btn:40px; --rail-edge:12px }
  #zoom .ind>button:active,#scale button:active{
    background:var(--ovl-tag); border-radius:8px;
  }
  /* ...AND UPRIGHT, THE TWO STAND ONE ABOVE THE OTHER INSIDE THE AXIS. The
     width argument above is the phone ON ITS SIDE's, where the scale is 272px
     tall; upright it is 600px, and the price column is now as narrow as its
     labels (48px for a $4 stock, pinColumnWidth) - so a 96px row hung 48px
     into the chart's top-right corner and cut the legend off at `C 4.` (Dor's
     phone, 2026-09-24: "tight at the side, as much room for the chart as
     possible"). A column costs one or two axis labels at the top of a tall
     scale and keeps both targets 40px tall and the full width of the axis.
     `--rail-min` is taken back on #zoom ALONE: #scale is a corner as tall as
     the date row, where a column has no height to stand in.
     ⚠ ON A PHONE THIS NO LONGER APPLIES: with three keys the column was 151px
     - a quarter of the scale, not "one or two labels" - and the rail went to
     the end of the quote bar (2026-10-03, the phone block's `#chrome>#zoom`).
     What is left here is a finger on a screen that is not a phone. */
  body:not(.wide) #zoom{
    --rail-min:0px; flex-direction:column; align-items:stretch;
    height:auto; padding-bottom:12px;
    background:linear-gradient(to bottom, var(--bg) 0, var(--bg) calc(100% - 12px), transparent 100%);
  }
  body:not(.wide) #zoom .ind{flex:none}

  /* THE HOVERS THAT REVEAL RATHER THAN DECORATE. The other 46 :hover rules in
     this stylesheet only change a colour - sticky on touch, and harmless.
     These hide a CONTROL, and a control you cannot see is a control you do
     not have.

     `.wrow .x` USED TO BE ON THIS LIST AND IS NOT ANY MORE. Making it
     permanently visible was the right first answer to "a finger has no
     hover", but it puts a live delete beside every symbol in the list, under
     the thumb that is scrolling past them - and the rail carries the
     positions he actually holds. A finger gets a gesture instead: swipe the
     row left and it slides aside to uncover the ✕, which is then a tap like
     any other. See the peel below, and armRow() in watchlist.js. */
  .wact{opacity:1}                       /* add, and the list's own menu */
  .wact.cmore{opacity:.7}                /* no hover to bring it forward */
  .wact.tochart{display:grid}           /* ...and the way back to the chart from a full-screen drawer */
  #cal .cgo{opacity:1}                   /* chart this row / go to this date -
                                            and it was worse than unreachable:
                                            opacity:0 keeps pointer events, so
                                            it was a 22x22 invisible hit zone */

  /* THE LIFT HAS TO OWN THE TOUCH FROM THE FIRST FRAME.
     touch-action is latched by the browser at touch-sequence START, so
     #watch.dragging{touch-action:none} - added 380ms in, inside beginLift() -
     never applied to the touch already in flight: the rail scrolled under the
     committed lift and the pointercancel that followed tore it down. Group
     reorder could not succeed on a phone at all.

     `pan-y` and not `none`, because a list card is most of the folder screen's
     scrolling surface - a card you cannot scroll from is a rail you cannot
     scroll. The vertical scroll stays; what the lift needs on top of it is
     refused in JS at the moment it commits, and it can be, because a 380ms
     hold is by definition 380ms with no move. See noScroll() in
     watchlist.js. */
  .wcard{touch-action:pan-y}
  /* ...AND SO IS THE RAIL AND EVERY ROW ON IT, for the same reason one level
     down: `pan-y` keeps the vertical scroll the browser already runs and
     hands us the horizontal, which is the peel and the drawer behind it. The
     two HELD gestures refuse the scroll in JS at the moment they commit
     (`#watch.dragging`, `#watch.rowdrag`), because touch-action is latched
     when the touch begins and a hold has not moved by then. */
  #watch,.wrow{touch-action:pan-y}

  /* ---- A ROW IS READ AT ARM'S LENGTH HERE ----
     The rail is 216px beside a chart on a desktop and the type was tuned for
     that: an 11px price, a 10px percentage. As a drawer it is the WHOLE 390px
     and it inherited both, so the one thing a watchlist exists to show was the
     smallest text on a screen twice as wide with nothing to compete for it.

     The figures lead - the price and the day's move go up four and three
     points - and the label follows so the row still reads as a name with a
     number beside it rather than two numbers. The card grows with them: 5px of
     padding was right for a 22px row and mean for a 30px one, and a phone row
     has to be a target as well as a line. */
  .wrow{padding:8px 10px 8px 11px}
  .wrow .sym{font-size:14px}
  .wrow .px{font-size:14px}
  /* The pill grows with its figure - a 13px number in the desktop's 17px pill
     had no air above or below it - and the column stays straight at the new
     width. The tick flash is the pill's own layer, so it moves and grows with
     it: the row's phone padding (8px under, 10px right) is its inset. */
  .wrow .ch{font-size:13px; font-weight:500}
  .wrow .chs .ch{line-height:20px; height:20px; min-width:66px; padding:0 7px}
  .wrow .fx{right:10px; bottom:8px; width:66px; height:20px}
  .wrow .l>span:not(.sym),.wrow .opt,.wrow .qty,.wrow .nm{font-size:11px}
  .wrow .xch{font-size:10.5px}
  .wrow .och{font-size:11.5px}
  .wrow .val{font-size:12.5px}
  .wrow .pnl{font-size:12.5px}
  /* ...and the two columns get the room that needs. */
  .wrow .top{gap:8px}
  .wrow .chs{gap:6px}
  /* The flat screen's rows carry a second line of list names, so they get the
     same treatment without the price column growing twice. */
  .wrow.flat .nm.in{font-size:11px}

  /* ...AND `draggable="true"` HAS TO BE TURNED OFF FOR A FINGER, OR IT EATS
     ALL THREE GESTURES BEFORE ANY OF THEM STARTS.
     Every row carries the attribute so a MOUSE can drag it between groups,
     and HTML5 drag-and-drop never fires on a touchscreen - which is why the
     rail used to answer a long press with a note saying so. But the attribute
     is not inert on iOS: a held press on a draggable node starts WebKit's own
     drag-lift and its text callout, and both take the touch sequence away, so
     the 380ms hold never completes and the sideways travel never arrives.
     Invisible to a synthetic PointerEvent, which is dispatched straight at the
     node and never asks WebKit what it wanted to do with the touch.
     `-webkit-user-drag` is the one property that turns it off without taking
     the attribute away from the desktop that still uses it. */
  .wrow{-webkit-user-drag:none; -webkit-touch-callout:none;
        user-select:none; -webkit-user-select:none}

  /* ---- SWIPE A ROW RIGHT AND IT UNCOVERS A DELETE BUTTON ----
     The row's two columns move and the button does not: it sits at the
     left-hand edge underneath them, so sliding the words aside is the whole
     animation and there is nothing to lay out. `--peel` is written per frame
     by the finger and settles to one of two values; `.peeling` takes the
     transition off while it is held, so it follows rather than chases
     (rule 5).

     RIGHT, AND THE DRAWER GOES LEFT. Both were leftward at first - the row
     uncovering its delete for the first stretch and the drawer taking
     everything past it - which is one finger doing two things, split by
     distance, and it read as neither working. Dor's call off the device:
     *"since delete and closing are a swipe the same way, let us make delete a
     swipe to the RIGHT"*. Opposite directions, each with its whole travel,
     and neither a stage of the other.

     AND IT IS A SQUARE, NOT A GLYPH. A 10px ✕ hiding at the end of a row is
     a control you have to already know about; a red block the height of the
     row that the row moves aside to show you is the thing itself, and the
     colour says what it does before the shape has to. It is still only an
     ASK - the tap on it is what deletes, and a tap anywhere else on the row
     puts it away. */
  .wrow .l,.wrow .r,
  .wcard>*:not(.x):not(.ren),.wfolder>*:not(.x):not(.ren),.gtop>*:not(.x):not(.ren){
    transform:translateX(var(--peel,0px)); transition:transform .16s var(--ease);
  }
  .wrow.peeling .l,.wrow.peeling .r,
  .wcard.peeling>*:not(.x):not(.ren),.wfolder.peeling>*:not(.x):not(.ren),.gtop.peeling>*:not(.x):not(.ren){transition:none}
  .wrow .x,.wcard>.x,.wfolder>.x,.gtop>.x{
    position:absolute; left:0; right:auto; top:0; bottom:0; transform:none;
    width:var(--peel,0px); min-width:0; padding:0; border-radius:0;
    display:flex; align-items:center; justify-content:center;
    overflow:hidden; font-size:0; background:var(--down); color:#fff;
    border:0; opacity:1; transition:width .16s var(--ease);
  }
  .wrow.peeling .x,.wcard.peeling>.x,.wfolder.peeling>.x,.gtop.peeling>.x{transition:none}
  /* The glyph is drawn rather than inherited, so it is the size of the block
     and not the size of the 10px control the desktop hover uncovers. */
  .wrow .x::before,.wcard>.x::before,.wfolder>.x::before,.gtop>.x::before{
    content:"\2715"; font:400 20px var(--sans); line-height:1;
  }
  .wrow.peel{background:var(--raise)}
  /* The card is a rounded box, so its block has to be clipped to the same
     corners or a square of red hangs off the left edge of it. */
  .wcard>.x,.wfolder>.x,.gtop>.x{border-radius:var(--r-lg) 0 0 var(--r-lg)}
  .wfolder.peel{background:var(--raise)}
  .wfolder>.x{display:flex}
  /* ⚠ A FOLDER UNCOVERS TWO BLOCKS, AND ONLY A FOLDER (Dor, 2026-09-18: *the
     rename should work the way I drag the folder right and get the red ✕ -
     beside it there should be a block with a rename symbol*, and *only for the
     folders, not for the stocks*). A row is a symbol and a card is a list;
     a folder IS its name, `u:<label>` being the whole record, and a list
     already renames from the ⋯ that also files, merges and deletes it.

     THE HALVES ARE OF `--peel` ITSELF, so both grow under the finger as the
     strip does rather than one appearing at the end of the travel. The width
     is `PEEL_WIDE` in watchlist.js and the arithmetic here is the only other
     place that number is spent. */
  .wcard>.x,.wfolder>.x,.gtop>.x{width:calc(var(--peel,0px) / 2)}
  .wrow>.x{border-radius:var(--r) 0 0 var(--r)}
  .wcard.peel,.gtop.peel{background:var(--raise)}
  .wcard>.x,.gtop>.x{display:flex}
  /* A LIST IS NOT DELETED BY A SWIPE ANY MORE, AND THAT IS THE POINT OF THE
     MENU. The group header used to carry the same red block a row does -
     which put a delete for twenty-two symbols under the same gesture as a
     delete for one, and sat it exactly where a leftward swipe to put the rail
     away begins. Both collided, both were reported from the device. A list is
     now deleted from `⋯` on its own screen, which asks, and the swipe belongs
     to the row alone. */

  /* TAP TARGETS. The visual box is right; the hit area is not. Extending it
     with a pseudo-element costs no layout and leaves the design alone.
     *
     * AND `position:relative` GOES ONLY ON THE ONES THAT ARE STATIC.
     * An absolutely-positioned element is ALREADY a containing block for its
     * own ::after, so it needs nothing here - and giving it `relative` anyway
     * does not "make sure": it OVERRIDES the `absolute` it was placed with and
     * drops it into normal flow. The first version of this block did exactly
     * that to `.wrow .x` and to all three tick rows, which are
     * `position:absolute; bottom:0; transform:translateX(-50%)` - so on a real
     * phone every calendar dot, news tick and earnings tick fell out of its row
     * and cascaded diagonally down the page and out of the chart into the
     * footer. Caught on Dor's device 2026-09-13; emulation had the same bug and
     * it simply did not look wrong in a screenshot.
     *
     * Two lists, then, and the split is the point: STATIC ones get `relative`,
     * ALREADY-POSITIONED ones get only the ::after. tests/phone.test.mjs holds
     * the invariant. */
  .wact::after,.wback::after,.wrow .x::after,
  .panel .chip button::after,.chead button::after,
  .newsread .nwclose::after,.macroread .mclose::after,.earnread .eclose::after,
  .alertread .aclose::after,.wadd2::after{
    content:""; position:absolute; inset:-12px;
  }
  /* static, so they need a containing block. `.wadd2` is the home screen's two
     22px `+` (a new folder, a new list), and was the one small control on the
     rail left at its visual size under a finger. */
  .wact,.wback,.panel .chip button,.chead button,
  .newsread .nwclose,.macroread .mclose,.earnread .eclose,
  .alertread .aclose,.wadd2{position:relative}
  /* `.wrow .x` is absolute already (style.css:725) and is deliberately absent. */
  /* ---- ...AND ON A FINGER THE RENAME IS THE SECOND PEEL BLOCK ----
     ON A FOLDER AND ON A LIST CARD ALIKE, because both are things with a name
     of their own. Reported by Dor of a list inside a folder: *I cannot change
     its name - the rename is not there on the swipe right.* A `.wrow` keeps
     its single block: a symbol has no name to change.
     It is the ✕'s own arrangement, which is the whole ask: a desktop uncovers
     a small control on hover, a finger swipes the row aside and taps a block.
     One control, two presentations, exactly like `.wrow .x` two hundred lines
     up - so nothing new is learnt on either surface.

     ⚠ IT IS DECLARED HERE, AFTER `.wact{position:relative}`, AND NOT WITH THE
     PEEL RULES ABOVE. `.ren` is placed `absolute` and wears `.wact` for the
     gesture's carve-out, so that line REPLACES its placement - the pencil fell
     out of the row and landed at the bottom-left of the card, on a coarse
     pointer only. This block's own documented trap, arriving through the CLASS
     rather than through the selector, which is the way it will arrive again:
     anything absolute wearing `.wact` takes its `absolute` back down here.

     THE RENAME IS THE NEAR HALF AND THE ✕ IS THE FAR ONE. The strip is
     uncovered at the row's LEFT while the finger travels right, so the block
     nearest the finger is the one a thumb lands on by momentum - and that one
     must not be the destructive half. */
  .wcard>.ren,.wfolder>.ren,.gtop>.ren{
    position:absolute; left:calc(var(--peel,0px) / 2); right:auto; top:0; bottom:0;
    width:calc(var(--peel,0px) / 2); height:auto; min-width:0; padding:0;
    transform:none; border:0; border-radius:0; overflow:hidden;
    display:flex; align-items:center; justify-content:center;
    background:var(--gold); color:#0b0f15; opacity:1; font-size:0;
    transition:width .16s var(--ease), left .16s var(--ease);
  }
  .wcard.peeling>.ren,.wfolder.peeling>.ren,.gtop.peeling>.ren{transition:none}
  /* Drawn rather than inherited, at the size of the BLOCK and not of the 11px
     control the desktop hover uncovers - the ✕'s rule, one block along. */
  /* ⚠ `\FE0E` IS THE VARIATION SELECTOR AND IT IS NOT DECORATION. U+270E has an
     emoji presentation on iOS, so without it the block carries a colour
     cartoon pencil - on a gold ground, beside a ✕ drawn in the page's own
     type. The ✕ (U+2715) has no emoji form and needs none. */
  .wcard>.ren,.wfolder>.ren,.gtop>.ren{display:flex}
  .wcard>.ren::before,.wfolder>.ren::before,.gtop>.ren::before{
    content:"\270e\fe0e"; font:400 19px var(--sans); line-height:1;
  }
  /* The block IS the target; the standard -12px halo would hang it over the ✕
     beside it and over the row it is uncovered from. */
  .wcard>.ren::after,.wfolder>.ren::after,.gtop>.ren::after{content:none}
  /* ---- ...AND A LIST CARD'S THIRD BLOCK IS THE EYE (Dor, 2026-10-05) ----
     Pin to Glance, nearest the finger because it is the harmless one; the
     rename moves to the middle and the ✕ stays the far reach. The thirds are of
     `--peel` itself, as the halves are, so all three grow with the finger
     (`PEEL_THREE` in watchlist.js is the only other place the width lives).
     Slate rather than another gold, so the eye and the pencil beside it read as
     two different things; pinned, the eye is GOLD - the same lit state the eye
     beside `+` wears (a struck-through eye read as "hidden"). `#watch` outranks the rule that slides
     every other child of the card with the finger: the blocks are what the
     card is slid aside to SHOW. */
  .wcard:has(> .eye)>.x,.gtop:has(> .eye)>.x{width:calc(var(--peel,0px) / 3)}
  .wcard:has(> .eye)>.ren,.gtop:has(> .eye)>.ren{left:calc(var(--peel,0px) / 3); width:calc(var(--peel,0px) / 3)}
  #watch .wcard>.eye,#watch .gtop>.eye{
    position:absolute; left:calc(var(--peel,0px) * 2 / 3); right:auto; top:0; bottom:0;
    width:calc(var(--peel,0px) / 3); height:auto; min-width:0; padding:0;
    transform:none; border:0; border-radius:0; overflow:hidden;
    display:flex; align-items:center; justify-content:center;
    background:#2c3a52; color:#e8eefb; opacity:1;
    transition:width .16s var(--ease), left .16s var(--ease), background-color .16s;
  }
  #watch .wcard.peeling>.eye,#watch .gtop.peeling>.eye{transition:none}
  /* `justify-content:flex-start` with `margin:auto`: centred in an open block,
     and in a CLOSED one (width 0) the icon starts inside it rather than being
     centred on its edge - hanging 11px out of the card, clipped from sight but
     not from layout (glance.test's overflow check found it). */
  #watch .wcard>.eye,#watch .gtop>.eye{justify-content:flex-start}
  .wcard>.eye svg,.gtop>.eye svg{width:22px; height:22px; fill:none; stroke:currentColor; stroke-width:1.8;
                  stroke-linecap:round; stroke-linejoin:round; flex:none; margin:0 auto}
  /* PINNED, THE EYE LIGHTS - and only the eye (Dor, 2026-10-05: *not
     aggressive - something delicate, maybe just the eye lights up*). The block
     keeps its slate, warmed a shade; the eye turns orange with a soft glow,
     and the press is that glow rising and settling rather than a flash. */
  #watch .wcard>.eye.on,#watch .gtop>.eye.on{background:#333645; color:var(--warn); box-shadow:none}
  .wcard>.eye.on svg,.gtop>.eye.on svg{filter:drop-shadow(0 0 5px rgba(240,160,60,.55))}
  .wcard>.eye.pop svg,.gtop>.eye.pop svg{animation:eyelight 620ms cubic-bezier(.3,.7,.3,1)}
  @keyframes eyelight{
    0%{transform:scale(.92); filter:drop-shadow(0 0 0 rgba(240,160,60,0)); opacity:.7}
    55%{transform:scale(1.05); filter:drop-shadow(0 0 9px rgba(240,160,60,.8)); opacity:1}
    100%{transform:scale(1); filter:drop-shadow(0 0 5px rgba(240,160,60,.55))}
  }
  .wcard>.eye:active svg,.gtop>.eye:active svg{transform:scale(.88)}
  .wcard>.eye::after,.gtop>.eye::after{content:none}
  /* ...AND A ROW THAT CAN CARRY A STAR UNCOVERS THE SAME TWO BLOCKS: the star
     near, in the gold the frame it leaves is drawn in, and the ✕ the far
     reach. `peelMax()` gives it the wide strip (`data-star`). */
  .wrow>.star{
    position:absolute; left:calc(var(--peel,0px) / 2); right:auto; top:0; bottom:0;
    width:calc(var(--peel,0px) / 2); height:auto; min-width:0; padding:0;
    transform:none; border:0; border-radius:0; overflow:hidden;
    display:flex; align-items:center; justify-content:center;
    background:var(--gold); color:#0b0f15; opacity:1; font-size:0;
    transition:width .16s var(--ease), left .16s var(--ease);
  }
  .wrow.peeling>.star{transition:none}
  /* No `\FE0E` here, unlike the pencil: neither star has an emoji form, and
     with the selector the filled one drew NOTHING in Chrome (2026-09-28). */
  .wrow>.star::before{content:"\2606"; font:400 20px var(--sans); line-height:1}
  .wrow>.star.on::before{content:"\2605"}
  /* The desktop's gold glyph on this gold block is no glyph at all. */
  .wrow>.star.on{color:#0b0f15}
  .wrow>.star::after{content:none}
  .wrow[data-star]>.x{width:calc(var(--peel,0px) / 2)}
  /* ...and with the pencil off the row here, the room it reserves on a desktop
     goes back to the name. */
  .wfolder .fgo{padding-right:8px}
  /* The tick rows are 9-14px dots on the x-axis, and they need a bigger target
     than a dot. Two bounds decide how much bigger, and the first version had
     both wrong:
     *
     * ACROSS - the three rows are 18px apart, so a halo taller than 18px makes
     * a news tick and the calendar dot above it the same target. 18px is the
     * honest maximum.
     *
     * DOWNWARD - "reaching down is free, the axis is below" was true when the
     * bottom of the chart was the bottom of the page. It is not any more: the
     * DOCK is there now. A 14px downward halo put the earnings row's hit area
     * 12px from the dock's top edge, so a thumb reaching for a tool button
     * landed on a tick and opened its reading instead. Reported from the phone.
     * The halo now leans UP, away from the bar. */
  /* THE ROWS SPREAD, AND THE HALOS ARE SIZED TO THE SPREAD. These two numbers
     are one decision and must live together: a halo taller than the gap between
     rows makes a news tick and the calendar dot above it a single target, and
     the tap opens the wrong mark.
     18px apart was a mouse's spacing. At 24px each row can carry a 24px target,
     which is what a finger needs.

     THE BASE IS AS LOW AS THE DOCK ALLOWS, AND IT USED TO BE HIGHER THAN THAT.
     It was 38px, which lifted the lowest halo 35px clear of the dock - from the
     round where a 14px DOWNWARD halo put the earnings row 12px from the bar and
     a thumb reaching for a tool button opened a mark instead. 35 was never
     measured against anything; it was what the old halo happened to leave. The
     three rows are 64px of a 272px pane on a phone in landscape and they sit on
     the candles, so every pixel of that clearance is bought from the chart:
     "the gold dots of the news hide the candles - they can go lower, close to
     the x axis" (Dor, from the phone, 2026-09-15). 28px leaves the halo 25px
     clear, which is twice the distance that produced the bug and 10px of pane
     back on every one of the three rows. */
  #wrap{ --tick-base:28px; --tick-step:24px }
  /* Sized to the row spacing, per row, because the ticks are not all the same
     size: a dot is 9px and an earnings print is 15px, so one inset cannot give
     both the same target. 24px tall each - the rows are 24px apart on a phone -
     and never more, or a news tick and the calendar dot above it become one
     target and the tap opens the wrong mark. */
  .newsticks .nwtick::after,.macroticks .mtick::after,.earnticks .etick::after{
    content:""; position:absolute; left:-7px; right:-7px;
  }
  .newsticks .nwtick::after,.macroticks .mtick::after{top:-8px; bottom:-7px}
  .earnticks .etick::after{top:-5px; bottom:-4px}

  /* The credit is a 108x13 link and the licence expects it to be usable. */
  footer .credit a{position:relative}
  footer .credit a::after{content:""; position:absolute; inset:-12px}
  /* THE `+n` CHIP GROWS INSTEAD OF BORROWING. A negative-inset ::after is
     absolutely positioned, so it joins its container's SCROLLABLE OVERFLOW -
     and #layers is the box fitStrip() measures (`scrollWidth <= clientWidth`).
     A 12px halo made #layers report 46/35 with nothing in it, so fitStrip read
     the row as overflowing and dropped every tile again - undoing the scroll
     fix by making the chip easier to press. Real padding is measured honestly.
     Inside anything that measures itself, grow the box; never halo it. */
  #strip .fold{min-height:24px; padding:0 10px}
  /* And the rail must not gain a sideways scroll from the halos inside it:
     `overflow-y:auto` alone leaves overflow-x computing to auto. */
  #watch{overflow-x:hidden}
  .wrow{padding-top:8px; padding-bottom:8px}
  .whit{padding:9px 7px}
  #results .row{padding:11px 10px}
  .tfs button{padding:0 11px}

  /* ARMED HAS TO BE VISIBLE WITHOUT A CURSOR. Both the ruler and a by-hand
     drawing announced themselves ONLY through cursor:crosshair, which a phone
     does not have - so with the panel dismissed nothing on screen said the
     next tap would be taken. */
  #wrap.measuring::after,#wrap.picking::after{
    content:""; position:absolute; inset:0; z-index:4; pointer-events:none;
    box-shadow:inset 0 0 0 2px var(--gold-line);
  }
  #wrap.picking::after{box-shadow:inset 0 0 0 2px var(--agent-line)}

  /* THE DRAW BAR IS A CONTROL, SO IT IS A 40px TARGET LIKE THE DOCK'S.
     It is also the only way out of an armed tool on this device - there is no
     Esc - so it is the one thing on the pane that must not be fiddly. The text
     box takes what is left of the row rather than a fixed 190px, because a
     phone is 390px wide and the row already carries a name and two buttons. */
  .drawbar{top:6px; gap:6px; padding:5px; max-width:calc(100% - 16px)}
  .drawbar .nm{font-size:11px}
  .drawbar .step{font-size:11px}
  .drawbar button{min-width:40px; height:40px; font-size:13px}
  .drawbar input[type=text]{flex:1; min-width:0; width:auto; height:40px; font-size:16px}
  /* A mark's symbol, a size up for a fingertip (its target is bigger still -
     see pinSlop() in index.html). */
  .ovl>div.pin{width:22px; height:22px; font-size:12px}
  .ovl>div.pin[data-role="structure"]{width:18px; height:18px; font-size:10px}
}

/* =========================================================================
   THE PHONE ON ITS SIDE
   =========================================================================
   844 x 390, and the arithmetic is the portrait layout's turned through
   ninety degrees: width is suddenly plentiful and HEIGHT is the thing that ran
   out. Header 44 + strip 28 + dock 48 + footer 22 is 142 of 390, so the pane
   is about 248px tall and 784px wide - a letterbox.

   Two answers follow from that one fact, and both are the opposite of what
   portrait does:

     THE ANALYST IS A COLUMN AGAIN. A bottom sheet is a HEIGHT, and `half` is
     52% of the pane: 129px, which is too little to read in and too much to
     give up. On a screen this shape the desktop's own answer is the right one
     - a panel down the right - and it costs the chart width, which is the
     dimension there is plenty of. chrome.js stops driving the detents here and
     clears the inline height a portrait session left on the node.

     EVERYTHING WRITTEN ON THE CHART GETS SMALLER. Thirteen captions at 17px
     are 221px of a 248px pane. The caption is the same words at 9.5px in a
     tighter box, and the two boxes that are prose - the sentence note and the
     board - give up the most, because they are the tallest. This is Dor's ask:
     "reduce the text box if I side the phone for horizontal screen."

   Scoped to `body.wide`, written by chrome.js from the same query, rather than
   to a bare media block - the sheet rules it has to beat live inside the phone
   block above, and a class is how this wins without an `!important` or a
   duplicated selector chain. */
@media (max-height:500px) and (pointer:coarse){

  /* THE OPEN LIST IS BOUNDED BY THE PANE, AND ON ITS SIDE THE PANE IS 269px.
     Eight rows at 34 plus the head and the three verbs is ~330px - taller than
     the chart it is standing on, which is this feature covering the candles in
     a new way. It scrolls instead, at a share of the pane rather than a row
     count, because the row count is exactly the thing that grows when a finder
     is added. The chip itself stays put; only the rows move. */
  #lights .rows{max-height:42vh; overflow-y:auto; overscroll-behavior:contain}
  #lights button{height:30px}


  /* THREE BANDS NOW, and each gives up a little without giving up its job: the
     credit stays, the quote bar stays one line, the dock keeps 36px targets.
     The fourth - the footer - is gone from every phone since 2026-09-15 and
     what it was for rides the foot of the dock, so the dock's height here is
     its targets PLUS that band and not a number of its own: 36 + 2. Set wrong
     it does not overflow, it SQUASHES - `align-content:center` on a wrap
     container centres the two lines and lets them spill over the button row's
     own edges, which reads as slightly small buttons rather than as a bug. */
  header{min-height:40px}
  #dock{height:calc(38px + var(--footband))}
  #dock .ind>button{width:36px; height:36px; border-radius:var(--r)}
  #dock .ind>button svg{width:18px; height:18px}
  .tfs.compact button{height:36px; min-width:36px}
  #dock .dname{height:36px}
  /* The tray on its side: the same tiles, a head on one line and no hint
     under them - a phone lying down has height for two rows of names and
     not for a caption about a gesture. */
  .swhead{padding:0 8px 8px}
  .swstrip{padding-bottom:7px}
  .swname b{font-size:14.5px}
  .swt{min-height:42px; padding:5px 10px}
  .swhint{display:none}
  /* Only ever on screen in the broken-IBKR state (see the portrait block). */
  footer{min-height:18px; font-size:9.5px}

  /* The analyst, as a column over the chart. Not a sheet: no handle, no
     detents, nothing to drag - a fixed width, which is what the shape of the
     screen is asking for. It still does not dim the chart, for the reason the
     portrait sheet does not. */
  body.wide #chat{
    left:auto; right:0; top:0; bottom:0; height:100%;
    width:min(400px, 46%);
    border-top:0; border-left:1px solid var(--gold-line);
    border-radius:var(--r-xl) 0 0 var(--r-xl);
    box-shadow:-18px 0 44px -12px rgba(0,0,0,.66), -2px 0 10px rgba(0,0,0,.3);
    padding-right:var(--sar);
    transition:border-left-color .9s var(--ease-out);
  }
  /* A column again, so the light is on its left edge again, as on a desktop. */
  body.wide #chat::after{
    top:0; bottom:0; left:0; right:auto; width:44px; height:auto;
    border-radius:var(--r-xl) 0 0 var(--r-xl);
    background:radial-gradient(44px 46% at 0 0,rgba(110,169,255,.14),rgba(110,169,255,0));
  }
  body.wide #chat:has(#chatDot.work){border-left-color:rgba(110,169,255,.72); transition-duration:.35s}
  /* The grab handle was the sheet's affordance and there is nothing to grab. */
  body.wide .chead{cursor:default; padding-top:8px}
  body.wide .chead::before{display:none}
  /* `at-peek` hid the transcript to leave the composer - that is a height
     state and this panel has one height. */
  body.wide #chat.at-peek .clog{display:block}
  body.wide #chat.at-peek .cdisc{display:block}

  /* The drawer keeps its manner and loses its width: 86% of 844 is the whole
     screen for a list of ten rows. */
  #watch{width:min(320px, 42%)}

  /* ---- the three tick rows, which are the other thing on the chart ----

     THE SPREAD IS THE ONE PLACE LANDSCAPE SPENDS LESS THAN PORTRAIT, and it is
     the same arithmetic as everything else in this block: portrait has 700px of
     pane and can afford 24px a row, landscape has 272px and three rows at 24
     are 64px of it - a QUARTER of the chart, sitting on the candles, which is
     what was reported. At 20px the band is 56px and its top drops 18px; each
     row still carries a 20x23 target, which is the same finger the 40px dock
     buttons and the 36px timeframe pills in this block are sized for. The
     halos go with it, because a halo taller than the spread makes two rows one
     target - the rule above, applied to this block's own number. */
  #wrap{ --tick-step:20px }
  .newsticks .nwtick::after,.macroticks .mtick::after{top:-6px; bottom:-5px}
  .earnticks .etick::after{top:-3px; bottom:-2px}

  /* ---- and everything written on the chart ---- */

  /* One line. Two lines of legend is 36px of a 248px pane, and the same row is
     in the strip directly above it. */
  /* Bounded by the AXIS again, the base rule's own bound: the rail that was
     wider than the axis here went up to the end of the quote bar (2026-10-03),
     so there is no camera on the pane for the legend to stop short of. */
  #legend{-webkit-line-clamp:1; font-size:10.5px}

  /* The caption, reduced. Smaller type, tighter box, and capped by PIXELS as
     well as by share - 36% of 784px is 282px, which is a caption that has
     stopped being a caption. */
  .o-note.tag,.o-label.tag{
    max-width:min(230px, 30%, calc(100% - 16px));
    font-size:9.5px; padding:1px 4px;
  }
  /* The one sentence the picture cannot carry. It keeps its box - it is the
     mark whose whole job is to be read - and gives up the width that was
     making it three lines tall. */
  .o-note{max-width:min(230px, 30%); font-size:10.5px; line-height:1.35; padding:3px 6px}
  /* The board gives up the most, because it is the tallest thing on the chart
     and the only one that is a paragraph. It opens folded here as it does in
     portrait, and the fold is two lines rather than three. */
  .o-board{max-width:min(280px, 32%); max-height:46%; font-size:11px; line-height:1.4}
  /* Same two lines; the type and the leading are this block's, not portrait's. */
  /* A vertical rule's caption and a span's, which sit at the top of the pane
     where the legend also is. */
  .o-vline b,.o-span b{font-size:9px; padding:1px 4px}
}

/* THE CHART OWNS ITS OWN TOUCHES.
   The library sets no touch-action and index.html configures no gesture
   options, so touch-action computed to `auto` on every canvas and a pinch was
   offered to the page and the chart at once. The pane is not a scroller and
   never has been; saying so hands the library every touch cleanly. Outside the
   media queries on purpose - it is true of the element, not of the screen. */
/* AND HOLDING STILL ON THE PANE IS A GESTURE NOW (GRAB_HOLD_MS), so the pane
   has to say that a long press is not an invitation to select or to copy
   anything. iOS offers a callout on a long press and both ends offer a text
   selection; neither is reachable on a canvas by any other route, so nothing is
   lost by refusing them, and what is gained is that the gesture which picks a
   caption up does not also pop a menu over it. `.o-board` already says the same
   for the same reason, from the round in which the hold was only its own.

   ABOVE the pane's own rule and not below it: that one is the last declaration
   in this file on purpose, so that nothing can scope it. */
#wrap{-webkit-touch-callout:none; user-select:none; -webkit-user-select:none}
/* ---------------- the account, at the right end of the header ----------------
   A 26px disc with the account's initial, and a menu under it carrying the
   full address and the way out. GOLD, because the account is the USER - the
   two temperatures, and the one place on this page that is about the person
   rather than the market.

   IT DOES NOT FOLD. `HEADER_YIELDS` holds readouts that can be inferred from
   somewhere else when room runs out; which account this is cannot be, and with
   more than one on a machine it is the control that matters most. 26px is what
   that costs.

   The menu reuses `.panel` so it is the same object as the I/D/N dropdowns -
   same surface, same radius, same shadow, same pop - and only the two rows
   inside it are new. Anchored to the RIGHT edge, because it is the last thing
   in the header and a left-anchored panel would hang off the page. */
.acct{position:relative; display:inline-flex; margin-left:8px}
.acct > button{
  display:inline-flex; align-items:center; justify-content:center;
  width:26px; height:26px; padding:0; border-radius:999px; cursor:pointer;
  font:600 11px/1 var(--sans); text-transform:uppercase; letter-spacing:.02em;
  color:var(--gold); background:var(--gold-soft);
  border:1px solid transparent; box-shadow:inset 0 0 0 1px rgba(228,199,120,.3);
  transition:background-color var(--dur-1), box-shadow var(--dur-1);
}
.acct > button:hover{background:rgba(228,199,120,.2); box-shadow:inset 0 0 0 1px var(--gold-line)}
.acct > button[aria-expanded="true"]{background:rgba(228,199,120,.2); box-shadow:inset 0 0 0 1px var(--gold-line), var(--ring)}
.acct-menu{left:auto; right:0; width:auto; min-width:200px; max-width:280px}
.acct-who{
  padding:2px 10px 8px; font:400 12px/1.35 var(--sans); color:var(--fg);
  overflow-wrap:anywhere;
}
/* The build stamp, under the address. Dim and small: it is never the thing
   you came to the menu for, and it is always the first thing worth knowing
   when something looks wrong. */
.acct-ver{
  padding:0 10px 8px; font:400 11px/1.3 var(--mono); color:var(--dim);
  font-variant-numeric:tabular-nums;
}
/* WHERE THE WEEK STANDS, exactly (web/usage.js). The one place the figure is
   shown; the chat only warns. The bar is the user's gold until 90, then the
   warning orange - the same turn the chat makes. */
.acct-use{padding:2px 10px 8px; font:400 12px/1.35 var(--sans); color:var(--text)}
.acct-use .uh{font:500 10.5px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; color:var(--dim); margin-bottom:6px}
.acct-use .um{font-variant-numeric:tabular-nums}
.acct-use .ub{height:4px; margin:6px 0 5px; border-radius:2px; background:var(--line); overflow:hidden}
.acct-use .ub > i{display:block; height:100%; width:0; background:var(--gold); border-radius:2px}
.acct-use.urgent .ub > i{background:var(--warn)}
.acct-use.urgent .um{color:var(--warn)}
.acct-use .us,.acct-use .ux{font-size:11px; color:var(--dim); font-variant-numeric:tabular-nums}
.acct-out{
  display:block; width:calc(100% - 8px); margin:5px 4px 1px; padding:8px 10px;
  text-align:left; border:0; border-radius:var(--r); cursor:pointer;
  font:500 12.5px/1 var(--sans); color:var(--fg); background:transparent;
  transition:background-color var(--dur-1), color var(--dur-1);
}
.acct-out:hover{background:var(--hover); color:var(--text)}
/* ---------------- a fresh start, asked rather than mentioned ----------------
   It was a card in the transcript, and the argument for that was that a
   suggestion which blocks is not a suggestion. The card scrolled away, which
   is what a card in a log does - and the thread it was about went on getting
   longer and more expensive after the one moment somebody was in a position to
   answer. So it blocks now (Dor's call, 2026-09-17), and it blocks THE THING
   IT IS ABOUT: `#newchat` is the last child of `#chat` and `inset:0` against
   the panel's own box, so the conversation cannot be gone on with until it is
   answered and the CHART IS UNTOUCHED - still readable, still drawable, with
   the analyst's last answer still drawn on it. A window over the whole page
   was the first attempt and it took the chart away to ask about the
   conversation.

   `#chat` is `position:relative` on a desktop and `position:absolute` on a
   phone, so it is the containing block either way and this needs no help to
   land on the panel in both layouts.

   ⚠ NOT `#fresh`, WHICH IS TAKEN, by the "a newer copy of the app is ready"
   strip at the foot of the chart - which has a Reload button of its own.

   IT TAKES THE OTHER WINDOWS' --doc-* TOKENS, not the panel's, and that is the
   point of the skin here: it has to read as the app asking a question, not as
   another message in a log. What it does not borrow is `.win` - those four are
   documents with a title bar and a scrolling body, and this is four lines and
   two buttons. */
#newchat{
  --doc-bg:#0a0d12; --doc-text:#e8e4d8; --doc-muted:#9a9789;
  --doc-line2:rgba(228,199,120,.28); --doc-edge:rgba(228,199,120,.26);
  --doc-accent:#e4c778; --doc-accent-hi:#f8e7b0; --doc-accent-soft:rgba(228,199,120,.1);
  position:absolute; inset:0; z-index:5;
  display:flex; align-items:center; justify-content:center; padding:14px;
  overflow:auto;
}
/* ...and the panel's head is not under it. `.chead` carries `z-index:6` for
   this: the ✕ and the phone's drag handle keep working, so the way to put the
   conversation down is never taken away by a question about the conversation.
   The offer is a child of `#chat`, so putting the panel away puts it away too
   - and brings it back with it, unanswered, which is the point. */
#newchat[hidden]{display:none}
/* Over the panel and nothing else, so it is dimmer than a window's scrim: the
   transcript under it is what the question is ABOUT and stays legible. */
#newchat .scrim{
  position:absolute; inset:0; background:rgba(8,10,14,.66); backdrop-filter:blur(1.5px);
}
/* A QUESTION THE APP ASKS, SO IT WEARS THE FLOATING SURFACE - the popover
   ground, a neutral hairline, the window's shadow with its lit top edge - and
   says it in the panel's own type: a title at body weight and a sentence of
   reason a step quieter. */
#newchat .card{
  position:relative; box-sizing:border-box; width:100%; max-width:330px;
  padding:18px 18px 16px; background:var(--panel2); color:var(--text);
  border:1px solid var(--line2); border-radius:var(--r-lg);
  box-shadow:var(--shadow);
  animation:pop var(--dur-2) var(--ease-out);
}
#newchat h2{margin:0 0 8px; font:600 14px/1.35 var(--sans); color:var(--text)}
#newchat p{margin:0; font:400 12.5px/1.6 var(--sans); color:var(--muted)}
/* THE TWO ANSWERS ARE THE SAME WEIGHT AS EACH OTHER. Gold is the user's own
   temperature and `Reload` is the recommendation, so it carries it - but this
   opened by itself, so the answer that changes nothing is neither smaller nor
   quieter, and it is the one that holds focus. Both are the same shape; one
   wears the page's "on" (gold word, a breath of gold, the inset), the other a
   breath of neutral fill. */
#newchat .nacts{display:flex; gap:8px; margin-top:16px}
#newchat .nacts button{
  flex:1 1 0; min-width:0; height:34px; padding:0 12px; cursor:pointer;
  border-radius:var(--r-sm); font:500 12.5px var(--sans);
  color:var(--text); background:var(--hover); border:0;
  transition:background var(--dur-1), color var(--dur-1), box-shadow var(--dur-1);
}
#newchat .nacts button:hover{background:var(--press)}
#newchat .nacts button.gold{
  color:var(--gold-hi); background:var(--gold-soft);
  box-shadow:inset 0 0 0 1px rgba(228,199,120,.34);
}
#newchat .nacts button.gold:hover{background:rgba(228,199,120,.2)}
#newchat .nacts button:focus-visible{outline:2px solid var(--doc-accent); outline-offset:2px}
/* ---------------- a newer copy of the app is ready ----------------
   ALONG THE BOTTOM OF THE PANE, NOT OVER IT. `#msg` is the full-pane overlay
   and it is the right weight for "this chart could not load"; this is the
   opposite kind of fact - worth knowing, never worth interrupting - so the
   chart goes on working behind it and the candles it covers are the oldest
   ones on screen rather than the live edge.

   GOLD, because reloading is the USER's move (the two temperatures) and
   because the two things it must not read as are an error and an agent mark.
   It rises rather than appears: a strip that blinks into existence over a
   moving chart reads as a glitch, and 160ms of travel says "this arrived"
   instead. It sits above the dock's own band on a phone, so it never covers
   the controls it is asking you to go on using.
   The pill is a floating thing, so its edge is the floating hairline and its
   gold is the one button - flat, with the lit top edge, never a gradient:
   it sits on the pane. */
#fresh{
  position:absolute; z-index:4; left:50%; transform:translateX(-50%);
  /* Clear of the time axis: `#wrap` already ends above the dock on a phone and
     above the footer on a desktop, so the only thing left to miss is the date
     row and the tick strips that ride on it. */
  bottom:40px;
  display:flex; align-items:center; gap:10px;
  max-width:calc(100% - 24px); padding:6px 6px 6px 16px;
  font:400 12.5px/1.35 var(--sans); color:var(--text);
  background:var(--panel2); border:1px solid var(--line2);
  border-radius:999px; box-shadow:var(--shadow-pop);
  animation:freshUp .16s var(--ease);
}
#fresh[hidden]{display:none}
#fresh > span{white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
/* A tap here is a tap. Without this the browser holds it open as the possible
   start of a double-tap zoom, which is most of what "does not press well"
   feels like - and the pill is over a pane that claims every other gesture. */
#fresh,#fresh button{touch-action:manipulation}
#fresh button{
  flex:none; cursor:pointer; font:500 12px/1 var(--sans);
  border-radius:999px; border:1px solid transparent;
  transition:background var(--dur-1), color var(--dur-1);
}
#fresh button.gold{
  padding:7px 14px; color:#1a1408; background:var(--gold); font-weight:600;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3);
}
#fresh button.gold:hover{background:var(--gold-hi)}
#fresh button.x{
  width:26px; height:26px; padding:0; font-size:11px;
  color:var(--muted); background:transparent; border-color:transparent;
}
#fresh button.x:hover{color:var(--text); background:var(--hover)}
@keyframes freshUp{
  from{opacity:0; transform:translate(-50%, 8px)}
  to  {opacity:1; transform:translate(-50%, 0)}
}
/* ---- THE BOARD'S OWN CONTROLS, ON A FINGER (docs/מצב-לימוד.md §7) ----
   Its own block rather than a line inside the quarantined phone block above:
   these two buttons did not exist when that block was written, and a rule for
   a new control does not belong inside one nobody is allowed to edit. It sits
   BEFORE the chart-pane touch rule because that rule has to stay the last
   declaration in this file - tests/phone.test.mjs asserts exactly that, so
   that nothing can ever scope it to a screen. 44px is what this page gives
   anything a thumb presses; the pill grows with its buttons. */
@media (pointer:coarse){
  .sketch-pace{gap:8px; padding:5px 8px; bottom:calc(var(--pad-bottom, 0px) + 16px)}
  .sketch-pace button{width:44px; height:44px; font-size:20px}
  .sketch-pace span{min-width:44px; font-size:12px}
  .sketch-pace button.stop{font-size:13px}
  /* The bar is a thumb target the whole height of the pill, and the pill the
     width of the phone less its gutters. */
  .sketch-pace.video{width:calc(100% - 24px)}
  /* The speed is ONE chip under a finger, the lit one, and a tap steps to
     the next (`setSpeed` in web/sketch.js reads which chips are shown). */
  .sketch-pace button.speed{width:auto; min-width:48px; padding:0 8px; font-size:13px}
  .sketch-pace button.speed:not(.on){display:none}
  .sketch-pace button.speed.on{border-color:rgba(110,169,255,.55)}
  .sketch-pace .scrub{height:44px}
  .sketch-pace .scrub .knob{width:16px; height:16px; margin:-8px 0 0 -8px}
}

/* THE FAVOURITES ON A FINGER (the bar and the star are above, beside the D
   panel). Late in the sheet and not beside them: `tests/phone.test.mjs`
   reads the FIRST coarse block as the phone's own, and these are two small
   additions to it rather than a second phone layout. The star is status only
   here - a thumb meaning the tool lands on that corner - and the bar's targets
   grow to a finger's while staying under the dock's own size. */
@media (pointer:coarse){
  #drawPanel .tool .fav{pointer-events:none}
  .favbar{gap:3px; padding:3px; border-radius:11px}
  .favbar button{width:34px; height:34px; border-radius:8px}
  .favbar button svg{width:19px; height:19px}
  .favbar button:hover{color:var(--muted); background:transparent}
  .favbar button.armed{color:var(--gold); background:var(--gold-soft)}
}

/* ---- GLANCE IS READ AT ARM'S LENGTH, SO ITS FIGURES ARE THE BIGGEST ON THE RAIL ----
   Dor, off the phone: *"the numbers are a bit small, give each stock more
   room"*. Glance is the screen you open to read prices and close again, so
   the row is a 44px target (a finger's minimum) and the three figures lead -
   name and price at 15px, the day's move at 14px in a pill a size up. A first
   pass at 50px and 16px was, in his words, a little too big. The stepper's two
   buttons were 24x20, under a finger's width; they are 36x30 here.
   ⚠ AT THE END OF THE FILE, NOT BESIDE THE REST OF GLANCE: tests/phone.test
   reads the FIRST `@media (pointer:coarse)` as the phone's block, and a second
   one written earlier became it. */
@media (pointer:coarse){
  .rows1 .wrow{padding:9px 11px 9px 12px; min-height:44px}
  .rows1 .wrow .sym{font-size:15px; font-weight:600; letter-spacing:.01em}
  .rows1 .wrow .px{font-size:15px; line-height:19px}
  .rows1 .wrow .r{gap:12px}
  .rows1 .wrow .chs .ch,.rows1 .wrow .ch{
    font-size:14px; line-height:24px; height:24px; min-width:76px; padding:0 8px;
  }
  /* The tick's pulse sits over the pill, which on this row is centred. */
  .rows1 .wrow .fx{right:11px; bottom:auto; top:50%; margin-top:-12px; width:76px; height:24px}
  .rows1 .wrow.wx .fx{top:auto; margin-top:0; bottom:9px}
  .rows1 .wrow + .wrow::before{left:12px; right:11px}
  .gphead{padding:12px 10px 13px 13px}
  .gpmeta{font-size:10.5px}
  .gppct{font-size:13.5px; padding:2px 8px}
  .gpname{font-size:15px}
  .gpmeta{font-size:12px}
  .gppct{font-size:15px}
  .gmore{padding:6px 10px 10px 11px; font-size:12.5px}
  .gstep{height:34px; border-radius:17px; padding:0 2px}
  .gstep button{width:36px; height:30px; border-radius:15px; font-size:18px}
  .gstep b{min-width:22px; font-size:14px}
  .grest{font-size:13.5px; padding:8px 2px 8px 10px}
  .gall{font-size:12.5px}
  .gnote{font-size:12.5px}
  .gfoot{font-size:12px}
  .wtab{height:36px; font-size:14px}
  .gfold{top:10px; width:40px; height:40px; border-radius:12px}
  .gfold svg{width:20px; height:20px}
  .gphead{padding-right:54px}
  .wlogo{width:26px; height:26px}
  .wlogo b{font-size:12px}
  .wrow .wlogo{margin-right:4px}
  /* The mark travels with the row when it peels, as the two columns do. */
  .wrow .wlogo{transform:translateX(var(--peel,0px)); transition:transform .16s var(--ease)}
  .wrow.peeling .wlogo{transition:none}
}

/* The one remark Android's back makes (index.html, armBackGuard): above the
   dock and above every surface, because the first press can come with the
   lists or the analyst in front, and it takes no touch from anything. */
.backnote{
  position:fixed; left:50%; transform:translateX(-50%); z-index:20;
  bottom:calc(54px + var(--footband, 0px) + var(--kb, 0px));
  padding:8px 14px; border-radius:var(--r); pointer-events:none;
  background:var(--panel2); border:1px solid var(--line2); color:var(--text);
  box-shadow:var(--shadow-pop); font:13px var(--sans, sans-serif); white-space:nowrap;
}
.backnote[hidden]{display:none}

#chart,#chart canvas{touch-action:none}
