/* ============================================================================
   CHARTVEIL — REVIEW PASS
   Shipped 2026-09-09. Loads after styles.css and depth3d.css, and only ever
   adds: every rule here either repairs something that was visibly broken or
   evens out a rhythm that was uneven. No colour of its own, no copy changes.

   Each fix keeps its own class, and the classes are set on <html> in
   index.html — so the whole pass works with JavaScript disabled, and any one
   fix can be dropped by deleting its class from that attribute rather than by
   picking this file apart. refine.js handles only the parts that need markup:
   the real screenshots in the panels, and the click-to-enlarge.

   The reasoning behind each fix is in the comment above it. Where a rule looks
   arbitrary it is usually load-bearing — read the comment before tuning it.
   ========================================================================== */

/* ============================================================================
   CHARTVEIL — REVIEW PASS (preview only, 2026-09-09)

   Each fix is scoped to its own class on <html> so it can be toggled on and off
   against the live page. Nothing here is a redesign: every rule either repairs
   something that is visibly broken, or evens out a rhythm that is currently
   uneven. Copy changes live in preview-fixes.js next to this.

   fx-num    the 4/4/4 -> 1 numerals, which are currently 13px body text
   fx-hero   the hero label said once instead of three times
   fx-plan   the annual card's badge no longer collides with its own border
   fx-axis   the orphaned price watermark sliced off by the section edge
   fx-rhythm one gap between sections instead of eleven different ones
   fx-table  the comparison table grouped instead of 12x the word "Included"
   fx-calm   the depth entrance stops moving (it was called horrid once before)
   fx-read   long-form greys lifted to AA contrast
   fx-mobile the product visible on a phone without scrolling
   ========================================================================== */

/* ---------------------------------------------------------------------------
   fx-num — THE BROKEN NUMERALS
   .four__row span sets 13px/--text-muted and .four__n is a <span> inside it,
   so the row rule wins on specificity and the whole 4-4-4-1 device — the thing
   the section is built around — renders as three lines of grey body text.
   Matching the specificity is the entire fix.
--------------------------------------------------------------------------- */
html.fx-num .four__row > span.four__n{
  font-family:var(--font-data);font-weight:700;
  font-size:clamp(40px,4.6vw,58px);line-height:1;margin-top:0;
  color:var(--text-faint);
  text-decoration:line-through;text-decoration-color:var(--brand);
  text-decoration-thickness:3px;
}
html.fx-num .four__row--us > span.four__n{
  color:var(--select);text-decoration:none;
  text-shadow:0 0 24px rgba(255,176,32,.45);
}
@media (max-width:600px){
  html.fx-num .four__row > span.four__n{font-size:34px}
}

/* ---------------------------------------------------------------------------
   fx-hero — SAY IT ONCE
   The panel currently announces "liquidity heatmap" in the window title, again
   on a floating chip, and a third time in the caption underneath. Three labels
   for one picture reads as clutter, not emphasis. The caption is the one that
   stays (it is the one that changes with the carousel and carries the
   sentence); the redundant chip goes, and the surviving chip is moved off the
   artwork so it stops covering the top-left of the book.
--------------------------------------------------------------------------- */
html.fx-hero .d3-chip--a{display:none}
html.fx-hero .d3-chip--b{right:-18px;bottom:26%}

/* ---------------------------------------------------------------------------
   fx-plan — THE ANNUAL CARD'S COLLISION
   The depth pass pushes the recommended plan forward with translateZ AND
   scale(1.015). The scale is what breaks it: the card grows into its own
   "BEST VALUE" badge and the badge then sits across the border, next to the
   "LAUNCH -50%" chip, so the top edge reads as three overlapping objects.
   And translateZ is the same mistake wearing a different hat: under the
   1200px perspective on .plans, translateZ(50px) magnifies the card by 4.3%,
   which is MORE than the scale it was paired with. Any geometric push forward
   resizes this card, and this card has a badge pinned to its top border and a
   sibling it must line up with. It is already the loudest thing in the section
   — red border, amber badge, the only solid button. Depth by light only.
--------------------------------------------------------------------------- */
html.fx-plan .plans .plan--hero{
  transform:none;
  box-shadow:
    0 0 0 1px rgba(229,53,47,.22),
    0 40px 80px -34px rgba(229,53,47,.6),
    0 34px 60px -30px rgba(0,0,0,1),
    var(--d3-rim);
}

/* ---------------------------------------------------------------------------
   fx-axis — THE ORPHAN
   The faint price-scale watermark is anchored so high in #pricing that the
   section clips its top half, leaving "YOUR LOCKED RATE $100/mo" floating in
   the margin looking like a element that failed to load. Pull it down inside
   the section it belongs to.
--------------------------------------------------------------------------- */
html.fx-axis .pricing__axis{
  top:auto;bottom:8%;opacity:.5;
}
@media (max-width:1200px){
  html.fx-axis .pricing__axis{display:none}
}

/* ---------------------------------------------------------------------------
   fx-rhythm — ONE GAP, NOT ELEVEN
   Measured between the last line of one section and the first of the next:
   20px, 113, 159, 160, 162, 207, 207, 208, 303, 308. The page has no vertical
   rhythm at all — trade->pricing is jammed while why->desk is a void you can
   scroll through. A single value applied at both ends fixes both complaints
   and takes 333px off the desktop page without deleting a word. On a phone it
   is close to neutral: the phone rules already halved these paddings.
--------------------------------------------------------------------------- */
html.fx-rhythm .section{padding-block:clamp(56px,5.4vw,86px)}
html.fx-rhythm .section--tight{padding-block:clamp(48px,4.6vw,74px)}
html.fx-rhythm .styles__note,
html.fx-rhythm .table__note,
html.fx-rhythm .rack__note,
html.fx-rhythm .plans__note{margin-top:var(--sp-5)}
/* #trade was the one section with no breathing room at all */
html.fx-rhythm #trade{padding-bottom:clamp(56px,5.4vw,86px)}
@media (max-width:600px){
  html.fx-rhythm .section{padding-block:clamp(44px,9vw,64px)}
  html.fx-rhythm .section--tight{padding-block:clamp(38px,7.5vw,54px)}
}

/* ---------------------------------------------------------------------------
   fx-table — THE WALL OF "INCLUDED"
   Seventeen rows, twelve of which say the same word, with shipped and unshipped
   items interleaved in no order — so the one thing a buyer actually wants from
   this table (what do I get TODAY?) takes seventeen reads to answer. The rows
   are regrouped by the JS; this styles the result: a tick instead of the
   repeated word, the status column pulled in from the far edge so the eye is
   not crossing 500px of nothing, and a header for each group.
--------------------------------------------------------------------------- */
/* The table is 1180px wide for two columns of text; pulling it in is what
   stops the eye crossing 400px of nothing to find a one-word answer.
   It stays on the section's left axis: the eyebrow, heading, lede, the two
   fit columns and the closing note all start at the same edge, and centring
   the one block that carries a visible border indented it ~118px from all of
   them, which read as the whole section being off-centre. The lede and the
   note are already narrower than the column and left-aligned; the table now
   belongs to that same family.
   Widths only above the phone breakpoint: a fixed 150px status column forces
   the table wider than a 390px screen, and this table's wrapper does not clip,
   so the whole document picks up a horizontal scroll. The grouping below is
   markup, costs no width, and helps most on the small screen anyway. */
@media (min-width:721px){
  html.fx-table .tablewrap--own{max-width:880px;margin-inline:auto}
  /* auto, not 0. Capping the width without centring left an 880px table
     pinned to the left of a 1180px section — which is the "why is this
     off to one side" that the cap was supposed to fix in the first place. */
  html.fx-table .table--own td.is-us,
  html.fx-table .table--own th.is-us{width:150px;padding-left:var(--sp-5)}
}
html.fx-table .fx-group th{
  text-align:left;
  /* the sheet sets nowrap on th, and a full-sentence group header under that
     rule sets the table's minimum width — 491px on a 390px phone */
  white-space:normal;
  font-family:var(--font-display);font-size:var(--display-xs);
  letter-spacing:var(--display-track-wide);text-transform:uppercase;
  color:var(--text-secondary);background:var(--surface-panel);
  padding:var(--sp-3) var(--sp-4);border-top:1px solid var(--line-strong);
}
html.fx-table .fx-group--soon th{color:var(--select)}
html.fx-table .fx-tick{
  color:var(--up);font-family:var(--font-data);font-size:var(--data-md);
  display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
}
html.fx-table .fx-tick::before{content:'✓';font-size:13px;line-height:1}

/* ---------------------------------------------------------------------------
   fx-calm — THE ENTRANCE STOPS MOVING
   The scroll-reveal animation was deliberately removed from this site once
   before ("quite horrid"), and the depth pass I shipped this morning quietly
   put one back: content now arrives from 70px away with a blur. The depth
   itself — the planes, the shadows, the hover recession — is not motion and
   stays. Only the arrival goes.
--------------------------------------------------------------------------- */
html.fx-calm .reveal:not(.d3-in),
html.fx-calm .reveal{opacity:1;transform:none;filter:none;transition:none}

/* ---------------------------------------------------------------------------
   fx-read — CONTRAST
   Body copy runs #8C8C8C on #0A0A0A: 5.0:1, which passes, and #6E6E6E for
   secondary lines: 3.2:1, which does not. On a page people read at night with
   the brightness down, the difference is whether the second half of a sentence
   is worth finishing. Headings and data keep their exact colours; this only
   touches prose.
--------------------------------------------------------------------------- */
html.fx-read{
  --text-muted:#A6A6A6;
  --text-faint:#8A8A8A;
}
html.fx-read .lede,
html.fx-read .feat__item p,
html.fx-read .who__card p,
html.fx-read .card p{color:#B4B4B4}
html.fx-read .four__row span:not(.four__n),
html.fx-read .rack__row p{color:#A6A6A6}

/* ---------------------------------------------------------------------------
   fx-mobile — THE PRODUCT, ON A PHONE, WITHOUT SCROLLING
   On a 390x844 phone the first sight of the actual product is 740px down: the
   headline, five lines of sub-copy, two full-width buttons and a four-line
   spec list all come first. Tightening those four things lifts the top of the
   window into the first screen, which is the only thing on this page that
   cannot be described in words.
--------------------------------------------------------------------------- */
@media (max-width:600px){
  html.fx-mobile .hero{padding-top:var(--sp-5)}
  html.fx-mobile .hero__sub{font-size:var(--body-md);margin-top:var(--sp-4)}
  html.fx-mobile .hero__cta{margin-top:var(--sp-5);gap:var(--sp-2)}
  html.fx-mobile .hero__cta .btn{padding-block:13px}
  html.fx-mobile .hero__note{
    margin-top:var(--sp-4);gap:var(--sp-1) var(--sp-4);font-size:10px;
  }
  /* The spec list used to carry a price line in two variants and this hid
     them on a phone. They are gone from the markup now — the price is on the
     sale bar, the CTA and the pricing section — so there is nothing left worth
     hiding and all three lines stay. */
  html.fx-mobile .hero__stage{margin-top:var(--sp-5)}
}

/* ===========================================================================
   ROUND TWO — 2026-09-09, after looking at the depth pass with reading eyes
   rather than design eyes. Four of these are repairs to the depth pass itself.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   fx-haze — THE FOG WAS EATING THE TEXT
   The aerial haze lays up to 69% black over the bottom 100px of the viewport
   and 80% over the top. On a page you scroll, EVERY line passes through that
   band on its way up: text arrives dim, brightens, then dims again as it
   leaves. It looked like atmosphere in a screenshot and reads as a dying
   monitor in use. The idea was wrong, not the amount — the fix is to delete
   it, not to tune it.
--------------------------------------------------------------------------- */
html.fx-haze .d3-haze{display:none}

/* ---------------------------------------------------------------------------
   fx-hover — THE RECESSION WAS TOO EAGER, AND BLURRED WORDS
   Two faults. First, it triggers on the PLANE, so resting the pointer in a
   gutter between cards dims all twelve and lifts none — the page goes grey for
   no reason and nothing is gained. Gating on :has() means a tile must actually
   be under the pointer. Second, blur(.6px) on body text is the single most
   expensive thing here: the eleven cards you are not pointing at become
   genuinely harder to read. Depth-of-field is a photographic idea; on a page
   made of words it just makes the words worse. Dim only, and gently.
--------------------------------------------------------------------------- */
html.fx-hover .d3-plane:hover > .d3-tile{filter:none;transform:none}
html.fx-hover .d3-plane:has(> .d3-tile:hover) > .d3-tile{
  filter:brightness(.86);
  transform:scale(.995);
}
html.fx-hover .d3-plane:has(> .d3-tile:hover) > .d3-tile:hover{
  filter:none;
  transform:translate3d(0,calc(var(--d3-lift) * -.7),40px)
            rotateX(calc(var(--d3-ty,0) * var(--d3-tilt) * .6))
            rotateY(calc(var(--d3-tx,0) * var(--d3-tilt) * .6));
}
/* the pricing plane is the one place where dimming is actively wrong: it
   greys out the plan being recommended the moment anyone looks at the other */
html.fx-hover .plans:has(> .plan:hover) > .plan{filter:none;transform:none}
html.fx-hover .plans > .plan:hover{transform:translateY(-3px)}
/* the shared spotlight was doing very little at 7% and washing type at the
   same time; the tile lift already says which one you are on */
html.fx-hover .d3-plane::after{display:none}

/* ---------------------------------------------------------------------------
   fx-band — TWELVE ROWS OF COPY ON TOP OF A LIVE HEATMAP
   .band__heat is masked radial(90% 80% at 50% 50%, #000 20%, transparent) —
   which keeps the decoration at FULL strength exactly where the text is and
   fades it at the edges, the opposite of what it needs to do. Coloured bars
   and a price line run straight through the study descriptions. Same artwork,
   held back where the words are and left alive around them.
--------------------------------------------------------------------------- */
html.fx-band .band__heat{
  opacity:.14;
  -webkit-mask-image:radial-gradient(78% 62% at 50% 50%,transparent 18%,#000 88%);
          mask-image:radial-gradient(78% 62% at 50% 50%,transparent 18%,#000 88%);
}
/* and a settled ground under the rack itself, so no row is ever read off a
   bright band */
html.fx-band .rack{
  position:relative;isolation:isolate;
}
html.fx-band .rack::before{
  content:'';position:absolute;inset:-24px -32px;z-index:-1;pointer-events:none;
  background:radial-gradient(120% 100% at 50% 50%,rgba(10,10,10,.92),rgba(10,10,10,.55) 70%,transparent);
}

/* ---------------------------------------------------------------------------
   fx-cards — THE RAGGED GRID
   Every "Read more" sits directly under its own paragraph, so across a row of
   four cards the links land at four different heights and the twelve-card grid
   reads as ragged. Cards are already equal height; the link just needs to be
   told to go to the bottom of one.
--------------------------------------------------------------------------- */
html.fx-cards .grid > .card{display:flex;flex-direction:column}
html.fx-cards .grid > .card .card__more{margin-top:auto;padding-top:var(--sp-4)}
html.fx-cards .grid > .card p{margin-bottom:0}

/* ---------------------------------------------------------------------------
   fx-foot — THE FOOTER ADVERTISES THE COMPETITION
   "Not affiliated with CME Group, Bookmap, ATAS or Sierra Chart." Three of
   those four are rivals, printed on our own home page, under a house rule that
   says their names go nowhere in our product or UI. The disclaimer does real
   work — it is the sentence that stops anyone claiming we imply endorsement —
   but it does that work without the names, and the page no longer mentions
   them anywhere else. The wording swap is in the JS.
   Also: the subscribe box is 90px taller than the thing inside it.
--------------------------------------------------------------------------- */
html.fx-foot #subscribe .cta{padding-bottom:var(--sp-8)}
html.fx-foot #subscribe .form{margin-bottom:0}

/* ---------------------------------------------------------------------------
   fx-chip — THE THING THAT POPS OUT WORST
   The floating chip sits ON the product shot, over the top-left corner of the
   book, and it is the one element on the page that looks stuck on rather than
   built in. The panel already has a title bar and a caption underneath; a
   third label hovering in front of the artwork is decoration arguing with the
   product. This turns the last one off — the hero keeps the tilt, the glare,
   the under-glow and the cast shadow, which is where the depth actually is.
--------------------------------------------------------------------------- */
html.fx-chip .d3-chip{display:none}

/* ---------------------------------------------------------------------------
   fx-real — THE HERO SHOWS THE PRODUCT, NOT A DRAWING OF IT
   The four hero panels are drawn in JavaScript: about thirty bars, a smooth
   price line, a handful of bubbles. The real market-by-order book has hundreds
   of hairline rows of resting size and a ladder you can read prices off. Side
   by side the canvas reads as an illustration of a heatmap, and on a page whose
   entire claim is depth, an illustration is the wrong thing to lead with.
   These are the same panels rendered out of the v3.1 tree at the hero's own
   size, off the recorded 3 Sep NQ session, cropped to the live edge.

   The honesty cost is the point of the status line below: a still is not a
   live feed, so it must not say LIVE FEED - CONNECTED. It says what it is.
--------------------------------------------------------------------------- */
html.fx-real .hero .miniCanvas{display:none}
/* FIT BY RENDERING TO THE FRAME, not by bending one to the other. The window is
   16/10; the shots are now rendered 1178x736, which is exactly 16/10, so the
   image fills it with no crop, no letterbox and no stretch. The override is
   still needed because the sheet drops the frame to 4/3 at the tablet
   breakpoint, and a 4/3 box would crop the sides off a 16/10 screenshot. */
html.fx-real .hero .frame__body{aspect-ratio:16 / 10}
html.fx-real .fx-shot{
  position:absolute;inset:0;width:100%;height:100%;
  /* cover is safe ONLY because the render and the box are the same aspect now.
     They were not before — the shots were 1.524 in a 1.600 box, so cover scaled
     to the width and ate the top and bottom of the image, which on a screenshot
     of an application means its toolbar and its status bar. The app's own
     chrome is the entire reason for using a real render, and it was the first
     thing being cropped off. */
  object-fit:cover;object-position:50% 50%;display:block;
  /* refine.js paints the still under a loop as background-image, so the panel
     is there before the loop has downloaded; cover keeps it on the same crop */
  background:#0A0A0A 50% 50% / cover no-repeat;
}
html.fx-real .hero .frame__clock{display:none}
html.fx-real .hero .frame__status{color:var(--text-muted)}

/* ---------------------------------------------------------------------------
   fx-glare — THE SHEEN COMES OFF THE PRODUCT
   The depth pass puts a pointer-tracked highlight on a plane 80px in front of
   the hero window. On an empty slab that reads as glass. Over a heatmap it
   reads as a smear across the one thing on the page a visitor is trying to
   look INTO — and the moment they move the mouse toward the panel to inspect
   it, the panel gets harder to see. A picture of a book should get clearer
   when you approach it, not dimmer.
   The tilt, the under-glow and the cast shadow stay; only the film goes.
--------------------------------------------------------------------------- */
html.fx-glare .d3-glare{display:none}


/* ---------------------------------------------------------------------------
   fx-arrows — THE CAROUSEL ARROWS COME OFF THE PICTURE
   Both arrows sit ON the panel, one of them right where the big-trade markers
   land. They are navigation furniture parked on top of the product shot: the
   two things a visitor most wants to look at are underneath the two things
   they are least likely to need. Outside the frame on anything wide enough to
   hold them, and on narrow screens they fade back until the panel is touched,
   where the dots underneath are doing the same job anyway.
--------------------------------------------------------------------------- */
/* Scoped to .stage__wrap — the lightbox reuses .stage__arrow for its own
   buttons, and an unscoped rule here pushed those to left:-52px, i.e. clean off
   the screen. Every rule that styles a shared control has to say WHERE. */
@media (min-width:1100px){
  html.fx-arrows .stage__wrap > .stage__arrow--prev{left:-52px}
  html.fx-arrows .stage__wrap > .stage__arrow--next{right:-52px}
}
@media (max-width:1099px){
  html.fx-arrows .stage__wrap > .stage__arrow{opacity:0;transition:opacity var(--dur-base) var(--ease)}
  html.fx-arrows .stage__wrap:hover > .stage__arrow,
  html.fx-arrows .stage__wrap > .stage__arrow:focus-visible{opacity:1}
}

/* ---------------------------------------------------------------------------
   fx-zoom — THE PANEL OPENS
   The shot is 1178px of real interface displayed at about 580. Half the detail
   in it — the per-level sizes down the ladder, the contract counts on the
   markers — is there and unreadable, which is a strange thing to do with the
   one asset that proves the product is real. A click opens it at full size.
   The affordance has to be visible or nobody clicks: cursor, a hint that fades
   up on hover, and the frame's edge lifting to say the thing is a control.
--------------------------------------------------------------------------- */
html.fx-zoom .hero .stage{cursor:zoom-in}
html.fx-zoom .hero .stage__wrap::after{
  content:'Click to enlarge';
  position:absolute;right:14px;bottom:60px;z-index:6;pointer-events:none;
  padding:5px 10px;border:1px solid var(--line-strong);border-radius:var(--r-pill);
  background:rgba(16,16,16,.86);backdrop-filter:blur(8px);
  font-family:var(--font-data);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--text-secondary);
  opacity:0;transition:opacity var(--dur-base) var(--ease);
}
html.fx-zoom .hero .stage__wrap:hover::after{opacity:1}
html.fx-zoom .hero .stage:focus-visible{outline:2px solid var(--select);outline-offset:3px}
/* the lightbox sits above the depth pass's haze and the sticky nav */
.fx-lb{z-index:400}
@media (hover:none){
  html.fx-zoom .hero .stage__wrap::after{opacity:1}
}

/* ---------------------------------------------------------------------------
   fx-behind — EVERY GLOW GOES BEHIND THE THING IT IS LIGHTING
   The hero's red orb is `position:absolute; z-index:0` on .hero::after, and the
   headline it sits behind is `position:relative; z-index:auto`. Equal stacking,
   so paint order decides — and a pseudo-element paints AFTER its host's
   children. The orb has been washing over "SEE THE BOOK, NOT THE CANDLE" the
   whole time: 26% red at 90px blur across white display type, which is exactly
   the kind of haze that makes text feel slightly out of focus without ever
   looking like a bug you could point at.
   One line each. The glows keep every bit of their job — they are the light the
   panels are lit BY — they just stop being in front of the light's subject.
--------------------------------------------------------------------------- */
html.fx-behind .hero::after{z-index:-1}
html.fx-behind .d3-underglow,
html.fx-behind .d3-cast{z-index:-2}
/* belt and braces on the two that already behave, so a later edit to either
   sheet cannot quietly promote them over the copy */
html.fx-behind .hero__grid{z-index:-3}
html.fx-behind .band__heat{z-index:-1}

/* ---------------------------------------------------------------------------
   fx-tilt — THE PANEL ARRIVES ALREADY TURNED
   The depth pass only yaws the window while a pointer is over the hero, so the
   first thing anyone sees is a flat rectangle, and the 3D reads as a trick you
   discover rather than as how the page is built. Worse, it never happens at all
   on a phone. A resting yaw means the panel is a three-dimensional object from
   the first frame, and the pointer then just moves something that was already
   moving.

   The angle turns the window's face toward the headline it belongs to. The JS
   writes --d3-rx / --d3-ry as inline custom properties, which would beat any
   stylesheet value for those vars — so this overrides the composed `transform`
   instead, and only while the pointer is away (.is-live means the pointer has
   arrived and takes over).
--------------------------------------------------------------------------- */
/* NEGATIVE yaw is the one that reads as "turned to the left": the left edge
   comes forward and the right edge recedes. I had this the wrong way round —
   +7 brings the RIGHT edge forward, which reads as turned to the right. */
html.fx-tilt{--d3-rest-yaw:-7deg;--d3-rest-pitch:2.5deg}
html.fx-tilt .d3-persp:not(.is-live) .stage__wrap{
  transform:
    rotateX(var(--d3-rest-pitch)) rotateY(var(--d3-rest-yaw))
    translate3d(0,var(--d3-py,0px),0);
  transition:transform 700ms cubic-bezier(.16,1,.3,1);
}
/* the cast shadow has to lean with it or the window looks pasted on */
html.fx-tilt .d3-persp:not(.is-live) .d3-cast{
  transform:translate3d(13px,0,-120px);
}

/* ---------------------------------------------------------------------------
   ...but square-on below a laptop. The resting turn exists so the window reads
   as a three-dimensional object rather than a flat rectangle, and on a desktop
   there is room either side for the perspective to be legible. On a phone or a
   tablet the panel is most of the screen's width, so the same 7 degrees stops
   reading as depth and starts reading as a picture that is not straight. The
   pitch stays: a slight recline still says "object", and it costs no width.
--------------------------------------------------------------------------- */
@media (max-width:1024px){
  html.fx-tilt{--d3-rest-yaw:0deg}
  html.fx-tilt .d3-persp:not(.is-live) .d3-cast{transform:translate3d(0,0,-120px)}
}


/* ---------------------------------------------------------------------------
   The enlarged view: the site's own arrow, in the margin beside the card
   rather than a circle parked on the picture. It inherits .stage__arrow, so it
   is the same control the carousel uses — same size, same hover, same focus
   ring — and only its position is set here.
--------------------------------------------------------------------------- */
.fx-lb__nav{
  position:fixed;top:50%;z-index:5;
  /* the carousel's own arrow sits on the page, where a near-black fill reads
     fine. Here it sits on the modal scrim, which is also near-black — the
     button disappeared into it. Same control, lifted enough to be seen. */
  background:rgba(32,32,32,.96);border-color:var(--text-faint);
  box-shadow:0 10px 30px -12px rgba(0,0,0,1);
}
.fx-lb__nav--prev{left:max(16px,calc(50% - min(1500px,96vw)/2 - 60px))}
.fx-lb__nav--next{right:max(16px,calc(50% - min(1500px,96vw)/2 - 60px))}

/* the window chrome carried into the lightbox: the frame draws the border, so
   the card gets out of its way */
.fx-lb .modal__card{padding:0;background:transparent;border:0;box-shadow:none;
  width:min(1500px,96vw);max-height:94vh;overflow:visible}
.fx-lb .modal__x{top:-34px;right:0;z-index:6}
.fx-lb__frame{margin-top:0}
.fx-lb__frame .frame__body{aspect-ratio:16 / 10;height:auto}
.fx-lb__frame .frame__body img{
  width:100%;height:100%;object-fit:cover;display:block;max-height:none;
}
.fx-lb .lightbox__cap{margin-top:var(--sp-3)}
@media (max-width:900px){
  .fx-lb__nav--prev{left:8px}
  .fx-lb__nav--next{right:8px}
  .fx-lb .modal__x{top:-30px}
}

/* ---------------------------------------------------------------------------
   fx-shots — the real renders in the desk mosaic and the tool panels
   The containers were sized for the canvas simulations, so each one is given
   the shots' 16/10 first — otherwise cover crops the app's toolbar and status
   bar off again, which is the whole thing the renders are there to show.
   The one exception is the tall heatmap cell in the mosaic: it spans two rows
   and is nearly square by construction, so it crops rather than fits — and it
   crops from the LEFT, because the live edge and the ladder are on the right
   and they are the half worth keeping.
--------------------------------------------------------------------------- */
html.fx-shots .miniCanvas:not(.band__heat){display:none}
html.fx-shots .hero .miniCanvas{display:none}
.fx-shot--panel{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  object-fit:cover;object-position:100% 50%;background:#0A0A0A;
}
html.fx-shots .frame--sm .frame__body{aspect-ratio:16 / 10}
html.fx-shots .desk__cell--foot,
html.fx-shots .desk__cell--dom{aspect-ratio:16 / 10}
html.fx-shots .desk__cell{position:relative}
/* the tall cell is square and so is its render: nothing to crop, so centre it */
html.fx-shots .desk__cell--heat .fx-shot--panel{object-position:50% 50%}
html.fx-shots .desk__cell--foot .fx-shot--panel,
html.fx-shots .desk__cell--dom .fx-shot--panel,
html.fx-shots .feat__panel .fx-shot--panel{object-position:50% 50%}
/* the panel tags sit on top of the shot, not under it */
html.fx-shots .desk__tag{z-index:2}

/* ---------------------------------------------------------------------------
   fx-tilt, second pass — THE WINDOW ONLY EVER TURNS ONE WAY
   Pointer yaw ran -6.5 to +6.5, so moving the mouse left swung the panel round
   to face out of the page, away from the copy. Clamping the yaw at zero means
   it can look left, up and down and never right: the resting angle is the
   furthest it ever comes back, and the pointer only ever turns it further
   toward the headline.
--------------------------------------------------------------------------- */
html.fx-tilt .d3-persp.is-live .stage__wrap{
  transform:
    rotateX(var(--d3-rx,0deg))
    /* The pointer ADDS to the resting turn instead of replacing it, and only
       in one direction. min(0deg, --d3-ry) is zero whenever the pointer is on
       the right half and negative on the left, so the window rests at -7 and
       can be turned as far as about -13 by moving left — never back toward
       square, never past it to the right. A plain min(rest, ry) clamp pinned it
       at -7 in both directions, because the pointer's own range (+-6.5) never
       reaches below the resting angle. */
    rotateY(calc(var(--d3-rest-yaw) + min(0deg, var(--d3-ry,0deg))))
    translate3d(0,var(--d3-py,0px),0);
}

/* The app's own toolbar names every panel — VEILHEATMAP, VEILCHARTS, ADVANCED
   DOM — so the site's coloured tag chips now sit on top of a label that is
   already there, and collide with it. With the canvas drawings the chips were
   the only label; with the real renders they are a duplicate in the way. */
html.fx-shots .desk__grid .desk__tag{display:none}

/* ---------------------------------------------------------------------------
   The cropped ladder needs its margin back INSIDE a panel
   Cropping the DOM to its content was right for the hero and the lightbox,
   where the site's own window frame draws the border. In the mosaic and the
   tool panels there is no such border — the cell edge IS the boundary — so the
   ladder ran straight into the grid hairline and read as clipped. A few pixels
   of the panel's own ground puts the frame back without bringing the old 16px
   gutter with it. The heatmap and footprint already have this: it is their app
   chrome's own padding.
--------------------------------------------------------------------------- */
html.fx-shots .desk__cell--dom .fx-shot--panel,
html.fx-shots .feat__panel .fx-shot--panel{
  padding:7px;background:var(--surface-panel);
  /* CONTAIN, not cover: with box-sizing:border-box the padding shrinks the
     content box, and cover then scales to fill it and crops the difference —
     which took the bottom off the position strip. Contain fits the whole
     picture inside the inset and the few spare pixels sit on the panel's own
     ground, where they are invisible. */
  object-fit:contain;
}

/* ---------------------------------------------------------------------------
   NOTHING POPS OUT OF THE DESK OR THE STUDIES RACK
   Both were given the hover lift by the depth pass. On a mosaic that is making
   the point "these are four panels on ONE tape", pulling one of them forward
   argues against the sentence. And in the studies rack the rows are a numbered
   LIST — a list item that leaps at the cursor is a fidget, not an affordance.
   The lift stays where it earns its place: the feature cards and the plans,
   where each tile really is a separate thing you might choose.
--------------------------------------------------------------------------- */
html.fx-hover .desk__grid:hover .desk__cell,
html.fx-hover .desk__grid .desk__cell:hover{
  transform:none;filter:none;box-shadow:none;
}
html.fx-hover .rack:has(> .rack__row:hover) > .rack__row,
html.fx-hover .rack > .rack__row:hover{
  transform:none;filter:none;box-shadow:none;
}
/* the rack keeps its own quiet row highlight from styles.css, which is the
   right amount of feedback for a list */

/* ---------------------------------------------------------------------------
   fx-badge — TWO LABELS FIGHTING FOR ONE CORNER
   The annual card carries a centred "BEST VALUE" pill pinned to its top border
   and a "LAUNCH -50%" chip pinned to its top right. Measured, the pill runs
   x105-323 and the chip x310-403: they overlap by 13 px and step diagonally
   down into the corner, which is what makes the top of the card look like an
   accident rather than a design.

   The pill becomes a full-width band instead. It cannot collide with anything
   because it owns the whole edge, it says "this is the one" far more clearly
   than a small centred pill, and it costs no layout: the badge was already
   absolutely positioned over the 28 px of top padding the card reserves, so
   the two cards still line up exactly.
--------------------------------------------------------------------------- */
html.fx-badge .plan__badge{
  left:0;right:0;transform:none;width:auto;
  display:flex;align-items:center;justify-content:center;gap:8px;
  padding:5px var(--sp-4);
  box-shadow:0 10px 26px -14px rgba(255,176,32,.75);
}
/* ...and the LAUNCH chip comes off this card entirely. It was the second of
   two labels competing for one corner, and it is the more redundant of the
   two: the line directly under the price already reads "$2,000 / yr list
   price" struck through, which says -50% more concretely than a chip can. The
   monthly card keeps its chip — it has no band and nothing to collide with. */
html.fx-badge .plan--hero .plan__tag{display:none}
/* the card's own top padding already reserves 28px for the badge, so nothing
   here moves the content and the two cards stay aligned to the pixel */

/* ---------------------------------------------------------------------------
   fx-centre — THE COMPARISON SECTION SITS CENTRED, LIKE ITS NEIGHBOURS
   #pricing above it and #faq below it both centre their heading block; #compare
   was the odd one out, left-aligned over a table that the earlier fix had
   already pulled in to 880px and centred. So the table sat in the middle of the
   page with its own heading hard against the left margin, which is the mismatch
   that reads as wrong rather than the alignment itself.
   The table rows stay left-aligned — centring a list of features would make it
   unreadable. Only the heading block moves.
--------------------------------------------------------------------------- */
html.fx-centre #compare > .wrap > .eyebrow{justify-content:center}
html.fx-centre #compare > .wrap > h2,
html.fx-centre #compare > .wrap > .lede{text-align:center}
html.fx-centre #compare > .wrap > .lede{margin-inline:auto}
html.fx-centre #compare > .wrap > .table__note{text-align:center;margin-inline:auto;max-width:74ch}
