/* ============================================================================================
   ChessBase for Mac — landing page stylesheet.
   This is the CB17 upgrade landing's cb-landing.css taken across whole, so the two pages stay
   siblings and every fix made there still applies here. Sections this page does not use
   (timeline, comparison table, packages, offer, why bar, the since-counter) are inert and left
   in place; the knight was removed. Everything specific to THIS page is at the very end, under
   "ChessBase for Mac — the Mac look".
   ============================================================================================ */
/* ============================================================================================
   ChessBase — CB17-26 / F21 upgrade landing.
   Design system: the house style of the live cb14/15/16upgrade.chessbase.com pages,
   recoloured to the August 2026 CI manual.

   WHERE THIS COMES FROM
     Layout, rhythm and energy are lifted from the live CB15 upgrade page: white paper with a
     faint chequer, poster headlines in condensed type at display scale, a version timeline
     over a comparison table, oversized feature names in red, box shots, package cards.
     That page sells; this one has the same job.

   WHERE IT DELIBERATELY DIFFERS FROM THOSE LIVE PAGES
     · Red is #B70801, not the #ED1C24 those pages use. The August 2026 CI manual names
       #B70801 as the only permitted brand red and says it overrides earlier documents.
       If you would rather match the live pages exactly, change --cb-red here and nowhere else.
     · Display type is Trade Gothic Pro Bold Condensed (the CI's display face, bundled) rather
       than Anton. Same poster weight, and it is the licensed brand face.
     · Cyan #0094D1 is used, which the manual's palette does not contain. See CAMPAIGN PALETTE
       below: it is sampled from the CB26 key art this page is built around, it is scoped to
       this campaign, and it never takes a CTA.

   BACKGROUND IS WHITE, except the hero. Not cream, not paper-warm: a previous revision used the
   CI's Gelb hell #FEFDE8 as the page ground and it read as yellow. The hero band is the one
   exception, and only because it is set to the poster's own cream so the artwork dissolves into
   the page rather than sitting on it in a box.

   TEXTURES. Three, all generated in CSS — no image requests, and the page still makes zero
   external requests of any kind. Paper grain on the light bands, a large masked chequer that
   fades in below the headlines, and a wall of real Marshall Attack notation on the dark band.
   Each is held well below the copy; if you can read the notation without looking for it, it is
   too strong.
   ============================================================================================ */

/* ---------------------------------------------------------------- fonts ---- */
/* Self-hosted; the page makes no external requests at all.

   ANTON AND MONTSERRAT, the CB14/15/16 campaign's own faces, replacing Gotham and Trade Gothic Pro
   Bold Condensed. A deliberate departure from the August 2026 CI manual, which names Trade Gothic
   as the display face — made so this page reads as part of that campaign rather than beside it.

   Montserrat here is a VARIABLE font: one file per subset covers 400-800, declared once with a
   weight range instead of five times. Only latin and latin-ext ship; the campaign also carries
   Cyrillic and Vietnamese, which two languages do not need.

   Licensing improves with the change: both are under the SIL Open Font License, where Gotham and
   Trade Gothic are commercial faces whose licensing the README still lists as unresolved. */
@font-face { font-family:'Roboto'; src:url('fonts/Roboto-latinext-italic.woff2') format('woff2');
             font-weight:100 900; font-style:italic; font-display:swap;
             unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'Roboto'; src:url('fonts/Roboto-latin-italic.woff2') format('woff2');
             font-weight:100 900; font-style:italic; font-display:swap;
             unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family:'Roboto'; src:url('fonts/Roboto-latinext.woff2') format('woff2');
             font-weight:100 900; font-style:normal; font-display:swap;
             unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'Roboto'; src:url('fonts/Roboto-latin.woff2') format('woff2');
             font-weight:100 900; font-style:normal; font-display:swap;
             unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* --------------------------------------------------------------- tokens ---- */
:root {
  /* CI palette, August 2026 manual — nothing invented. */
  --cb-red:        #B70801;   /* the only brand red. CTAs, display accents, highlights   */
  --cb-navy:       #12233A;   /* dark ground, headings                                   */
  --cb-grey:       #637178;   /* secondary text                                          */
  --cb-line:       #9CA4A7;   /* borders and rules — never text                          */
  --cb-cream:      #FEFDE8;   /* used ONLY as the highlighter-pen mark, never as a ground */
  --cb-cream-deep: #F5F1D3;   /* the highlighter itself                                  */
  --cb-black:      #1A1A1A;   /* body copy                                               */
  --cb-white:      #FFFFFF;   /* the page                                                */

  /* ---- CAMPAIGN PALETTE ---------------------------------------------------------------
     Approved 2026-08-06 as a deliberate, campaign-scoped extension to the August 2026 manual.
     The CB26 key art is 31% cream and 25% cyan; a page that quotes that artwork while refusing
     its colours reads as two things bolted together. Cyan is an ACCENT — it marks what is new
     and what the software found. It never takes a CTA: red still means "click this". */
  --cb-cyan:       #0094D1;   /* sampled from the key art                                  */
  --cb-cyan-deep:  #0077A8;   /* :hover                                                    */

  /* CHART SERIES — THE SAME MOVE, THE SAME COLOUR, ON THE BOARD AND IN BOTH CHARTS. Slot 1 in a
     trend chart is the same move as the first arrow on the board, so it takes the same ChessBase
     colour: HC_GREEN, then HC_BLUE, HC_YELLOW, HC_RED. That correspondence is the whole point of
     the change — a reader who has picked out the green arrow can follow it straight down into
     "Wie sich die Mode verschoben hat" without being told.
     DARKENED, AND ONLY AS FAR AS THEY HAD TO BE. ChessBase picks those colours for a blue or wooden
     BOARD; these charts sit on a white card, where the raw values are 1.25:1 (cyan), 1.57:1
     (yellow), 1.90:1 (orange) and 2.41:1 (green) — a cyan line on white is effectively invisible,
     and the move labels are 12px text, which check-page.js holds to 4.5:1. So each keeps its exact
     hue and saturation from BoardWindow2D.cpp:274-296 and loses only lightness, stopping at the
     first value that clears 4.5:1. Blue needed nothing: #000AFF is already 8.44:1.
     Re-derive rather than nudge by eye if these ever change — the arrow and the line must stay the
     same hue or the correspondence is a lie. */
  --cb-series-1:   #0A8A07;   /* HC_GREEN  #0EC20A, L .40 -> .28,  4.51:1 on white */
  --cb-series-2:   #000AFF;   /* HC_BLUE   unchanged,              8.44:1 */
  --cb-series-3:   #917100;   /* HC_YELLOW #FFC700, L .50 -> .28,  4.60:1 */
  --cb-series-4:   #ED0000;   /* HC_RED    #FF0000, L .50 -> .46,  4.56:1 */
  /* A DIVERGING PAIR LIVED HERE — amber #B26A00 under 50 % and teal #157F62 over it, for a version
     of .orestrip that drew each rating class as a deviation from break-even. It is gone with that
     design: the strip now uses the same White/draws/Black bar as .orewdl and the Monte Carlo panel,
     so the page draws a result exactly one way. Recorded rather than silently dropped because the
     reasoning is worth keeping if a diverging chart is ever wanted here: both hues are already
     sanctioned above, and neither is red-against-green — that pair is the one roughly one man in
     twelve cannot separate, which is why this set was chosen against colour-blind vision at all. */
  /* --cb-poster (#E7E6D4) lived here: the key art's own cream ground, which the hero band used
     to be painted in so the poster's rectangle dissolved into the page. The hero is white now
     and carries the chessboard like every other light band, so nothing needs the cream — and a
     token nobody uses is a suggestion to use it. Its two dependants went with it:
     --cb-poster-line (#CFCFB6), a hairline that only had to exist because the white-page one
     vanished on cream, and a darkened --cb-grey (#546066) for text that only failed AA on cream.
     All three are out of the approved palette in check-ci.js too, so putting the cream back is a
     deliberate act that has to be argued for rather than something that quietly reappears. */

  /* ---- THE CHESSBASE 26 BOARD AND ITS ARROWS ------------------------------------------------
     Not campaign colours, not invented, and deliberately outside the manual: these are the values
     ChessBase 26 itself ships, lifted from its source so that the board in the report widget is
     the board the reader opens in the program. A demo of CB26 that draws its own board in its own
     colours is arguing with the screenshot next to it.
       CBSrcCpp/FrameWork/Graphics/CBTheme.cpp:99-102 — board back, margin, light and dark squares,
       held there as COLORREF, which is 0x00BBGGRR, so the bytes are swapped round here.
       Cross-check: the classic board at CBSrcCpp/FrameWork/ChessApp/ChessShellVersion.cpp:54,64 is
       RGB(238,238,238) on RGB(125,145,190) = #EEEEEE on #7D91BE — the same blue family as the
       themed #799ABF, which is what confirms the byte order rather than assuming it.
       CBSrcCpp/FrameWork/ChessTools/DynamicHints.cpp:248-261 — the three arrow colours the program
       draws its analysis with: attack, defence, threat. It draws them at 70% opacity
       (TransparentColor's number is OPACITY, not transparency — 100 is opaque).
     Scoped to the board overlay and nothing else. They never touch page furniture, and red here
     is a chessboard arrow, never a call to action. */
  --cb26-board-back:   #E2E9F2;
  --cb26-board-margin: #153251;
  --cb26-sq-light:     #C5D3E2;
  --cb26-sq-dark:      #799ABF;
  /* THE ARROW PALETTE IS THE ONE THE PROGRAM OFFERS ITS USERS, not the dynamic-hints one. Both
     exist: DynamicHints.cpp has an attack/defence/threat set drawn at 70% opacity, but the colours
     a reader actually meets on a ChessBase board — the ones behind the coloured-arrow buttons, and
     the ones BoardWindow2DEx::DrawArrows resolves through GetHighlightColor() — are the table at
     CBSrcCpp/FrameWork/ChessGUI/BoardWindow2D.cpp:274-296. It is held there as HSL; these are those
     values converted:
       green  HSL(0.33, 0.9, 0.4)   red HSL(0, 1, 0.5)      yellow HSL(0.13, 1, 0.5)
       blue   HSL(0.66, 1, 0.5)     cyan HSL(0.5, 1, 0.5)   orange HSL(0.072, 1, 0.7) */
  --cb26-arrow-red:    #FF0000;   /* HC_RED    */
  --cb26-arrow-green:  #0EC20A;   /* HC_GREEN  */
  --cb26-arrow-yellow: #FFC700;   /* HC_YELLOW */

  /* Derived, each with a reason. */
  --cb-red-dark:   #8E0601;   /* :hover — same hue darkened, never a second red */
  --cb-navy-dark:  #0B1626;
  --cb-hairline:   #E4E6E6;   /* #9CA4A7 at ~18% over white; a full-strength hairline is too heavy at this scale */
  --cb-tint:       #F4F5F5;   /* the faintest possible band separator on white */

  /* Functional only — forms and system feedback. Distinct from the brand red by FORM as well
     as hue: brand red is always a solid filled block, functional red only ever a thin edge. */
  --cb-error:      #D93025;
  --cb-error-bg:   #FCEEEC;
  --cb-ok:         #1E7A4B;
  --cb-ok-bg:      #EAF5EF;

  --display: 'Roboto',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  --sans:    'Roboto',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  /* A third role the CI does not cover and the subject demands: chess notation, evaluations
     and Elo bands are data, and data wants tabular figures. Nothing brand-critical is set in
     it, so a system stack is right — it costs no bytes and never falls back to something odd. */
  --mono: ui-monospace,'Cascadia Mono','Segoe UI Mono',Consolas,'SF Mono',Menlo,monospace;
  /* Chess glyphs. Segoe UI Symbol ships on every Windows machine, which is where this page
     opens; the rest are for everyone else. */
  --chess: 'Segoe UI Symbol','Apple Symbols','Noto Sans Symbols 2','DejaVu Sans',sans-serif;

  /* ---- TEXTURES ------------------------------------------------------------------------
     Three, each derived from the subject rather than bought off the shelf.

     1. The chequer — the brand's own signet. Two failed settings are worth recording so nobody
        repeats them. At 2.2%/120px it was imperceptible, so it was doing nothing but sitting in
        the stylesheet. At 4.5%/44px it was WORSE than invisible: small neutral squares at that
        contrast are read as Photoshop's transparency grid, the one association a page built on
        printed artwork must not trigger. 120px squares in navy read as chess. */
  --chequer: repeating-conic-gradient(rgba(18,35,58,.085) 0% 25%, transparent 0% 50%);
  --chequer-size: 240px;   /* 240px tile = 120px squares — the knight's move length, see cb-extras.js */

  /* 2. Paper grain. The key art is a printed poster; this gives the page the same tooth so
        the artwork doesn't look pasted onto a screen. fractalNoise desaturated, multiplied
        at low alpha — no image request, no external anything. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");

  /* 3. The notation wall. Real algebraic notation — the opening of the Marshall Attack, the
        same game the analysis elsewhere on the page comes from — tiled as a background layer.
        This is the one texture no competitor can copy, because it only means something on a
        chess page. */
  --notation: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='560' height='150'%3E%3Ctext x='0' y='22' font-family='monospace' font-size='15' fill='%23ffffff' fill-opacity='.055'%3E1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 Nf6 5.O-O Be7%3C/text%3E%3Ctext x='0' y='60' font-family='monospace' font-size='15' fill='%23ffffff' fill-opacity='.055'%3E6.Re1 b5 7.Bb3 O-O 8.c3 d5 9.exd5 Nxd5 10.Nxe5%3C/text%3E%3Ctext x='0' y='98' font-family='monospace' font-size='15' fill='%23ffffff' fill-opacity='.055'%3ENxe5 11.Rxe5 c6 12.d4 Bd6 13.Re1 Qh4 14.g3 Qh3%3C/text%3E%3Ctext x='0' y='136' font-family='monospace' font-size='15' fill='%23ffffff' fill-opacity='.055'%3E15.Be3 Bg4 16.Qd3 Rae8 17.Nd2 Re6 18.a4 Qh5%3C/text%3E%3C/svg%3E");

  --sp-xs:4px; --sp-sm:8px; --sp-md:16px; --sp-lg:24px; --sp-xl:32px; --sp-2xl:48px; --sp-3xl:80px;
  --radius:6px; --radius-lg:14px;
  --maxw:1180px;
  --shadow-s:0 2px 10px rgba(18,35,58,.06);
  --shadow-m:0 18px 50px rgba(18,35,58,.12);
  --shadow-l:0 40px 90px rgba(18,35,58,.20);
  --head-h:72px;
}

/* ----------------------------------------------------------------- base ---- */
*,*::before,*::after { box-sizing:border-box; }
[hidden] { display:none !important; }

html {
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  scroll-padding-top:calc(var(--head-h) + 16px);   /* sticky header must not cover anchors */
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior:auto; }
  *,*::before,*::after { animation-duration:.001ms !important; transition-duration:.001ms !important; }
}

/* THE BOARD LIVES HERE, on the page itself rather than on each band.
   It used to be an ::after on every band, which meant eight separate tilings that had to be
   pulled into step by JavaScript (alignBoard, now deleted). One background on one element is one
   board by construction: it starts at the document's top-left and runs the whole length, and
   there is nothing left to keep aligned.
   It also puts the board BELOW everything, which is what lets the knight sit on it and the
   content sit in front of the knight — see the layer note by .band--plain. */
body {
  margin:0;
  background-color:var(--cb-white);
  background-image:var(--chequer);
  background-size:var(--chequer-size) var(--chequer-size);
  color:var(--cb-black);
  font-family:var(--sans);
  font-weight:400;
  font-size:18px;
  line-height:1.6;
}

img,svg { max-width:100%; height:auto; }
a { color:var(--cb-red); text-decoration:underline; text-underline-offset:2px; }
a:hover { color:var(--cb-red-dark); }

/* One loud focus ring. Many of these readers are on keyboards and older screens. */
:focus-visible { outline:3px solid var(--cb-red); outline-offset:2px; border-radius:2px; }
.on-dark :focus-visible { outline-color:var(--cb-white); }
/* Headings the script focuses programmatically. Not in the tab order, so suppressing the ring
   cannot strand anyone — and a red ring round a thank-you reads as an error. */
[tabindex="-1"]:focus { outline:none; }

.cb-skip { position:absolute; left:-9999px; top:0; z-index:200; background:var(--cb-white); color:var(--cb-navy); padding:var(--sp-sm) var(--sp-md); font-weight:500; }
.cb-skip:focus { left:var(--sp-md); top:var(--sp-md); }
.visually-hidden { position:absolute !important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }

/* -------------------------------------------------------------- layout ----- */
.wrap { width:100%; max-width:var(--maxw); margin:0 auto; padding:0 var(--sp-lg); }
/* A NARROWER GUTTER ON A PHONE, and it is a hit-target fix as much as a typographic one.
   The report's board breaks out of its card to run edge to edge, so the board is exactly this
   wide: at 390px a 24px gutter leaves 342, and 342/8 = 42.75px squares — under the 44px floor
   every other control on this page is held to. 16px leaves 358, and 358/8 = 44.75.
   It went unnoticed for as long as it did because check-page.js asked Chrome for a 390px window,
   Chrome quietly gave it 500, and the gate printed "390px" over a measurement of 500. */
@media (max-width:430px) { .wrap { padding:0 var(--sp-md); } }
.wrap--narrow { max-width:860px; }

section { padding:calc(var(--sp-3xl) / 2) 0; }
/* Emptied kickers, ledes and notes must not hold their line box open. */
.kicker:empty,.lede:empty,.small:empty { display:none; margin:0; }
#pakete .packs { margin-top:calc(var(--sp-2xl) / 2); }
@media (max-width:760px) { section { padding:calc(var(--sp-2xl) / 2) 0; } }

/* Bands carry their texture on a ::before layer rather than a stacked background-image, so the
   grain can multiply against the band colour without tinting the content above it. */
.band--paper,
.band--plain,
.band--tint,
.band--poster,
.band--ink { position:relative; isolation:isolate; }
.band--paper > *,
.band--plain > *,
.band--tint > *,
.band--poster > *,
.band--ink > * { position:relative; z-index:1; }
.band--paper::before,
.band--plain::before,
.band--tint::before,
.band--poster::before,
.band--ink::before {
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
}

/* THE THREE LAYERS, and the order is the whole idea.

     0  the board and the paper tooth, on the body — one continuous chequer down the page
     1  the knight, fixed, hopping across that board
     2  the content: a column of opaque paper with the words on it

   So the knight is ON the board and BEHIND the page. It can stand anywhere, including on a square
   the content covers, and there it simply disappears behind the paper — which is what a piece
   under something should look like. It can also cross from one margin to the other in a single
   move, because the two margins are not two boards with a gap between them: they are one board
   with a sheet of paper laid over the middle.

   Every band is therefore TRANSPARENT and paints only the column its words sit in. Before this
   each band painted its own full-width ground and carried its own copy of the chequer, which is
   why the page needed a mask to cut the board out of the content and JavaScript to hold eight
   tilings in step. Both are gone, along with the tint band, the two grain opacities, and the
   vertical fade that bleached every band boundary.

   The band classes stay in the markup: they carry the column, and they are the seam where a
   section could be given a ground of its own again — now a deliberate act rather than the
   default. */
.band--plain,
.band--tint,
.band--paper,
.band--poster,
.band--ink { background:transparent; z-index:2; }

.band--plain::before,
.band--tint::before,
.band--paper::before,
.band--poster::before,
.band--ink::before {
  content:""; position:absolute; top:0; bottom:0; z-index:0; pointer-events:none;
  /* The content column, edge to edge of the wrap. --maxw IS the column, so this tracks the layout
     rather than restating it.
     Clamped at zero, because below --maxw the inset goes NEGATIVE and the column hangs off both
     edges of the viewport — 340px of horizontal scroll on a phone, on every band at once, with no
     element whose rect was past the edge to point at. Narrower than the column there are no
     margins to leave: the paper is the full width, which is what max(0px, …) says. */
  left:max(0px, calc(50% - var(--maxw) / 2));
  right:max(0px, calc(50% - var(--maxw) / 2));
  background:var(--cb-white);
}
/* The navy band paints its column navy. Its margins stay transparent like every other band's, so
   the board runs unbroken past it and the knight does not vanish for a whole section — which is
   what happened when this one band was left opaque across the full width. */
.band--ink::before { background:var(--cb-navy); }

/* Paper tooth on the paper. The column is the printed sheet, so the grain belongs on it and not
   on the margins — the margins are a board, and a board is not paper.
   This sits in the ::after the per-band chequer used to occupy. One opacity for every band: two
   different ones (.28 and .34) used to be a visible difference in ground between the hero and the
   rest. Multiply can only darken, so dark type on it keeps or gains contrast. */
.band--plain::after,
.band--tint::after,
.band--paper::after,
.band--poster::after {
  content:""; position:absolute; top:0; bottom:0; z-index:0; pointer-events:none;
  left:max(0px, calc(50% - var(--maxw) / 2));
  right:max(0px, calc(50% - var(--maxw) / 2));
  background-image:var(--grain); opacity:.28; mix-blend-mode:multiply;
}

/* The hero ground used to BE the poster's own cream (--cb-poster), so the artwork's cream
   rectangle dissolved into the page and the pawn appeared to stand on the page itself. That was
   the better trick in the abstract, and it is gone on purpose: the ground it needed is opaque
   cream edge to edge, which meant the hero — the one screen every visitor sees — was the only
   part of the page with no chessboard under it.
   So the hero is white now and carries the squares like every other light band, and the poster
   goes back to being what it is: a printed sheet lying on the board, with the pawn breaking out
   of its own frame. --cb-poster is still the artwork's ground colour and is still used for the
   hairlines that have to read against it.
   The grain still sits ABOVE the content here (z-index 2), the opposite of every other band. It
   was originally a fix for a cream-on-cream mismatch that no longer exists, but it is worth
   keeping for its own sake: one sheet of paper tooth running across band and artwork alike is
   what a printed poster actually looks like, and it stops the artwork reading as pasted on.
   Multiply can only darken, so dark type on light ground keeps or gains contrast; the page check
   verifies that at every breakpoint rather than taking my word for it. */

.band--ink { color:#DDE2E8; }   /* its ground is the column, painted by ::before above */
/* The notation is a WALL, not a paragraph. At full strength it competed with the lede for the
   same reading attention and won, which is the opposite of what a background is for; the body
   copy sat inside a field of move numbers and became unreadable. Held down until it is legible
   only if you go looking. */
/* THE TEXTURE'S OPACITY MUST NOT LAND ON THE GROUND.
   This was one rule: it added the notation image to .band--ink::before and set opacity:.32 — but
   ::before is also what paints the navy, so the whole layer faded and the band rendered at
   rgb(164,171,179), a pale grey. Its headings are white and its lede #D6DDE7, both chosen for a
   dark ground, so the section carrying the four trust pillars was white on light grey.
   The contrast gate missed it: it reads the ::before background COLOUR, which is a correct navy,
   not the opacity composited over it.
   The texture gets its own layer. .band--ink had no ::after — the grain block above lists only
   plain/tint/paper/poster — so this collides with nothing. */
.band--ink::after {
  content:""; position:absolute; top:0; bottom:0; z-index:0; pointer-events:none;
  left:max(0px, calc(50% - var(--maxw) / 2));
  right:max(0px, calc(50% - var(--maxw) / 2));
  background-image:var(--notation); opacity:.32;
}
.band--ink h2,.band--ink h3,.band--ink h4 { color:var(--cb-white); }
.band--ink .lede { color:#D6DDE7; }     /* the base .lede is near-black — invisible on navy */
.band--ink .kicker { color:#F2A9A5; }   /* #B70801 on navy is 1.4:1 — unreadable. A tint is not. */

/* ------------------------------------------------------------ typography --- */
.kicker {
  font-family:var(--display); font-weight:400; font-size:15px;
  letter-spacing:.18em; text-transform:uppercase; color:var(--cb-red);
  margin:0 0 var(--sp-md);
}

/* Poster type. This is the single biggest difference from an ordinary content page and the
   thing that makes it read as a ChessBase upgrade page. Tight leading, huge sizes. */
h1,h2,.display {
  font-family:var(--display); font-weight:400;
  /* Anton ships ONE face: no bold, no italic. Without this an inherited font-weight:700 or a
     stray <em> makes the browser smear or shear a synthetic one — which is what made the headline
     read as a solid block. Refuse the fake rather than hunt down every declaration. */
  font-synthesis:none;
  /* .94 was tuned for Trade Gothic. Anton's ascenders and descenders are longer, and at .94 they
     touch the line above: the i-dot of "sichern" met the C of "ChessBase". */
  color:var(--cb-navy); line-height:1.06; letter-spacing:.005em;
  margin:0 0 var(--sp-md); text-wrap:balance;
}
h1 { font-size:clamp(40px,6.6vw,80px); }
/* No italic exists in this family; an <em> here would otherwise be a sheared roman. */
h1 em, h2 em, .feature__name em { font-style:normal; }
h2 { font-size:clamp(32px,4.6vw,60px); }
h3 { font-family:var(--sans); font-weight:700; font-size:21px; line-height:1.25; color:var(--cb-navy); margin:0 0 var(--sp-sm); }
h4 { font-family:var(--sans); font-weight:500; font-size:17px; color:var(--cb-navy); margin:0 0 var(--sp-xs); }

.t-red { color:var(--cb-red); }
.t-ink { color:var(--cb-navy); }

p { margin:0 0 var(--sp-md); max-width:64ch; }
.lede { font-size:20px; line-height:1.55; color:var(--cb-black); max-width:56ch; }
.small { font-size:15px; line-height:1.5; color:var(--cb-grey); }
.small--tight { margin-bottom:0; }
.band--ink .small { color:#AEB8C4; }

/* The highlighter-pen kicker from the live pages. A flat brand-cream block behind the words —
   no gradient, the manual forbids those on brand surfaces. */
/* Skew the block, un-skew the text inside it. A ::before at z-index:-1 disappears behind the
   nearest ancestor that happens to form a stacking context, which is exactly what went wrong
   the first time — this version cannot. */
.marker {
  display:inline-block;
  background:var(--cb-cream-deep);
  transform:skewX(-9deg);
  padding:5px 14px; margin:0 0 var(--sp-lg); border-radius:2px;
}
.marker > span {
  display:inline-block; transform:skewX(9deg);
  font-family:var(--sans); font-weight:700; font-size:clamp(15px,1.6vw,19px);
  color:var(--cb-black); letter-spacing:.01em;
}

.rule { width:88px; height:5px; background:var(--cb-red); border:0; margin:0 0 var(--sp-lg); }

/* --------------------------------------------------------------- header ---- */
/* Sticky, like the live pages: the buy button must never be more than a glance away. */
.site-head {
  position:sticky; top:0; z-index:100;
  background:rgba(255,255,255,.94);
  border-bottom:1px solid var(--cb-hairline);
  height:var(--head-h);
  display:flex; align-items:center;
}
@supports (backdrop-filter:blur(8px)) { .site-head { backdrop-filter:blur(8px); } }
.site-head__inner { display:flex; align-items:center; gap:var(--sp-lg); width:100%; }

.brand { display:flex; align-items:center; gap:var(--sp-sm); text-decoration:none; min-height:44px; flex:none; }
.brand:hover { text-decoration:none; }
.brand__mark { width:36px; height:36px; display:block; flex:none; }
/* Wortmarke: solid black on light. Never red, never multicoloured, no shadow. */
.brand__word { font-family:var(--display); font-weight:400; font-size:27px; color:var(--cb-black); line-height:1; }

.site-nav { display:flex; gap:var(--sp-lg); margin-left:var(--sp-xl); }
/* min-WIDTH as well as min-height. Every label here happened to be long enough until "Try it"
   arrived at 41px, so the rule only ever constrained one axis and nobody noticed. A nav item
   must make a 44px target whatever word ends up in it. */
.site-nav a { font-family:var(--sans); font-weight:500; font-size:16px; color:var(--cb-grey); text-decoration:none; display:inline-flex; align-items:center; justify-content:center; min-height:44px; min-width:44px; }
.site-nav a:hover { color:var(--cb-navy); }
@media (max-width:900px) { .site-nav { display:none; } }

.head-right { margin-left:auto; display:flex; align-items:center; gap:var(--sp-sm); flex:none; }
.langswitch { display:flex; align-items:center; gap:2px; flex:none; }
.langswitch a,.langswitch span {
  font-family:var(--sans); font-weight:700; font-size:15px; letter-spacing:.04em;
  display:inline-flex; align-items:center; justify-content:center;
  /* flex:none matters as much as the min-width. A flex item shrinks below its own min-width by
     default, so these measured 41px as soon as the header got one nav item busier — the hit
     target quietly shrank with no visible change to the layout. */
  flex:none; min-width:44px; min-height:44px; padding:0 8px; border-radius:var(--radius);
  text-decoration:none; color:var(--cb-grey);
}
.langswitch a:hover { color:var(--cb-navy); background:var(--cb-tint); }
.langswitch [aria-current="true"] { color:var(--cb-white); background:var(--cb-red); }
@media (max-width:520px) { .site-head .btn--head { display:none; } }

/* -------------------------------------------------------------- buttons ---- */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-sm);
  font-family:var(--sans); font-weight:700; font-size:18px; line-height:1.1;
  padding:16px 30px; min-height:52px;
  border-radius:var(--radius); border:2px solid transparent;
  text-decoration:none; cursor:pointer;
  transition:background-color .15s ease,color .15s ease,border-color .15s ease,box-shadow .15s ease;
}
.btn:hover { text-decoration:none; }
.btn--primary { background:var(--cb-red); color:var(--cb-white); border-color:var(--cb-red); box-shadow:0 6px 22px rgba(183,8,1,.28); }
.btn--primary:hover,.btn--primary:focus { background:var(--cb-red-dark); border-color:var(--cb-red-dark); color:var(--cb-white); }
.btn--ghost { background:transparent; color:var(--cb-navy); border-color:var(--cb-navy); }
.btn--ghost:hover,.btn--ghost:focus { background:var(--cb-navy); color:var(--cb-white); }
.btn--dark { background:var(--cb-navy); color:var(--cb-white); border-color:var(--cb-navy); }
.btn--dark:hover,.btn--dark:focus { background:var(--cb-navy-dark); border-color:var(--cb-navy-dark); color:var(--cb-white); }
.btn--quiet { background:transparent; color:var(--cb-navy); border-color:transparent; text-decoration:underline; text-underline-offset:3px; padding-left:0; padding-right:0; font-weight:500; }
.btn--quiet:hover { color:var(--cb-red-dark); }
.btn--head { font-size:16px; padding:11px 20px; min-height:44px; }
.btn--big { font-size:21px; padding:20px 44px; min-height:64px; }
.btn[disabled],.btn[aria-disabled="true"] { background:var(--cb-line); border-color:var(--cb-line); color:#3C464C; cursor:default; box-shadow:none; }
.btn-row { display:flex; flex-wrap:wrap; gap:var(--sp-md); align-items:center; }

/* The hero's explanatory aside. It had a PRINT rule and a comment describing its hairline, and no
   screen rule at all — so the paragraph explaining why the reader is looking at this page rendered
   as plain body copy directly under the headline, and the hero read as two competing messages
   rather than one message with a footnote.
   An old comment refers to a --cb-poster-line hairline for this box on the hero's cream. That
   variable was never defined, and inventing a value for it put an off-palette colour on the page —
   caught immediately by check-ci. The ordinary hairline is used instead: fainter on cream than a
   bespoke one would be, and the CI palette is not mine to extend. */
.why {
  margin-top:var(--sp-xl); max-width:56ch;
  border:1px solid var(--cb-hairline); border-radius:var(--radius-lg);
  padding:var(--sp-lg);
}

/* ------------------------------------------------------------- why bar ---- */
/* The explanation of why this page opened, as a bar along the foot of the window.

   It sits above the page rather than in it, so the offer owns the first screen — but it stays
   on screen while the reader scrolls, because the question it answers ("why am I being shown
   this?") can occur at any point, not only at the top.

   The page gets bottom padding equal to the bar's height, set from script because the height
   depends on how the text wraps. Without it the bar covers the real footer, which is where the
   imprint and the privacy line live. */
.whybar {
  /* A full-bleed strip along the foot of the window, kept as shallow as the words allow. */
  position:fixed; left:0; right:0; bottom:0; z-index:70;
  background:var(--cb-white); color:var(--cb-navy);
  /* White needs an edge; the red had its own. A hairline plus a soft lift is what separates the
     strip from whatever it happens to be sitting over. */
  border-top:1px solid var(--cb-hairline);
  box-shadow:0 -8px 26px rgba(18,35,58,.12);
}
.whybar[hidden] { display:none; }
.whybar__in {
  display:flex; align-items:center; gap:var(--sp-md);
  /* NOT the content column. .wrap caps at --maxw, which on a wide screen leaves the strip's own
     width unused and forces the text into more lines than it needs — the opposite of shallow.
     Full width with gutters instead: the wider the window, the fewer lines, the shorter the bar. */
  max-width:none; padding:10px clamp(16px, 3vw, 40px);
}

/* Set smaller and tighter than body copy. At page size this ran to four lines and took 199px of an
   860px window — a footnote occupying a quarter of the screen, sitting over the CTA. The floor is
   the page's own 12px minimum, and 15px clears it with room. */
/* The two paragraphs run as ONE flowing block rather than stacked, which is what keeps this to a
   couple of lines: stacked, each was a paragraph of its own and the bar was five lines deep. */
.whybar__text { flex:1 1 auto; font-size:14.5px; line-height:1.4; }
.whybar__text > p { margin:0; display:inline; }
.whybar__text > p + p::before { content:" "; }
.whybar__text .small { font-size:14.5px; }

/* On white the page's own ink applies again: navy for the emphasis, brand red for the link. The
   underline stays, so the link is identifiable without relying on colour alone. */
.whybar strong { color:var(--cb-navy); }
.whybar a {
  color:var(--cb-red); text-decoration:underline;
  text-underline-offset:2px; text-decoration-thickness:1px;
}
.whybar a:hover { text-decoration-thickness:2px; }

/* 44px, because that is the smallest thing a finger reliably hits and check-page enforces it.
   The glyph is decorative — the button's name comes from aria-label, so a screen reader says
   "dismiss this note" rather than "times". */
.whybar__close {
  flex:none; width:44px; height:44px; padding:0;
  display:flex; align-items:center; justify-content:center;
  font:400 26px/1 var(--sans); color:var(--cb-grey);
  background:transparent; border:1px solid var(--cb-hairline); border-radius:var(--radius);
  cursor:pointer; transition:color .15s ease, border-color .15s ease;
}
.whybar__close:hover { color:var(--cb-navy); border-color:var(--cb-grey); }
.whybar__close:focus-visible { outline:3px solid var(--cb-cyan); outline-offset:2px; }

@media (max-width:700px) {
  .whybar__in { gap:var(--sp-sm); padding:8px var(--sp-md); align-items:flex-start; }
  .whybar__text { font-size:13.5px; }
  .whybar__text .small { font-size:13.5px; }
}



/* ----------------------------------------------------------------- hero ---- */
/* The hero is the campaign poster at full scale, not a product photo in a corner. The band is
   the poster's own cream; the artwork bleeds off the bottom of the band so it reads as a
   printed thing the page is standing on rather than an image in a box. */
/* overflow-x, not overflow — and the bottom padding belongs to the SECTION, not one column.
   The old poster bled deliberately off the band's bottom edge, and `overflow:hidden` is what made
   that read as a bleed rather than as a tall picture. The poster was replaced by a box render; the
   clip outlived its reason and started cutting the box's drop shadow instead — a hard horizontal
   line straight across it, because the art column's foot was also the section's foot.
   Clipping sideways still guards against horizontal overflow; clipping downwards has nothing left
   to clip except the shadow that is the whole point of using a box. */
.hero {
  /* Fluid, not the fixed --sp-3xl: 80px above and below is right on a desktop and is 160px of
     nothing on a 13" laptop, where it is the difference between the hero fitting the screen and
     spilling off it. The floor keeps the headline off the header on a phone. */
  padding:clamp(40px, 7vh, var(--sp-3xl)) 0; overflow-x:clip;
  /* 3, where every other band is 2. The box shadow is 38px down with a 60px blur, so it
     reaches about 98px below the box. On a wide screen the hero is 100dvh and there is
     room for all of it; where the hero is only as tall as its content - the stacked layout
     under 761px, and any short window - the shadow runs past the foot of the band. Bands
     all share z-index 2, and at equal z-index the LATER element wins, so .band--plain
     below painted its own opaque column straight over the shadow and cut it off in a hard
     line across the page. Nothing needs to paint over the hero: it is the first band, and
     the header (100), the why bar (70) and the lightbox (300) are all above it anyway.
     Still above the knight at 1, which is the number the layer note cares about. */
  z-index:3;
  /* FULL SCREEN, minus the header that sits above it — otherwise "100vh" is a screen and a bit,
     and the first thing a reader does is scroll past a sliver of the next section.
     dvh after vh: on a phone, vh is the tallest the viewport ever gets, so a 100vh hero hides
     behind the browser's own chrome until the URL bar retracts. dvh tracks the real height.
     Older engines ignore the second declaration and keep the first. */
  /* --whybar-h is measured and published by cb-landing.js; 0px until it runs, and 0px again once
     the reader closes the bar. Without it the hero centres its content behind a bar that is six
     lines tall on a phone. */
  min-height:calc(100vh - var(--head-h) - var(--whybar-h, 0px));
  min-height:calc(100dvh - var(--head-h) - var(--whybar-h, 0px));
  /* min-height, not height: at 600px in landscape the content is taller than the screen, and a
     fixed height would crop the buttons rather than let the section grow. */
  display:flex; align-items:center;
}
.hero > .wrap { width:100%; }
.hero__grid {
  display:grid; gap:var(--sp-2xl);
  grid-template-columns:1.06fr .94fr;
  /* Start, not end: the copy column carries the lede, two buttons and the aside, so bottom-aligning
     the art parked the box level with the foot of all that — a long way below the headline it is
     supposed to sit beside. */
  align-items:start;
}
/* The bottom space is the section's now, so both columns end level. */
.hero h1 { max-width:17ch; }
.hero .lede { margin-top:var(--sp-lg); color:var(--cb-navy); }

.hero__art { position:relative; display:flex; justify-content:center; align-items:flex-start;
             padding-top:var(--sp-md); }

/* The copy sits lower than the box it stands beside. Both columns start at the grid's top edge,
   which lined the kicker up with the very top of the seal and made the headline feel like it was
   hanging off the header. Dropping the copy 48px puts the headline against the middle of the box
   instead, and the box keeps the raised position it was given earlier — the two moved apart, not
   together.
   Only where there are two columns: stacked on a phone this would just be a gap under the
   header. */
@media (min-width:761px) {
  /* A share of the screen with a ceiling, not a flat 48px: on a short laptop those 48px come
     straight off the bottom of the hero, where the second button already sits close to the
     sticky bar. */
  .hero__copy { padding-top:min(var(--sp-2xl), 6vh); }
}

/* THE BOX, ANGLED AND LIFTED — the CB14 campaign's hero object.
   Its numbers are that campaign's, read from J:\Work\CB14-Upgrade: a box at min(82%,400px),
   turned 1.5deg, on a long soft shadow. The art is the same key art as the poster this replaced,
   rendered as a physical box rather than printed flat.
   The poster bled off the bottom of a cream band and carried no shadow, because a shadow on a
   knocked-out surround traces the artwork's own rectangle and hands back the picture-box the
   bleed existed to remove. A box render has an actual edge, so a shadow is describing something
   that is really there. */
/* THE SHADOW IS ON THE IMAGE, NOT ON THE WRAPPER — and that is about sharpness, not about which
   element it hangs off.
   `filter` makes the browser draw an element and everything inside it into a separate picture,
   apply the effect, and then place the result. When the same element also carries a rotation, that
   finished picture gets turned, which means resampling: every edge inside it is smeared across an
   extra pixel. The seal sits inside this wrapper, so the wrapper's shadow was quietly softening
   the seal's type — the shadow belongs to the box, and the seal was paying for it.
   With the shadow on the img, the wrapper is a plain rotation again, the seal is drawn straight
   at full resolution, and the box keeps exactly the shadow it had. Measured at a 2x device pixel
   ratio, the seal's edges went from 2.97px wide to 2.16px — which is the floor for unfiltered
   content at that scale. scratchpad/hero-sharp.js is the harness. */
.hero__box {
  position:relative; z-index:1;
  width:min(82%, 400px);
  transform:rotate(1.5deg);
}
/* AND A HEIGHT CAP, because the two rules above only ever bound the WIDTH. The render is
   895x1400, so the box is two and a half times taller than it is wide, and on a 768px or
   720px laptop it ran straight off the bottom of the screen - 90px past it at 1366x768,
   134px at 1280x720. Worse with the why bar open, which is exactly when a reader meets
   this page: the bar is 85px of the screen and it is the reason the page was opened.
   The divisor is the box's VISUAL height per unit width: 1400/895 for the render, plus
   the 1.5deg rotation, which trades a little width for a little height. 1.6 rounds that
   up, so the cap errs on the side of fitting.
   Only where the two columns exist. Stacked under 761px the hero is taller than the
   screen by design and the reader scrolls to the box, so a screen-height cap there would
   shrink it for no reason.
   vh first, dvh second: same pair, and for the same reason, as the hero's own min-height. */
@media (min-width:761px) {
  .hero__box {
    max-width:calc((100vh  - var(--head-h) - var(--whybar-h, 0px)
                    - clamp(40px, 7vh, var(--sp-3xl)) - var(--sp-md) - 24px) / 1.6);
    max-width:calc((100dvh - var(--head-h) - var(--whybar-h, 0px)
                    - clamp(40px, 7vh, var(--sp-3xl)) - var(--sp-md) - 24px) / 1.6);
  }
}
.hero__boxshot {
  display:block; width:100%; height:auto;
  filter:drop-shadow(0 38px 60px rgba(20,15,10,.34));
}

/* The savings starburst. Drawn here rather than copied from the campaign as a .webp: the shape is
   a polygon and a ring, and drawing it means it takes the page's own --cb-cyan instead of whatever
   cyan was baked into an image three campaigns ago. One fewer binary, and it cannot drift
   off-palette — which check-ci would catch, but only after the fact. */
.savesticker {
  position:absolute; z-index:3; left:-30px; top:-34px;
  width:170px; height:170px; margin:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; color:var(--cb-white);
  transform:rotate(-8deg);
  /* No drop-shadow here either, for the same reason: it cost the seal's own lettering the last
     0.5px of sharpness, and the shadow it bought was a faint smudge under a disc that already
     separates from the page by being the only saturated cyan circle on it. The white ring does
     the lifting. */
  background:var(--cb-cyan);
  /* A 24-point star, and the ring is an inset white outline rather than a second element. */
  clip-path:polygon(
    100% 50%, 97.9% 56.4%, 98.3% 62.9%, 94.5% 68.2%, 93% 74.5%, 88% 78.6%, 85% 84.4%,
    79% 86.9%, 74.5% 91.6%, 68.2% 92.4%, 62.9% 95.8%, 56.4% 94.8%, 50% 97%, 43.6% 94.8%,
    37.1% 95.8%, 31.8% 92.4%, 25.5% 91.6%, 21% 86.9%, 15% 84.4%, 12% 78.6%, 7% 74.5%,
    5.5% 68.2%, 1.7% 62.9%, 2.1% 56.4%, 0% 50%, 2.1% 43.6%, 1.7% 37.1%, 5.5% 31.8%,
    7% 25.5%, 12% 21.4%, 15% 15.6%, 21% 13.1%, 25.5% 8.4%, 31.8% 7.6%, 37.1% 4.2%,
    43.6% 5.2%, 50% 3%, 56.4% 5.2%, 62.9% 4.2%, 68.2% 7.6%, 74.5% 8.4%, 79% 13.1%,
    85% 15.6%, 88% 21.4%, 93% 25.5%, 94.5% 31.8%, 98.3% 37.1%, 97.9% 43.6%);
}
.savesticker::before {
  content:""; position:absolute; inset:15px; border-radius:50%;
  border:2px solid var(--cb-white); opacity:.9;
}
/* 19px at weight 800, not 14px — and that is an accessibility floor, not a style choice.
   White on the campaign cyan is 3.41:1. WCAG asks 4.5:1 of small text and 3:1 of large, where
   large means 24px, or 18.66px when bold. At 14px the seal failed; at 19px bold the same colours
   pass, and CB14's white-on-cyan is kept rather than being darkened into something else.
   opacity is gone with it: fading white on cyan lowers the contrast the size just bought. */
.savesticker__s1,
.savesticker__s3 { font-size:19px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; }
.savesticker__s2 { font-family:var(--display); font-size:44px; line-height:.85; margin-top:1px; }
.savesticker__s3 { margin-top:2px; }
/* The third line is empty in the priced state and only fills in for the no-figure fallback, where
   it carries the second half of "Exklusives Angebot". Empty, it still held a 19px line box plus
   its margin open inside the disc, which pushed the two lines that do say something off centre and
   crowded them against the ring. Same reason as the :empty rules on .kicker and .lede — an element
   with nothing in it must not occupy space. Not display:none on the class itself: the fallback
   needs the span back the moment the script puts wording in it. */
.savesticker__s3:empty { display:none; }

/* Without a figure the seal is two words and no display line, so the type has to carry the space
   the number used to. Same seal, same place — it just says less. */
.savesticker--nofigure .savesticker__s1,
.savesticker--nofigure .savesticker__s3 {
  font-family:var(--display); font-weight:400; letter-spacing:.01em;
  font-size:25px; line-height:1.04; text-transform:none; opacity:1;
}
.savesticker--nofigure .savesticker__s3 { margin-top:0; }

/* ONE COLUMN ON A PHONE. The hero grid was the only two-column block on the page with no narrow
   rule, which was survivable while the hero was short and is not now that it claims a whole
   screen: at 390px the 1.06fr/.94fr split gave the art 180px, the box overflowed it sideways and
   the copy set at four words to a line, so the buttons fell off the first screen entirely.
   760px, not 900px: 900px still has room for two columns — it is where the box steps down a size.
   The break is where the copy column drops under about 320px, which is where the headline starts
   breaking mid-phrase. */
@media (max-width:760px) {
  .hero__grid { grid-template-columns:1fr; gap:var(--sp-xl); }
  /* The box is decoration under a phone headline, not a second column — hold it to a size that
     leaves the buttons on screen, and centre it under the copy it belongs to. */
  .hero__art { justify-content:center; padding-top:0; }
  .hero__box { width:min(78%, 280px); }
  /* The seal hangs off the box's top-left corner. Centred under the copy, that corner is close to
     the screen edge, so the overhang comes in and the disc comes down with it. */
  .savesticker { width:124px; height:124px; left:-6px; top:-14px; }
  .savesticker__s1,
  .savesticker__s3 { font-size:19px; }
  .savesticker__s2 { font-size:30px; }
  /* A phone screen is one column wide, so the headline has no neighbour to keep short for. */
  .hero h1 { max-width:none; }
}

@media (max-width:900px) {
  .hero__box { width:min(74%, 320px); }
  /* The seal cannot shrink below what its own type needs: 19px is the size that makes white on
     cyan legal, so the disc is sized around it rather than the other way round. The earlier 126px
     seal drove the wording down to 11px, under the page's own 12px floor as well as WCAG's. */
  .savesticker { width:152px; height:152px; left:-18px; top:-22px; }
  .savesticker__s2 { font-size:36px; }
}

/* Three rules used to live here and are gone with the cream ground, recorded so nobody puts them
   back by reflex when the hero looks slightly different from what they remember:
     · .why got a --cb-poster-line hairline, because the white-page hairline vanished on cream.
       On white the white-page hairline is correct again, and the beige one would look like a
       stray.
     · .marker was forced to white, because the highlighter's own --cb-cream-deep swipe was
       invisible against cream. It reads on white, so it goes back to being a highlighter.
     · --cb-grey was redefined darker for the band: Grau mittel measures 4.00:1 on #E7E6D4 and
       failed AA there while clearing it on white. On white the token needs no help.
   All three were compensation for the ground, not decisions about the hero. check-page.js is what
   proves the last one — it measures contrast at every breakpoint rather than trusting this note. */

/* -------------------------------------------------------------- spacing ---- */
/* One-off top margins that used to be written as style="" on the element. Same reason as
   the timeline above: under this page's CSP an inline style attribute never applies, so
   the spacing was silently dropped. Values are the originals, unchanged. */
.mt-16 { margin-top:16px; }
.mt-20 { margin-top:20px; }
.mt-28 { margin-top:28px; }
.mt-32 { margin-top:32px; }

/* ------------------------------------------------------------- timeline ---- */
/* "You skipped a few moves." Dots on a red rule, one per generation, sitting directly above
   the comparison table so the columns line up by eye. */
.timeline { margin:var(--sp-2xl) 0 0; }
/* The rule sits on .tl-track, NOT inside .tl-steps — as a grid child it would silently take a
   column and pull every dot out of line with its table heading below. */
/* Inset by the compare card's border + padding so the track shares the table's content box. */
.tl-track { position:relative; padding:0 25px; }
/* Left edge sits 0.5% before the CB17 dot (37.5%) — the same gap the right edge leaves after
   the CB26 dot (87.5%), so the rule is symmetric about the outer dots. */
.tl-line { position:absolute; left:37%; right:12%; top:31px; height:3px; background:var(--cb-line); }
/* grid-template-columns lives HERE and not in a style="" attribute on the div. This site
   sends style-src 'self' with no 'unsafe-inline', so an inline style attribute is blocked
   and its declaration is lost - the three steps then fall into one column and stack down
   the page instead of standing on the rule. Three generations, three columns; if a fourth
   is ever added, change the count here. */
/* Columns mirror .compare exactly (name column, CB17, CB18, CB26) so each generation stands
   over its own table column. The first step skips the name column. */
.tl-steps { display:grid; grid-template-columns:repeat(4,25%); position:relative; }
.tl-steps > .tl-step:first-child { grid-column:2; }
.tl-step { text-align:center; position:relative; padding-top:0; }
.tl-flag { font-family:var(--sans); font-weight:700; font-size:12px; letter-spacing:.1em; text-transform:uppercase; color:var(--cb-grey); min-height:16px; display:block; margin-bottom:6px; }
.tl-step--here .tl-flag { color:var(--cb-navy); }
.tl-step--new  .tl-flag { color:var(--cb-red); }
/* display:block is load-bearing — a <span> is inline, and width on an inline box is ignored,
   which renders every dot as an 8px sliver. */
.tl-dot { display:block; width:18px; height:18px; border-radius:50%; border:3px solid var(--cb-line); background:var(--cb-white); margin:0 auto; position:relative; z-index:1; }
.tl-step--past .tl-dot { border-color:var(--cb-red); }
.tl-step--here .tl-dot { background:var(--cb-navy); border-color:var(--cb-navy); }
/* Cyan marks what is NEW — the campaign accent doing a job only it does. Red stays reserved
   for things you click, so the two never compete for the same meaning. */
.tl-step--new  .tl-dot { background:var(--cb-cyan); border-color:var(--cb-cyan); box-shadow:0 0 0 5px rgba(0,148,209,.18); }
.tl-name { font-family:var(--sans); font-weight:700; font-size:15px; color:var(--cb-navy); margin-top:10px; display:block; }
.tl-step--new .tl-name { color:var(--cb-cyan-deep); }
.tl-year { font-size:14px; color:var(--cb-grey); display:block; }
@media (max-width:640px) { .tl-name { font-size:13px; } .tl-flag { font-size:12px; letter-spacing:.06em; } }
/* BELOW 640px THE TIMELINE STOPS BEING AN AXIS FOR THE TABLE, because it cannot be one. The
   four-column grid above lines the three dots up with the three generation columns, and measured
   against the real table it holds to under 2px from 690px up. It cannot hold further down, for two
   reasons that are not CSS's to fix: table-layout is auto, so once the row labels no longer fit in
   25% the browser widens that column past the hint and the columns walk right (-11px at 600px,
   -30px at 500px); and from about 640px down the table is wider than .compare-scroll and scrolls
   inside it, so the reader can move the columns under a fixed timeline at will. Measured at 390px
   the dots ended up 66-95px from the columns they name — an alignment claim that is simply false.
   So below here the timeline is what it is on its own: three generations, evenly spaced, in order.
   That says something true at every width, and the dots stay over their own names. The rule spans
   the outer two dots, which sit at one sixth and five sixths of the track. */
@media (max-width:640px) {
  .tl-steps { grid-template-columns:repeat(3, 1fr); }
  .tl-steps > .tl-step:first-child { grid-column:auto; }
  .tl-line { left:16.7%; right:16.7%; }
}

/* ------------------------------------------------------------- compare ----- */
/* table-layout STAYS auto, and there is a wrong turn recorded here so nobody takes it twice.
   `fixed` looks like the way to make the column percentages below exact — the timeline above draws
   its dots on a grid cut to the same percentages, so a column the browser may widen is a dot that
   drifts. It does not work, for a reason that is easy to miss: under fixed layout ONLY the cells of
   the table's first row establish the columns. That row is the <thead> row, whose first cell is
   `th[scope="col"]`, so `.compare th[scope="row"]` — which is where both the 25% and the narrow
   44% live — never reaches a width-setting cell at all. Column one simply takes the 25% left over
   after the three thead rules, at every width, and the 44% narrow row header becomes dead CSS.
   Measured cost of switching it on: identical layout at 1100px, so nothing gained at desktop
   width; and at 390px the row labels overflow their 81px cells into the ChessBase 17 column while
   .compare-scroll's horizontal scroll disappears, because the table no longer exceeds the scroller
   it was there to scroll. Under `auto` the four columns land within 2px of the percentages anyway,
   which is all the dots need. */
.compare { width:100%; border-collapse:collapse; margin-top:var(--sp-lg); font-size:16px; }
.compare th,.compare td { padding:11px 8px; border-bottom:1px solid var(--cb-hairline); }
.compare thead th { font-family:var(--sans); font-weight:700; font-size:14px; color:var(--cb-navy); text-align:center; border-bottom:2px solid var(--cb-line); }
.compare th[scope="row"] { text-align:right; font-family:var(--sans); font-weight:500; color:var(--cb-black); width:25%; }
.compare thead th:nth-child(2),.compare thead th:nth-child(3),.compare thead th.col-new { width:25%; }
.compare td { text-align:center; color:var(--cb-grey); }
.compare .yes { color:var(--cb-cyan-deep); font-size:19px; line-height:1; }
.compare .no  { color:var(--cb-grey); }   /* Grau hell is a BORDER colour — never text (manual p.03) */
/* The column the page is selling, marked by a red frame rather than a fill — a filled column
   would put brand red on half the table, and red is reserved for action. */
.compare .col-new { border-left:2px solid var(--cb-cyan); border-right:2px solid var(--cb-cyan); background:rgba(0,148,209,.045); }
.compare tbody tr:last-child .col-new { border-bottom:2px solid var(--cb-cyan); }
.compare thead .col-new { border-top:2px solid var(--cb-cyan); color:var(--cb-cyan-deep); }
/* Paper under the table, for the same reason as .report and by the same means. A chessboard is a
   grid at one pitch and this table is a grid at another; the two beat against each other and the
   ✓/– column becomes work to read. Everywhere else the squares are welcome to show through — a
   dense data table is exactly what is not.
   This used to be a bare white fill with a box-shadow bleed, which looked fine and was a second
   way of doing the one thing .report also does. One mechanism, so the two read as the same kind
   of object and neither drifts. */
.compare-scroll {
  overflow-x:auto;
  background:var(--cb-white); border:1px solid var(--cb-hairline);
  border-radius:var(--radius-lg); padding:var(--sp-lg);
  margin-top:var(--sp-lg);
}
.compare-scroll .compare { margin-top:0; }
@media (max-width:700px) { .compare-scroll { padding:var(--sp-md); } }
/* THE ROW HEADER STAYS AT 25% AT EVERY WIDTH. It used to widen to 44% here, from before the
   timeline above was aligned to these columns at all — and under table-layout:auto that override is
   honoured, so it moved the three data columns right while .tl-steps kept its four equal columns:
   measured at 690px, the CB17 dot sat 72px left of the CB17 column, and 49px at 600px. The two
   rules cannot both stand. 25% is the one that keeps them agreeing, and it is the cheaper side to
   give up: the row labels wrap a line or two more, inside a .compare-scroll that exists to scroll
   this table sideways and does so from about 640px down. Matching the timeline to a 44% column
   instead was tried and is worse — it leaves each generation column 61px while the unbreakable word
   "ChessBase" needs 74px at the 13px this label is already reduced to, so the three names overlap
   each other, and the page's 12px floor leaves no room to shrink them further. */
@media (max-width:700px) { .compare { font-size:14px; } }
/* THE TIMELINE DOES *NOT* GET ITS OWN NARROW GRID, and this is the second half of the wrong turn
   recorded by .compare above. A 44% row header below 700px would leave the three generations
   18.6667% each, and a matching `.tl-steps { grid-template-columns:44% repeat(3, 18.6667%) }` plus
   `.tl-line { left:52.8%; right:8.8% }` looks like the alignment fix that goes with it. Both were
   here and both are gone, because the 44% they are tuned to never materialises — see above — so
   they moved the dots RIGHT of the columns they name: measured at 690px, dots at 365/479/593
   against column centres 269/420/572, the CB17 dot nearly a full column adrift. The plain
   repeat(4,25%) the stylesheet already carries measures 269/421/574 against those same centres,
   inside 2px, at every width. It was right all along.
   And 18.6667% is too narrow for the labels regardless: at 390px each generation column is 61px
   while the unbreakable word "ChessBase" needs 74px at bold 13px, so the three names overlapped
   each other by 13px. Four equal columns give them 81px, which fits. */
@media (max-width:700px) { .tl-track { padding:0 16px; } }

/* ------------------------------------------------------------- features ---- */
/* Oversized feature name in red, description beside it, screenshot card. Alternating sides,
   exactly the rhythm of the live pages. */
.feature { display:grid; grid-template-columns:1fr 1fr; gap:var(--sp-2xl); align-items:center; padding:var(--sp-2xl) 0; }
.feature + .feature { border-top:1px solid var(--cb-hairline); }
.feature__name { font-family:var(--display); font-weight:400; color:var(--cb-red); font-size:clamp(34px,4.6vw,60px); line-height:1.06; margin:0 0 var(--sp-md); max-width:none; }
/* :not() is load-bearing — .feature__body p outranks .feature__name on specificity and would
   otherwise quietly set the poster headline in 19px body copy. */
.feature__body p:not(.feature__name):not(.feature__pull) { font-size:19px; }
/* One line under each feature, carrying the argument the screenshot cannot. Typographic, not
   decorated: an accent rail on a card is the single most over-used device in generated design,
   and the display face at this size does the emphasis better than a red bar ever did. */
.feature__pull {
  font-family:var(--display); font-weight:400;
  font-size:clamp(21px,2.1vw,27px); line-height:1.1; letter-spacing:.005em;
  color:var(--cb-navy); margin:var(--sp-lg) 0 0; max-width:26ch;
}
.feature__pull:empty { display:none; }
.feature__art { border-radius:var(--radius-lg); overflow:hidden; box-shadow:var(--shadow-m); background:var(--cb-white); border:1px solid var(--cb-hairline); }
.feature__art img { display:block; width:100%; }
.feature--flip .feature__body { order:2; }
@media (max-width:860px) {
  .feature { grid-template-columns:1fr; gap:var(--sp-lg); }
  .feature--flip .feature__body { order:0; }
}

/* ---------------------------------------------------------------- cards ---- */
.grid { display:grid; gap:var(--sp-lg); }
.grid--2 { grid-template-columns:1fr; }
.grid--3 { grid-template-columns:1fr; }
.grid--4 { grid-template-columns:1fr; }
@media (min-width:620px)  { .grid--2,.grid--3,.grid--4 { grid-template-columns:repeat(2,1fr); } }
@media (min-width:1000px) { .grid--3 { grid-template-columns:repeat(3,1fr); } .grid--4 { grid-template-columns:repeat(4,1fr); } }

.card { background:var(--cb-white); border:1px solid var(--cb-hairline); border-radius:var(--radius-lg); padding:var(--sp-lg); }
.card p:last-child { margin-bottom:0; }
.card__icon { width:32px; height:32px; display:block; margin-bottom:var(--sp-md); color:var(--cb-red); }
.card__icon svg { width:100%; height:100%; display:block; }
.band--ink .card { background:rgba(255,255,255,.05); border-color:rgba(255,255,255,.14); }
.band--ink .card p { color:#C6CEDA; }

/* ----------------------------------------------------------- data strip ---- */
/* Three real Opening Report outputs on the notation-walled dark band. This section exists for
   rhythm as much as content: the page needed one dense, dark, data-heavy stretch between the
   airy feature rows and the packages, or it reads as one long uniform scroll. */
.datastrip { display:grid; gap:var(--sp-lg); grid-template-columns:1fr; margin-top:var(--sp-xl); }
@media (min-width:900px) { .datastrip { grid-template-columns:repeat(3,1fr); } }
.datacard {
  background:var(--cb-white);
  border-radius:var(--radius-lg);
  overflow:hidden;
  box-shadow:var(--shadow-m);      /* was --shadow-md, undefined: no shadow at all */
  display:flex; flex-direction:column;
}
/* The three screenshots are 1.69, 1.65 and 1.43 wide — close, but not close enough for their
   captions to line up. A shared box with `contain` keeps every chart whole (cropping a chart
   destroys the thing it is evidence of) and puts the three captions on the same baseline. */
.datacard img { display:block; width:100%; aspect-ratio:16/10; object-fit:contain; background:var(--cb-white); }
.datacard__cap {
  padding:var(--sp-md) var(--sp-lg) var(--sp-lg);
  font-size:15px; line-height:1.45; color:var(--cb-grey); margin:0;
}
.datacard__cap b {
  display:block; font-family:var(--sans); font-weight:700; font-size:12px;
  letter-spacing:.09em; text-transform:uppercase; color:var(--cb-cyan-deep); margin-bottom:4px;
}

/* ------------------------------------------------------- opening report ---- */
/* The page's one interactive thing, and the only place the software argues for itself instead
   of being described. Every number in it is ChessBase's own — see cb-report.js.

   Colour carries meaning here and nowhere else on the page: the "played" bar is navy, because
   how often a move gets played is a fact about the crowd; the "scores" bar is campaign cyan,
   because that is the page's rule for what the software found. Red stays out of it entirely —
   nothing in this widget is a thing you buy. */
/* A SHEET UNDER THE WIDGET. The band behind it is a chessboard, and this is the densest thing on
   the page — a board, six rating buttons, a slider and a long table of small figures. Squares
   showing through a data table at a different pitch make every row harder to read than it needs
   to be, and the reader is here to compare numbers.
   Headline and lede stay on the bare board on purpose: display type is not hurt by texture, and
   the sheet starting where the interface starts is what makes it read as an instrument laid on
   the board rather than as a panel that happens to be there. Same white/hairline/radius as .card,
   because the page already has a way of saying "this is one object". */
.report {
  display:grid; gap:var(--sp-xl); grid-template-columns:1fr; margin-top:var(--sp-xl);
  background:var(--cb-white); border:1px solid var(--cb-hairline);
  border-radius:var(--radius-lg); padding:var(--sp-xl);
}
.report-fallback {
  background:var(--cb-white); border:1px solid var(--cb-hairline);
  border-radius:var(--radius-lg); padding:var(--sp-xl); margin-top:var(--sp-xl);
}
@media (max-width:700px) { .report, .report-fallback { padding:var(--sp-lg); } }
/* 372px = 360 for the squares plus the 12px of ChessBase board frame. The number that matters is
   360, not 372: the squares are click targets, 340/8 = 42.5px is under the 44px floor the rest of
   the page is held to and 360/8 = 45px clears it, and check-page.js enforces exactly that.
   This column is a FIXED width, so it — not .oreboard__grid's max-width — is what decides the
   square size above 940px. Adding the frame without widening it took the squares to 43.5px and put
   all 64 targets under the floor, on the four pages at once. The two numbers move together: change
   the frame and change this. */
/* Two columns, and the two charts SHARE ROW 2 so their headings sit on one line.
   The score chart used to be the last child of the panel, which meant its heading started
   wherever the rating strip, the game count and the player list happened to end — 263px below
   the fashion chart's heading at 1400px. The two charts are a matched pair answering the halves
   of one question, and reading them against each other across a 263px step is exactly the
   comparison the pair exists to make easy.
   Levelling them by matching the two blocks' heights was the other option and it is the fragile
   one: the blocks are different widths, so the same lede wraps to two lines in one and one line
   in the other, and it would come apart again in the other language. A shared row is level by
   construction, in both languages, at every width above 940px.
   The panel therefore no longer spans both rows. That costs about 100px of additional empty
   column under the board — the board is roughly half the panel's height, and that gap is
   inherent to the two-column arrangement rather than something this rule introduced.
   align-items:start keeps the board, the panel and both charts at the top of their rows instead
   of being stretched to fill a row some taller neighbour defined. */
/* MAC PAGE, 2026-09-09: the Monte Carlo button that used to sit under the board is gone (the
   Mac version has no Monte Carlo analysis), and both trend charts moved into the space it left —
   stacked under the board in the left column, with the move panel spanning all three rows on
   the right. The CB17 page's "two charts share row 2" arrangement is recorded above; here the
   left column is the position's column and both charts are about the position. */
@media (min-width:940px) {
  .report { grid-template-columns:372px 1fr; gap:0 var(--sp-2xl); align-items:start; }
  .oreboard       { grid-column:1; grid-row:1; }
  .orepanel       { grid-column:2; grid-row:1 / span 3; }
  .oretrend-host  { grid-column:1; grid-row:2; }
  .oretrend-score { grid-column:1; grid-row:3; }
}
/* Until the script runs there is nothing to show, so the static screenshot stands in. Once the
   widget is built it takes over. No JS, no broken empty box. */
/* A grid item will not shrink below its content's minimum width unless it is told it may, and the
   board deliberately has a wide minimum: under 430px it breaks out of the sheet's padding with
   negative margins, so its min-content width is the sheet's inner width plus 48px. Without this,
   that pushed the whole single column past the screen and the PAGE scrolled sideways instead of
   the board running to the edges. Verified at a real 390px viewport: 459px of scroll width, now
   390. (Chrome will not open a window under 500px, so scratchpad/narrow.js measures inside an
   iframe — check-page.js cannot see this class of bug.) */
.report > * { min-width:0; }

.report:not(.is-live) { display:none; }
.report + .report-fallback { display:block; }
.report.is-live + .report-fallback { display:none; }

/* ---- the fashion index ----
   Two line charts under the board and the move list, spanning both columns. See the long note in
   cb-report.js for why there are two of them and why identity is carried by labels rather than by
   colour: the page's approved palette has three hues, and four categorical series cannot be built
   from three without inventing colours the CI check would rightly reject.
   Marks are thin and the chrome recedes — a hairline grid one shade off the surface, solid and
   never dashed, and axis type in the mono face at the page's smallest size. */
/* One column: board, move list, then the charts. DOM order already gives that, so nothing to say
   here — the placement below only applies once there are two columns to place things in. */
.oretrend { border-top:1px solid var(--cb-hairline); margin-top:var(--sp-lg); padding-top:var(--sp-lg); }
.oretrend__h { font-family:var(--sans); font-weight:700; font-size:17px; color:var(--cb-navy); margin:0; }
.oretrend__h2 { font-family:var(--sans); font-weight:700; font-size:15px; color:var(--cb-navy);
                margin:var(--sp-lg) 0 0; }
/* Side by side the two charts are peers, not a heading and its sub-heading, so the score chart's
   h4 takes the other heading's size and drops the top margin that used to separate it from the
   plot above it. Keyed on :first-child rather than on a width, because that IS the condition:
   the h4 only heads its own block when the stylesheet has given the score chart its own column.
   Stacked in one column it is still the fourth thing inside the fashion chart's block, is not
   first-child, and keeps the smaller sub-heading treatment that reads correctly there.
   Without this the two headings sat 24px apart after being given the same grid row, which is the
   whole thing this pair was rearranged to fix. */
.oretrend > .oretrend__h2:first-child { font-size:17px; margin:0; }
.oretrend__lede { font-size:14px; color:var(--cb-grey); margin:6px 0 var(--sp-md); max-width:70ch; }
/* Height is set here and the viewBox does not preserve its aspect ratio, so the plot stretches to
   whatever width it is given while the type inside stays the size it was drawn at. The container
   includes the axis band — sizing it to the plot alone is what gives a card its own tiny
   scrollbar. overflow:visible so the right-hand labels are never clipped. */
.oretrend__fig { margin:0; position:relative; }
.oretrend__svg { display:block; width:100%; height:200px; overflow:visible; touch-action:pan-y; }
.oretrend__fig--score .oretrend__svg { height:150px; }
/* On one column the score chart is drawn INSIDE the fashion chart's block instead, so its own
   host is left empty — and an empty grid item still takes a row gap, which put 32px of nothing at
   the foot of the widget on every phone. :empty is exactly the condition and needs no width query.
   Note there is no whitespace inside the element to defeat :empty; the host is built by script and
   never given a text node. */
.oretrend-score:empty { display:none; }
/* In its own column the score chart is the fashion chart's peer and matches its height, so the
   pair reads as one figure in two halves rather than as a chart and an afterthought. Stacked in
   one column it stays the shorter of the two, which is right there: it follows the other chart
   inside the same block and is subordinate to it. Same :first-child logic as the heading above —
   the host element is the condition, so no width query has to be kept in step with the grid. */
.oretrend-score .oretrend__fig--score .oretrend__svg { height:200px; }

.oretrend__grid { stroke:var(--cb-hairline); stroke-width:1; }
.oretrend__band { fill:var(--cb-tint); }
/* 12px, not the 11px an axis usually wears: this page holds every piece of text to a 12px
   floor and check-page.js enforces it on SVG text too, which is how the first version of
   this chart was caught. The plot padding below is sized for the wider ticks. */
.oretrend__ytick,.oretrend__xtick { font-family:var(--mono); font-size:12px; fill:var(--cb-grey); }
.oretrend__ytick { text-anchor:end; }
.oretrend__xtick { text-anchor:middle; }
/* FOUR SERIES COLOURS, validated rather than chosen by eye. The four were run through the
   colour-blindness check before being written down: worst adjacent pair 13.2 ΔE under deuteranopia
   and 21.0 for normal vision, all four at least 3:1 against white. The set that was tried first —
   cyan, crimson, amber, green — FAILED it at 2.1 ΔE between the amber and the green under
   protanopia. Those two look perfectly distinct to me and are the same line to a red-blind reader,
   which is the entire reason the check exists and is not a matter of taste.
   Slot 1 stays the campaign cyan, so the move a reader is following keeps the colour the page
   already uses for "this is the one".
   Colour follows the MOVE, not its position in the list — see cb-report.js. A rating filter that
   changes which moves qualify must not repaint the ones that survive it. */
.oretrend__line { fill:none; stroke-width:1.8; stroke-linejoin:round; stroke-linecap:round; }
.oretrend__line--s1 { stroke:var(--cb-series-1); }
.oretrend__line--s2 { stroke:var(--cb-series-2); }
.oretrend__line--s3 { stroke:var(--cb-series-3); }
.oretrend__line--s4 { stroke:var(--cb-series-4); }
/* Emphasis is now WEIGHT, not colour — every line has a colour of its own, so the selected one
   cannot be marked by having one. */
.oretrend__line.is-on { stroke-width:3; }
.oretrend__label { font-family:var(--mono); font-size:12px; }
.oretrend__label--s1 { fill:var(--cb-series-1); }
.oretrend__label--s2 { fill:var(--cb-series-2); }
.oretrend__label--s3 { fill:var(--cb-series-3); }
.oretrend__label--s4 { fill:var(--cb-series-4); }
.oretrend__label.is-on { font-weight:700; }
/* The crosshair reads every line at one year at once — which is the question the chart invites.
   The dot gets a surface ring rather than a border, so overlapping series stay separable. */
.oretrend__hair { stroke:var(--cb-line); stroke-width:1; }
.oretrend__dot { stroke:var(--cb-white); stroke-width:2; }
.oretrend__dot--s1 { fill:var(--cb-series-1); }
.oretrend__dot--s2 { fill:var(--cb-series-2); }
.oretrend__dot--s3 { fill:var(--cb-series-3); }
.oretrend__dot--s4 { fill:var(--cb-series-4); }
.oretip {
  position:absolute; top:0; transform:translateX(-50%);
  background:var(--cb-navy); color:var(--cb-white); border-radius:var(--radius);
  font-family:var(--mono); font-size:12px; line-height:1.5; padding:7px 10px;
  pointer-events:none; opacity:0; transition:opacity .12s; white-space:nowrap; z-index:2;
}
.oretip.is-on { opacity:1; }
/* Near the right-hand edge the tip would hang off the card, so it flips to the other side of the
   crosshair instead of being clipped. */
.oretip--flip { transform:translateX(-100%) translateX(-10px); }
.oretip b { display:block; font-weight:700; margin-bottom:3px; }
.oretip span { display:flex; align-items:center; gap:6px; }
.oretip i { width:9px; height:3px; flex:none; border-radius:2px; }
.oretip i.s1 { background:var(--cb-series-1); }
.oretip i.s2 { background:var(--cb-series-2); }
.oretip i.s3 { background:var(--cb-series-3); }
.oretip i.s4 { background:var(--cb-series-4); }
.oretip em { font-style:normal; margin-left:auto; padding-left:12px; }
/* The read-out sentence that used to sit under each chart is gone. It restated every figure the
   chart drew, which is a lot of small type for something the endpoint labels already say — those
   now always carry their value, at every width, so the numbers are still readable without
   hovering anything. */
@media (max-width:700px) {
  .oretrend__svg { height:170px; }
  .oretrend__fig--score .oretrend__svg { height:130px; }
}

/* THE BOARD IS CHESSBASE 26'S BOARD. The frame is the program's own board margin rather than a
   1px hairline, because that dark edge is most of what makes a ChessBase board recognisable at a
   glance — the squares alone read as any chessboard. Square corners for the same reason: the
   program's board is a rectangle in a frame. */
/* 372px, WHICH IS 360 PLUS THE FRAME. box-sizing is border-box for everything on this page, so the
   frame is taken out of the max-width rather than added to it: at 360px the eight squares came out
   at 43.5px and every one of the 64 click targets fell under the 44px floor check-page.js enforces.
   The squares are what the 360 refers to — 360/8 = 45px, which clears it — so the frame is added on
   top. Changing the frame width means changing this number with it. */
.oreboard__grid {
  display:grid; grid-template-columns:repeat(8,1fr); width:100%; max-width:372px;
  border:6px solid var(--cb26-board-margin); background:var(--cb26-board-back); overflow:hidden;
}
/* On a phone the wrap's 24px gutters would squeeze the squares back under 44px, so the board
   breaks out of them and runs edge to edge. Boards are the one element where that reads as
   deliberate rather than broken. */
@media (max-width:430px) {
  .oreboard__grid {
    max-width:none; width:auto; border-left:0; border-right:0;
    margin-left:calc(var(--sp-lg) * -1); margin-right:calc(var(--sp-lg) * -1);
  }
}
.oreboard__grid .sq { aspect-ratio:1; display:flex; align-items:center; justify-content:center; position:relative; }
.sq--l { background:var(--cb26-sq-light); }
.sq--d { background:var(--cb26-sq-dark); }
/* The two squares of the move just played. Cyan again: this is what the report found. */
.sq--path { box-shadow:inset 0 0 0 3px var(--cb-cyan); }
/* A piece that has a move in the report. Deliberately faint — the board must not turn into a
   field of markers, and the reader can also just click a row. */
.sq--can::after { content:""; position:absolute; inset:3px; border-radius:3px; box-shadow:inset 0 0 0 2px rgba(18,35,58,.16); pointer-events:none; }
.sq--sel { box-shadow:inset 0 0 0 3px var(--cb-navy); }
.sq--sel::after { display:none; }
/* Where a selected piece may go. The dot is the universal convention and survives having a
   piece under it, which a border does not. */
.sq__dot { position:absolute; left:50%; top:50%; width:30%; height:30%; transform:translate(-50%,-50%);
           border-radius:50%; background:var(--cb-cyan); opacity:.85; pointer-events:none; }
.sq--to:has(.pc) .sq__dot { width:78%; height:78%; background:none; box-shadow:inset 0 0 0 4px var(--cb-cyan); opacity:.9; }
/* The hit area. A transparent button filling the square: keyboard-reachable and 40px+ at every
   board size, without wrapping the glyph in interactive markup. */
.sq__hit { position:absolute; inset:0; width:100%; height:100%; padding:0; margin:0;
           background:none; border:0; cursor:pointer; }
.sq__hit:focus-visible { outline:3px solid var(--cb-navy); outline-offset:-3px; }

/* ---- dragging ----
   touch-action is set ONLY on squares a piece can move from. Setting it on the whole board would
   stop the page scrolling under a finger anywhere on the board, which is infuriating on a phone;
   this way a drag starts from a movable piece and a scroll starts from anywhere else. */
.sq--can { touch-action:none; cursor:grab; }
.sq--can:active { cursor:grabbing; }
.sq.is-over { box-shadow:inset 0 0 0 4px var(--cb-cyan); }
/* .pc.pc--ghost, not .pc--ghost: `.pc` sets width:80%/height:80% and is declared LATER in this
   file, so at equal specificity it won and the ghost was sized at 80% of its offset parent. The
   ghost is position:fixed on the page root, so that meant 80% of the VIEWPORT — a piece the size
   window following the cursor. Doubling the class beats `.pc` on specificity whatever the order. */
.pc.pc--ghost {
  position:fixed; z-index:60; transform:translate(-50%,-50%); pointer-events:none;
  width:44px; height:44px; filter:drop-shadow(0 4px 8px rgba(18,35,58,.35));
}

.orenav { display:flex; gap:var(--sp-sm); margin:var(--sp-md) 0 0; align-items:center; flex-wrap:wrap; }
.orenav__hint { font-family:var(--mono); font-size:12px; color:var(--cb-grey); white-space:nowrap; }
.orenav__btn {
  font-family:var(--sans); font-weight:500; font-size:14px; padding:0 14px; min-height:44px;
  background:var(--cb-white); color:var(--cb-navy); border:1px solid var(--cb-line);
  border-radius:var(--radius); cursor:pointer;
}
.orenav__btn:hover:not([disabled]) { border-color:var(--cb-navy); }
.orenav__btn[disabled] { color:var(--cb-grey); border-color:var(--cb-hairline); cursor:default; }

/* Where the report has no rating breakdown the switch is disabled rather than left clickable and
   inert. The chosen class still shows — it is remembered and applies again as soon as the data
   has bands — but it must NOT keep the solid fill: a filled chip reads as an applied filter, and
   next to unfiltered numbers that is a straightforward lie. Held, not applied: a dashed outline. */
.oreband.is-off { opacity:.7; }
.oreband.is-off .oreband__btn { background:var(--cb-white); color:var(--cb-grey); border-style:dashed; }
.oreband.is-off .oreband__btn.is-on {
  background:var(--cb-white); color:var(--cb-navy);
  border:2px dashed var(--cb-navy); font-weight:700;
}
.oreband__btn[disabled] { cursor:default; }
/* CHESSBASE'S OWN DIAGRAM PIECES — the Fritz set the program draws on its board, taken from
   Common/Media/Chess/Pieces/DiagramTTFritz100.png and carried inline as one sprite strip.

   RASTER, DELIBERATELY. The first attempt used outlines pulled from Schach.otf, which is a real
   ChessBase font but the plainer set: flat silhouettes, no modelling. These pieces are shaded — a
   cream gradient on White, a charcoal one on Black, a drop shadow under each — and none of that
   survives being traced to a path. The picture IS the asset.

   100px cells for a 36px display size, so they stay sharp on a 2x screen. One strip with twelve
   positions beats twelve separate images for both bytes and requests; the page makes no requests
   at all, so it travels as a data URI like every other image here.
   The board is 360px at most, which puts a square at 45px and a piece at 80% of that. */
.pc, .knight__horse {
  background-repeat:no-repeat;
  background-size:1200% 100%;          /* twelve cells across */
  background-image:url("cb-pieces.png");
}
.pc { display:block; width:80%; height:80%; }
.pc--wk { background-position: 0.0000% 0; }
.pc--wq { background-position: 9.0909% 0; }
.pc--wn { background-position: 18.1818% 0; }
.pc--wb { background-position: 27.2727% 0; }
.pc--wr { background-position: 36.3636% 0; }
.pc--wp { background-position: 45.4545% 0; }
.pc--bk { background-position: 54.5455% 0; }
.pc--bq { background-position: 63.6364% 0; }
.pc--bn { background-position: 72.7273% 0; }
.pc--bb { background-position: 81.8182% 0; }
.pc--br { background-position: 90.9091% 0; }
.pc--bp { background-position: 100.0000% 0; }
.oreboard__cap { font-family:var(--mono); font-size:13px; color:var(--cb-grey); margin:var(--sp-sm) 0 0; }

/* The no-JS stand-in. Generated from the same data as the widget by build-report-data.py, so it
   can never show different numbers than the thing it stands in for — which the screenshot it
   replaced eventually did. */
.orefallback { width:100%; border-collapse:collapse; margin-top:var(--sp-lg); }
.orefallback caption { font-family:var(--mono); font-size:13px; color:var(--cb-grey); text-align:left; padding-bottom:var(--sp-sm); }
.orefallback th, .orefallback td { padding:10px var(--sp-sm); border-bottom:1px solid var(--cb-hairline); text-align:right; }
.orefallback thead th { font-family:var(--sans); font-weight:700; font-size:12px; letter-spacing:.08em; text-transform:uppercase; color:var(--cb-grey); }
.orefallback tbody th { font-family:var(--mono); font-weight:700; font-size:15px; color:var(--cb-navy); text-align:left; }
.orefallback td { font-family:var(--mono); font-size:14px; color:var(--cb-grey); }

.oreband { display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-sm) var(--sp-md); margin-bottom:var(--sp-lg); }
.oreband__label { font-family:var(--sans); font-weight:700; font-size:12px; letter-spacing:.09em; text-transform:uppercase; color:var(--cb-grey); }
.oreband__group { display:flex; flex-wrap:wrap; gap:6px; }
.oreband__btn {
  font-family:var(--mono); font-size:13px; padding:9px 12px; min-height:44px; cursor:pointer;
  background:var(--cb-white); color:var(--cb-navy);
  border:1px solid var(--cb-line); border-radius:var(--radius);
}
.oreband__btn:hover { border-color:var(--cb-navy); }
.oreband__btn.is-on { background:var(--cb-navy); border-color:var(--cb-navy); color:var(--cb-white); }

.orehead, .orerow {
  display:grid; align-items:center; gap:var(--sp-sm);
  grid-template-columns:78px 1fr 1fr 68px;
}
.orehead {
  /* 12px is the page's floor, and column headers are exactly the kind of label that gets set
     too small without anyone noticing. check-page.js enforces it. */
  font-family:var(--sans); font-weight:700; font-size:12px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--cb-grey); padding:0 var(--sp-sm) 6px;
  border-bottom:1px solid var(--cb-hairline);
}
.orehead span:last-child, .orerow__games { text-align:right; }

.orerow {
  width:100%; text-align:left; cursor:pointer; padding:10px var(--sp-sm); min-height:48px;
  background:none; border:0; border-bottom:1px solid var(--cb-hairline); color:inherit;
}
.orerow:hover { background:var(--cb-tint); }
.orerow.is-on { background:rgba(0,148,209,.07); }
.orerow__move { font-family:var(--mono); font-size:15px; font-weight:700; color:var(--cb-navy); }
.orerow__cell { display:flex; align-items:center; gap:8px; }
.orerow__num { font-family:var(--mono); font-size:13px; color:var(--cb-grey); white-space:nowrap; min-width:52px; }
.orerow__games { font-family:var(--mono); font-size:13px; color:var(--cb-grey); }

/* THE MOVE LIST ON A PHONE. The row is four columns — move, two bars with their figures, games —
   and every one of them carried a floor: 78px and 68px for the outer two, 52px under each figure,
   40px under each bar, 8px of gutter six times over. Added up that is 386px of minimum inside a
   294px column, so the row pushed the page sideways at 390px.
   Nothing is dropped and nothing is restacked: the floors are lowered to what the type actually
   needs. The figures set their own width now (min-width:0) at the page's 12px floor, and the bars
   keep enough length to still read as a proportion. */
@media (max-width:480px) {
  .orehead, .orerow { grid-template-columns:58px 1fr 1fr 46px; gap:var(--sp-xs); }
  .orehead { padding:0 var(--sp-xs) 6px; letter-spacing:.04em; }
  .orerow  { padding:10px var(--sp-xs); }
  .orerow__cell { gap:4px; min-width:0; }
  .orerow__num  { min-width:0; font-size:12px; }
  .orerow__games { font-size:12px; }
  .orebar { min-width:22px; }
}

.orebar { flex:1; height:9px; border-radius:5px; background:var(--cb-hairline); overflow:hidden; min-width:40px; }
.orebar__fill { display:block; height:100%; }
.orebar__fill--play  { background:var(--cb-navy); }
.orebar__fill--score { background:var(--cb-cyan); }

/* The result bar. White's share / draws / Black's share, the shape every chess player reads
   without a legend. White is the page white with a hairline so it reads on the row; draws are the
   neutral CI grey; Black is the CI near-black. No new colours, and no traffic-light — brand red
   stays reserved for things you click. */
/* Two sizes of the same bar, and only the taller one carries printed figures. In the move table
   this bar is one cell of a dense row and stays the 11px rule it always was: it can be 60px wide
   altogether, and the page holds all text to a 12px floor (check-page.js enforces it), so a
   printed share there would be either illegal or clipped to a digit. wdlBar()'s last argument asks
   for labels and the --labelled modifier below is what makes room for them. */
/* ROUNDED, AND THE END SEGMENTS DO THEIR OWN ROUNDING. Leaving it to overflow:hidden is what put
   a pale seam in the Black segment's corners and made it read as not quite filled: a rounded box
   with a 1px border clips its children to the INNER 4px arc, while the border is painted on the
   5px arc outside it, and the two curves do not share pixels. Squaring the box fixed the seam and
   lost the shape. Rounding the first and last SEGMENT instead means the black is painted round to
   begin with — its own corner lands exactly on the curve the clip would have cut, so there is
   nothing between them to leave a gap. 4px, not 5: inside a 1px border the inner radius is one
   less, and matching it is the whole point. */
.orewdl { flex:1; display:flex; height:11px; overflow:hidden; min-width:40px; border-radius:5px;
          border:1px solid var(--cb-hairline); background:var(--cb-hairline); }
.orewdl__seg:first-child { border-radius:4px 0 0 4px; }
.orewdl__seg:last-child  { border-radius:0 4px 4px 0; }
.orewdl__seg:only-child  { border-radius:4px; }
/* 20px because 12px type with a 1px border top and bottom does not fit 11px — the labels came out
   with their descenders sliced off. And Grau mittel rather than the hairline, round the outside and
   between the segments: printing the figures is what turns this bar from a glance into something a
   reader measures, and the White segment is #FFFFFF on a #FFFFFF panel, which the hairline bounds
   at 1.25:1 — no edge at all. #637178 is 5.04:1. The dense unlabelled table bars keep the
   hairline; eleven heavy outlines down a list is a different problem. */
.orewdl--labelled { height:20px; border-color:var(--cb-grey); }
.orewdl--labelled .orewdl__seg + .orewdl__seg { border-left:1px solid var(--cb-grey); }
/* The printed share, in its own element so it can be removed where there is no room for it. */
.orewdl__pct { pointer-events:none; }
/* UNDER 700px THE MOVE TABLE GOES BACK TO THE PLAIN BAR. Its bar cell is one of two 1fr columns in
   a panel about 326px wide, so the bar is roughly 100px and a 16 % segment 17px — narrower than the
   "16%" it would print, and a clipped figure reads as a wrong one. The rating strip below keeps its
   labels at every width because it has only one bar column and drops its score instead.
   Height and edges revert with the labels: 20px and a Grau mittel outline exist to hold printed
   text, and eleven of them down a dense list with nothing in them is just heavier. */
@media (max-width:700px) {
  .orerow .orewdl__pct { display:none; }
  .orerow .orewdl--labelled { height:11px; border-color:var(--cb-hairline); }
  .orerow .orewdl--labelled .orewdl__seg + .orewdl__seg { border-left:0; }
}
.orewdl__seg { display:flex; align-items:center; justify-content:center; height:100%;
               overflow:hidden; font-family:var(--mono); font-size:12px; line-height:1;
               color:var(--cb-black); }
.orewdl__seg--w { background:var(--cb-white); }
.orewdl__seg--d { background:var(--cb-line); }
/* The only segment dark enough to need the label reversed out of it. */
.orewdl__seg--b { background:var(--cb-black); color:var(--cb-white); }

.oredetail__sub { margin-top:var(--sp-md); }
.orelines, .oreplayers { list-style:none; margin:0; padding:0; display:grid; gap:4px; }
.orelines li { font-size:13px; line-height:1.45; color:var(--cb-grey); }
.orelines b { font-family:var(--mono); font-weight:700; color:var(--cb-navy); }
.orelines span { font-family:var(--mono); font-size:12px; }
.oreplayers li { font-size:13px; line-height:1.4; color:var(--cb-grey); }
.oreplayers b { font-family:var(--sans); font-weight:700; color:var(--cb-navy); }
.oreplayers span { font-family:var(--mono); font-size:12px; }

/* The pioneer line sits under the board, where it reads as a caption on the position rather than
   as another statistic in the table. */
.orehistory {
  margin:var(--sp-md) 0 0; padding-top:var(--sp-md); border-top:1px solid var(--cb-hairline);
  font-size:13px; line-height:1.5; color:var(--cb-grey);
}
.orehistory b { font-family:var(--sans); font-weight:700; color:var(--cb-navy); }
.orehistory:empty { display:none; }

.oresum { font-size:13px; color:var(--cb-grey); margin:var(--sp-md) 0 0; }

.oredetail { margin-top:var(--sp-lg); padding-top:var(--sp-lg); border-top:1px solid var(--cb-hairline); }
/* The block's own top heading is an h3 so it does not skip a level under the section's h2; its
   sub-headings stay h4. Both wear the same size — the level is about document structure, not
   about how loud the words are. */
.oredetail h3,
.oredetail h4 { font-family:var(--sans); font-weight:700; font-size:14px; color:var(--cb-navy); margin:0 0 var(--sp-md); }
.oredetail__hint, .oredetail__facts { font-size:14px; line-height:1.5; color:var(--cb-grey); margin:0 0 6px; }
.oredetail__facts b { color:var(--cb-navy); }

/* The per-band strip: the same move measured six times, laid out like the move table above it
   and like every reference book — the class, the White/draws/Black bar, the score, the count.

   IT HAS BEEN TWO OTHER THINGS, both recorded so neither comes back. Six vertical cyan columns
   whose heights were shares of 100: real opening scores all sit between about 45 % and 60 %, so
   the interesting range was 15 points inside a 100-point scale and the six bars stood within ten
   pixels of each other — rankable only by reading the six figures printed above them, which makes
   it a table with decoration. Then six stubs measured up and down from a 50 % axis, teal above and
   amber below: readable, but it invented a colour scheme used nowhere else on the page and put a
   traffic light on a page whose own stylesheet says it does not have one (see .orewdl).
   Reusing .orewdl instead costs nothing and buys two things. The page has one way of drawing a
   result, so a reader learns it once. And a split says more than a score can: 54 % is a lot of
   decisive games both ways, or a lot of draws, or a quiet edge, and only the three numbers tell
   you which. */
.orestrip { margin-bottom:var(--sp-md); }
/* The same four-part grid as .orehead / .orerow, one column shorter: there is no move to name, so
   the class label takes that slot. Bars line up down the block, which is what lets the eye compare
   White's share between classes without reading a number. */
.orestrip__head, .orestrip__row {
  display:grid; align-items:center; gap:var(--sp-sm);
  grid-template-columns:78px 1fr 68px;
}
.orestrip__head {
  /* 12px is the page's floor and check-page.js enforces it. */
  font-family:var(--sans); font-weight:700; font-size:12px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--cb-grey); padding:0 var(--sp-sm) 6px;
  border-bottom:1px solid var(--cb-hairline);
}
.orestrip__head span:last-child, .orestrip__games { text-align:right; }
.orestrip__row { padding:9px var(--sp-sm); border-bottom:1px solid var(--cb-hairline); }
/* The selected class, marked the way .orerow marks the selected move — same tint, same weight
   shift on the labels. Nothing new to learn. */
.orestrip__row.is-on { background:rgba(0,148,209,.07); }
.orestrip__row.is-on .orestrip__band,
.orestrip__row.is-on .orestrip__val,
.orestrip__row.is-on .orestrip__games { color:var(--cb-navy); font-weight:700; }
/* 12px, the floor the whole page is held to. These were 11 and 10 and had been since the strip
   was written — check-page.js never caught them because it only ever measured the page as loaded,
   and until the opening position grew a detail panel of its own this block appeared only after
   the reader played a move. A gate can only see what is on screen. */
.orestrip__band  { font-family:var(--mono); font-size:12px; color:var(--cb-grey);
                   letter-spacing:-.02em; white-space:nowrap; }
.orestrip__val   { font-family:var(--mono); font-size:12px; color:var(--cb-grey);
                   white-space:nowrap; min-width:46px; text-align:right; }
.orestrip__games { font-family:var(--mono); font-size:12px; color:var(--cb-grey); }
.orestrip__cell  { display:flex; align-items:center; gap:8px; min-width:0; }
/* A class with no games at all keeps the row, so the six classes stay a fixed list rather than a
   list that changes length as the reader plays — but it gets no bar, because there is nothing to
   divide. The dashed box says "no games here"; the score column says the same in one glyph. */
.orestrip__none { flex:1; height:18px; min-width:40px;
                  border:1px dashed var(--cb-line); border-radius:5px; }
/* NARROW: THE BAR GETS THE ROOM AND THE SCORE COLUMN GOES. At 390px the panel is about 326px, and
   78 + 68 for the two end columns plus the 46px score left the bar roughly 100px — a 16 % segment
   is then 16px, which is narrower than the "16%" it has to hold, so the labels came out clipped and
   a clipped figure reads as a wrong one. Trimming the end columns to the same 58/46 the move table
   uses down here, and dropping the score, gives the bar about 198px: the smallest segment that ever
   carries a label is 16 %, or 32px, which holds it.
   The score is the column to lose rather than the count, because it is the only one the bar already
   answers — "Weiß punktet 55,7 %" is (white + draws/2) of the very three numbers printed inside it.
   The game count is not in the bar at all, and without it a 46 % from three games looks like a 46 %
   from three thousand. */
@media (max-width:700px) {
  .orestrip__head, .orestrip__row { grid-template-columns:66px 1fr 46px; gap:var(--sp-xs); }
  .orestrip__row { padding:9px var(--sp-xs); }
  .orestrip__val { display:none; }
}

/* ---------------------------------------------------------------- pillars ---- */
/* Security / privacy / speed / updates — the reasons a verified copy is worth having. These
   used to be four bordered cards with four icons above four headings, which is the layout every
   SaaS page on earth reaches for. As plain columns under a hairline they read faster, take a
   quarter of the height, and stop competing with the screenshots above them. */
.pillars {
  display:grid; gap:var(--sp-lg) var(--sp-xl); grid-template-columns:1fr;
  margin-top:var(--sp-2xl); padding-top:var(--sp-xl);
  border-top:1px solid rgba(255,255,255,.16);
}
/* Standing alone in a band there is nothing above to separate from. */
.wrap > .pillars:first-child { margin-top:0; padding-top:0; border-top:0; }
@media (min-width:620px)  { .pillars { grid-template-columns:repeat(2,1fr); } }
@media (min-width:1000px) { .pillars { grid-template-columns:repeat(4,1fr); } }
.pillars h3 {
  font-family:var(--sans); font-weight:700; font-size:13px; letter-spacing:.11em;
  text-transform:uppercase; color:var(--cb-white); margin:0 0 var(--sp-sm);
}
.pillars p { font-size:15px; line-height:1.55; color:#B9C3D0; margin:0; }

/* -------------------------------------------------------------- package ---- */
/* The real 3D covers. A package row with no product on it is a price list, not a shop. */
/* THE CB14 EDITIONS ROW. Three boxes standing on one shelf, growing left to right — Starter,
   Mega, Premium — with the columns aligned on their feet so the tops step up. The sizes are that
   campaign's, read out of J:\Work\CB14-Upgrade: 172 / 230 / 288 for .ed--sm / .ed / .ed--feat.
   No card around them, also from CB14: a border drawn round each column would box the boxes, and
   three frames of the same size are exactly the "all equal" reading the staircase exists to
   replace. The white sheet is gone with the border — the packages sit on the page like the hero's
   box does. */
.packs {
  display:grid; grid-template-columns:1fr; gap:var(--sp-2xl);
  margin-top:var(--sp-2xl);
  /* end, not stretch: the columns are as tall as they need to be and stand on a common line, and
     that difference IS the staircase. Stretch would give three equal columns again. */
  align-items:end;
}
@media (min-width:760px) { .packs { grid-template-columns:repeat(3,1fr); gap:var(--sp-lg); } }

.pack {
  display:flex; flex-direction:column; align-items:center; text-align:center; position:relative;
  /* The flag's row, reserved in EVERY column and not only the one that has a flag. Reserving it
     once would push the featured column's box down and put a step in the wrong place. */
  padding-top:34px;
}
.pack__art {
  display:block; width:100%; max-width:200px; margin:0 auto var(--sp-lg);
  filter:drop-shadow(0 26px 40px rgba(20,15,10,.26));
  transition:transform .3s cubic-bezier(.2,.7,.3,1);
}
.pack--sm .pack__art { max-width:152px; }
.pack--lg .pack__art { max-width:252px; }
.pack:hover .pack__art { transform:translateY(-8px) rotate(-1deg); }
/* Between the three-column break and a full desktop the columns are only about 280px wide, and
   288px of Premium in 280px of column is why the boxes looked like they were leaning on each
   other. Same ratios, smaller shelf. */
@media (min-width:760px) and (max-width:1000px) {
  .pack__art       { max-width:168px; }
  .pack--sm .pack__art { max-width:128px; }
  .pack--lg .pack__art { max-width:208px; }
}
@media (prefers-reduced-motion: reduce) {
  .pack__art { transition:none; }
  .pack:hover .pack__art { transform:none; }
}
.pack__flag { position:absolute; top:0; left:50%; transform:translateX(-50%); background:var(--cb-red); color:var(--cb-white); font-family:var(--sans); font-weight:700; font-size:12px; letter-spacing:.1em; text-transform:uppercase; padding:5px 14px; border-radius:var(--radius); white-space:nowrap; }
.pack h3 { font-family:var(--display); font-weight:400; font-size:34px; letter-spacing:.01em; margin-bottom:var(--sp-sm); }
/* Same :not() guard as .feature__name — .pack p outranks .pack__price and would set a 40px
   price in 16px body copy. */
.pack p:not(.pack__price) { margin:0 auto var(--sp-lg); max-width:34ch; color:var(--cb-grey); font-size:16px; }
.pack__price { font-family:var(--display); font-size:40px; color:var(--cb-navy); line-height:1; margin:0 0 var(--sp-lg); max-width:none; }
/* The struck old price sits INSIDE .pack__price so the pair shares one baseline. Grey figure,
   red rule, same as the hero's — one way of saying "this price no longer applies". */
.pack__was { font-family:var(--sans); font-weight:400; font-size:18px; color:var(--cb-grey); margin-right:10px;
             text-decoration-line:line-through; text-decoration-color:var(--cb-red);
             text-decoration-thickness:2px; text-decoration-skip-ink:none; }
/* One width for all three buttons. Their labels differ by a word, and three buttons of three
   widths under three boxes of three sizes is one variable too many — the boxes carry the size
   difference, the buttons should not repeat it. */
.pack .btn { margin-top:auto; min-width:190px; }

/* ---------------------------------------------------------------- offer ---- */
/* The single version, and the cheapest way onto ChessBase 26 — the price the hero quotes. The
   shape is CB14's closing offer: two prices side by side under their own labels, the old one
   struck, then what you get, then one button.
   A card rather than a bare block, because this is the last thing the page asks for and it
   should read as one object rather than as more page. */
.offer {
  background:var(--cb-white); border:1px solid var(--cb-hairline); border-radius:var(--radius-lg);
  padding:var(--sp-xl); margin-top:var(--sp-xl); text-align:center; box-shadow:var(--shadow-s);
}
@media (max-width:620px) { .offer { padding:var(--sp-lg); } }

.offer__prices { display:flex; flex-wrap:wrap; justify-content:center; align-items:flex-end; gap:var(--sp-2xl); }
.offer__pr { display:flex; flex-direction:column; align-items:center; gap:6px; margin:0; max-width:none; }
/* 12px is the page's floor and check-page.js enforces it — a label under a price is exactly the
   kind of type that gets set at 10px without anyone noticing. */
.offer__lbl { font-family:var(--sans); font-weight:700; font-size:12px; letter-spacing:.1em;
              text-transform:uppercase; color:var(--cb-grey); }
.offer__val { font-family:var(--display); font-size:34px; line-height:1; color:var(--cb-grey); }
.offer__val--new { font-size:54px; color:var(--cb-red); }
.offer__val s { text-decoration-line:line-through; text-decoration-color:var(--cb-red);
                text-decoration-thickness:2.5px; text-decoration-skip-ink:none; }

.offer__list { list-style:none; margin:var(--sp-xl) auto 0; padding:0; max-width:52ch;
               text-align:left; display:grid; gap:var(--sp-sm); }
.offer__list li { position:relative; padding-left:32px; color:var(--cb-navy); }
/* A drawn tick rather than a character: a checkmark glyph is a different weight in every fallback
   face, and this one is always the same two strokes in the page's own cyan. */
.offer__list li:empty { display:none; }
.offer__list li::before {
  content:""; position:absolute; left:5px; top:.34em;
  width:12px; height:6px; border-left:2.5px solid var(--cb-cyan-deep);
  border-bottom:2.5px solid var(--cb-cyan-deep); transform:rotate(-45deg);
}
.offer .btn-row { justify-content:center; margin-top:var(--sp-xl); }
.offer .small { margin:var(--sp-md) auto 0; max-width:56ch; }

/* ------------------------------------------------------------------ faq ---- */
.faq { display:grid; gap:var(--sp-sm); }
.faq details { background:var(--cb-white); border:1px solid var(--cb-hairline); border-radius:var(--radius-lg); }
.faq summary { cursor:pointer; list-style:none; padding:var(--sp-md) var(--sp-lg); font-family:var(--sans); font-weight:700; font-size:18px; color:var(--cb-navy); display:flex; align-items:center; justify-content:space-between; gap:var(--sp-md); min-height:60px; }
.faq summary::-webkit-details-marker { display:none; }
.faq summary::after { content:""; flex:none; width:11px; height:11px; border-right:2px solid var(--cb-grey); border-bottom:2px solid var(--cb-grey); transform:rotate(45deg) translate(-3px,-3px); transition:transform .18s ease; }
.faq details[open] summary::after { transform:rotate(-135deg) translate(-3px,-3px); }
.faq .faq__body { padding:0 var(--sp-lg) var(--sp-lg); }
.faq .faq__body p:last-child { margin-bottom:0; }

/* ----------------------------------------------------------------- form ---- */
/* The secondary path, for the customer our records failed to match. Compact on purpose: it
   has to be easy to find and easy to ignore. */
.formcard { background:var(--cb-white); border:1px solid var(--cb-hairline); border-radius:var(--radius-lg); padding:var(--sp-xl); box-shadow:var(--shadow-s); }
@media (max-width:620px) { .formcard { padding:var(--sp-lg); } }

.field { margin-bottom:var(--sp-lg); }
.field > label { display:block; font-family:var(--sans); font-weight:700; font-size:16px; color:var(--cb-navy); margin-bottom:6px; }
.field .opt { font-weight:400; color:var(--cb-grey); font-size:15px; }
.field .hint { display:block; font-size:15px; color:var(--cb-grey); margin-top:6px; }
.field input[type="text"],.field input[type="email"],.field select,.field textarea {
  width:100%; font-family:var(--sans); font-size:17px; line-height:1.4;
  color:var(--cb-black); background:var(--cb-white);
  padding:13px 14px; min-height:50px;
  border:1px solid var(--cb-line); border-radius:var(--radius);
}
.field textarea { min-height:104px; resize:vertical; }
.field input::placeholder,.field textarea::placeholder { color:#7E888D; opacity:1; }
.field input:focus,.field select:focus,.field textarea:focus { border-color:var(--cb-navy); outline:3px solid var(--cb-red); outline-offset:1px; }
.field input.serial { letter-spacing:.06em; text-transform:uppercase; font-variant-numeric:tabular-nums; }

/* Functional red, thin edge only — never a fill, so it can never be mistaken for a CTA. */
.field.is-invalid input,.field.is-invalid select,.field.is-invalid textarea { border-color:var(--cb-error); border-width:2px; }
.field__error { display:none; margin-top:6px; padding:8px 12px; background:var(--cb-error-bg); border-left:3px solid var(--cb-error); border-radius:0 var(--radius) var(--radius) 0; color:#7A1A12; font-size:15px; }
.field.is-invalid .field__error { display:block; }
.field.is-off > label,.field.is-off .hint { color:var(--cb-grey); }
.field.is-off input { background:var(--cb-tint); color:var(--cb-grey); }

.checkline { display:flex; gap:var(--sp-sm); align-items:flex-start; min-height:44px; padding:6px 0; margin:-6px 0 var(--sp-lg); cursor:pointer; }
.checkline input[type="checkbox"] { width:22px; height:22px; margin:3px 0 0; flex:none; accent-color:var(--cb-red); }
.checkline span { font-size:17px; color:var(--cb-black); }

.from-program { display:block; margin-top:6px; font-size:15px; color:var(--cb-grey); padding-left:14px; border-left:3px solid var(--cb-line); }
.note-count { display:block; margin-top:6px; font-size:15px; color:var(--cb-grey); }

.error-summary { margin-bottom:var(--sp-lg); padding:var(--sp-md) var(--sp-lg); background:var(--cb-error-bg); border-left:4px solid var(--cb-error); border-radius:0 var(--radius) var(--radius) 0; }
.error-summary h3 { margin:0 0 var(--sp-sm); font-size:17px; color:#7A1A12; }
.error-summary ul { margin:0; padding-left:20px; }
.error-summary li { margin-bottom:4px; }
.error-summary a { color:#7A1A12; }

/* Honeypot: off-screen rather than display:none, which the better bots detect. */
.hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

.formstatus { margin-top:var(--sp-lg); padding:var(--sp-md) var(--sp-lg); border-radius:var(--radius); font-size:17px; }
.formstatus--ok  { background:var(--cb-ok-bg);    border-left:4px solid var(--cb-ok);    color:#14512F; }
.formstatus--err { background:var(--cb-error-bg); border-left:4px solid var(--cb-error); color:#7A1A12; }
.formstatus h3 { margin:0 0 var(--sp-sm); font-size:19px; color:inherit; }
.formstatus p:last-child { margin-bottom:0; }
#form-wrap h2 { font-family:var(--sans); font-size:26px; line-height:1.2; }

.spinner { width:18px; height:18px; border-radius:50%; flex:none; border:2px solid rgba(255,255,255,.45); border-top-color:var(--cb-white); animation:spin .8s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }

/* --------------------------------------------------------------- footer ---- */
/* THREE BANDS, GETTING QUIETER. The signet and the house line, then the links, then a hairline
   and the legal text under it. It was an address and four links side by side: correct, and the
   page ended rather than closed. The shape is the CB14 campaign's closing block, which this page
   already borrows its hero, its packages and its type from. */
/* ---- the privacy notice: label + text, the requirements list's shape but a longer measure,
   because these are sentences and not specs. Body colour, not .small: this is content the
   reader is entitled to, not a footnote. ---- */
.privacy { list-style:none; margin:0; padding:0; display:grid; gap:var(--sp-md); max-width:80ch; }
.privacy li { display:grid; grid-template-columns:minmax(120px,168px) 1fr; gap:var(--sp-xs) var(--sp-lg); font-size:16px; line-height:1.55; }
.privacy li strong { color:var(--cb-navy); font-weight:700; }
@media (max-width:600px) { .privacy li { grid-template-columns:1fr; } }

.site-foot { background:var(--cb-navy); color:#AEB8C4; padding:var(--sp-2xl) 0 var(--sp-xl); font-size:16px; }
.site-foot a { color:var(--cb-white); }
.site-foot__top {
  display:flex; flex-wrap:wrap; gap:var(--sp-lg) var(--sp-2xl);
  justify-content:space-between; align-items:center;
}
.site-foot__brand { display:flex; align-items:center; gap:var(--sp-md); text-decoration:none; }
.site-foot__brand img { display:block; width:44px; height:44px; }
/* The house line, in the display face and spaced out — it is a mark, not a sentence, and it
   should read as part of the signet rather than as the first of the links. */
.site-foot__tag {
  font-family:var(--display); font-weight:400; font-size:15px;
  letter-spacing:.14em; text-transform:uppercase; color:#AEB8C4;
}
.site-foot__brand:hover .site-foot__tag { color:var(--cb-white); }
.site-foot__links { display:flex; flex-wrap:wrap; gap:0 var(--sp-lg); list-style:none; margin:0; padding:0;
                    font-family:var(--sans); font-weight:700; font-size:15px; }
/* 44px of height on every link, because on a phone these are the last things a thumb reaches
   and they used to be 20px tall. */
.site-foot__links a { display:inline-flex; align-items:center; min-height:44px; text-decoration:none; }
.site-foot__links a:hover { color:var(--cb-cyan); text-decoration:underline; text-underline-offset:3px; }
/* rgba on navy rather than a token: this is a hairline ON a dark ground, and every hairline in
   the palette is drawn for white. */
.site-foot__base {
  margin-top:var(--sp-lg); padding-top:var(--sp-lg);
  border-top:1px solid rgba(255,255,255,.14);
}
.site-foot p { margin:0 0 6px; max-width:64ch; }
.site-foot__base p:last-child { margin-bottom:0; }
@media (max-width:640px) {
  .site-foot__top { flex-direction:column; align-items:flex-start; gap:var(--sp-md); }
  .site-foot__links { gap:0 var(--sp-md); }
}
/* The footer is a dark ground but not .band--ink, so it needs the same rescue: Grau mittel
   measures 3.1:1 on navy and misses AA. */
.site-foot .small { color:#AEB8C4; }

/* ---------------------------------------------------------------- print ---- */
@media print {
  body { background:#fff; color:var(--cb-black); font-size:11pt; }
  .band--ink,.band--tint,.band--paper { background:#fff !important; color:var(--cb-black) !important; background-image:none !important; }
  .band--ink h2,.band--ink p,.band--ink .kicker { color:var(--cb-black) !important; }
  .card,.formcard,.why,.feature__art { border:1px solid var(--cb-line); break-inside:avoid; }
  .faq details > .faq__body { display:block !important; }
  a[href^="http"]::after { content:" (" attr(href) ")"; font-size:9pt; color:var(--cb-grey); }
  a[href^="#"]::after,a[href^="mailto"]::after,a[href^="tel"]::after { content:""; }
}

/* ---- where the pieces go ----
   Not a second board any more: the report board shades the destinations of the moves it is
   already listing, so the picture and the table are the same data and cannot disagree. Under the
   piece, over the square — a piece standing on a hot square has to stay readable. */
/* ARROWS ON THE BOARD, one per listed move, thickness by how often it is played.
   This replaced a heat shading of the destination squares. Shading says where pieces END UP,
   which throws away the half of a chess move that makes it a move — a lit square on d5 does not
   say whether the queen came from d8 or the knight from c3.
   The overlay sits above the squares and below nothing: pointer-events are off, so every square
   underneath stays clickable and draggable exactly as before.
   Colours are the trend charts' four series slots, so a move is the same colour wherever the
   reader meets it; past the fourth an arrow is neutral, because a fifth hue would break the one
   rule the palette has. */
.oreboard__grid { position:relative; }
.orearrows {
  position:absolute; inset:0; width:100%; height:100%;
  pointer-events:none; z-index:2; overflow:visible;
}
/* PAINT AND OPACITY BOTH COME FROM THE SCRIPT NOW, as a gradient laid along each arrow — see
   cb26Fade() and the CB26 note above it in cb-report.js. ChessBase 26 draws its arrows through a
   GDI+ gradient from about 10% opacity at the tail to 70% at the head, so an arrow points by
   getting stronger, and that is what keeps a board with six of them on it readable. A blanket
   `opacity` here would flatten it back out, and a `stroke`/`fill` here would win over nothing but
   still confuse the next reader, so neither is set.
   Four `--s1`..`--s4` slot rules used to live here, colouring each arrow to match its line in the
   trend chart. They are gone with the recolour: the arrows now carry the move's SCORE in the
   program's own attack-red / threat-yellow / defence-green, which is a different and, on a board,
   more useful thing to say. The chart keeps its own legend. Restoring the slot link means putting
   these four rules back and dropping the pathColour() call in drawArrows.
   Line caps stay in CSS because they are shape, not paint: CB26 sets SetStrokeCaps(round) for the
   tail stub and SetStrokeJoin(round) for the head corners. */
/* The old board's arrow: a stroked shaft with a round stub at the tail (GDI+
   SetStartCap(LineCapRound)) and a FILLED swallowtail head whose corners are rounded
   (SetStrokeJoin(round)). Paint comes from the script as a gradient per arrow — see cb26Fade — so
   nothing here sets stroke or fill, and a blanket opacity would flatten the fade the program is
   built around. */
.orearrow line { stroke-linecap:round; fill:none; }
.orearrow polygon { stroke-linejoin:round; }

/* ---- the decade slider ----
   Sits under the rating chips and does the same job on a different axis. Laid out on one row so
   the label, the track and the current period read as a sentence. */
.oreera { display:flex; align-items:center; gap:var(--sp-md); margin:var(--sp-sm) 0 var(--sp-lg); flex-wrap:wrap; }
/* 12px to match .oreband__label and the page's floor — nothing on this page goes below it. */
.oreera__label { font-family:var(--sans); font-size:12px; font-weight:700; letter-spacing:.09em;
                 text-transform:uppercase; color:var(--cb-grey); }
.oreera__range { flex:1 1 200px; min-width:160px; height:44px; accent-color:var(--cb-cyan);
                 background:none; cursor:pointer; }
.oreera__range:focus-visible { outline:2px solid var(--cb-cyan); outline-offset:4px; }
.oreera__value { font-family:var(--mono); font-size:14px; font-weight:700; color:var(--cb-navy);
                 min-width:7.5em; }

/* "Alle" sits with the classes but is not one of them — a slight separation so the row reads as
   "everybody, or one of these six" rather than as seven equivalent bands. */
.oreband__btn--all { margin-right:var(--sp-sm); font-weight:700; }

/* A rating class with nothing in it. Stated plainly and on its own, because the alternative the
   page used to fall back to was everybody's figures sitting under a class chip. */
.oreempty { font-size:15px; font-weight:700; color:var(--cb-navy); margin:0 0 var(--sp-xs); }

/* ---- end of a line ----
   Every variation now stops at the same depth, so this panel is the last thing a reader sees in
   the section. It carries the summary of the line rather than a bare "that's all". */
.oreend h4 { font-family:var(--sans); font-weight:700; font-size:14px; color:var(--cb-navy); margin:0 0 var(--sp-sm); }
.oreend__line { font-family:var(--mono); font-size:14px; color:var(--cb-navy); margin:0 0 var(--sp-sm); line-height:1.5; }
.oreend__stat { font-size:15px; color:var(--cb-grey); margin:0 0 var(--sp-sm); }
.oreend__stat b { color:var(--cb-navy); }
/* Only the width and the spacing below. The height, the border and the segment dividers all
   come from .orewdl--labelled, which wdlBar() puts on any bar it prints figures into — so
   this bar and the rating strip cannot drift apart. 320px is what holds three 12px labels
   comfortably; the strip gets the same treatment from the same modifier. */
.oreend__wdl { display:flex; width:100%; max-width:320px; margin-bottom:6px; }
/* Most ends have a score but no win/draw/loss split, so they get the same single bar the move
   rows use — the reader has already learnt to read it three rows further up. */
.oreend__bar { display:block; flex:none; width:100%; max-width:320px; height:13px; margin-bottom:var(--sp-sm); }
.oreend__legend { font-family:var(--mono); font-size:12px; color:var(--cb-grey); margin:0 0 var(--sp-md); }
.oreend__share { font-size:13px; color:var(--cb-grey); margin:0 0 var(--sp-md); }
/* Whose score the continuation percentages are — the other side's, and saying so is cheaper than
   letting the reader carry the side over from the line above. */
.oreend__note { font-family:var(--sans); font-size:11px; letter-spacing:.08em; text-transform:uppercase;
                color:var(--cb-grey); margin:calc(-1 * var(--sp-xs)) 0 var(--sp-sm); }
.orenext { list-style:none; margin:0; padding:0; display:grid; gap:4px; }
.orenext li { font-size:13px; line-height:1.45; color:var(--cb-grey); }
.orenext b { font-family:var(--mono); font-weight:700; font-size:14px; color:var(--cb-navy); }
.orenext span { font-family:var(--mono); font-size:12px; }
/* ================================================================================================
   Two page gimmicks — neither of them about the board. See assets/cb-extras.js.
   ================================================================================================ */

/* ---- 1. what the database gained since ChessBase 17 ---- */
.since-host.is-live .small { display:none; }
.since { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.15fr); gap:var(--sp-2xl);
         align-items:center; }
.since__num { display:block; font-family:var(--display); font-weight:400; font-size:clamp(48px,9vw,104px);
              line-height:.95; color:var(--cb-red); font-variant-numeric:tabular-nums; }
.since__cap { font-size:16px; color:var(--cb-black); margin:var(--sp-md) 0 0; max-width:34em; }
/* One bar per year. Height is the count; colour is whether it is a year the reader has missed. */
.since__chart { display:flex; align-items:flex-end; gap:4px; height:190px; }
/* THE YEAR LABEL GETS ITS OWN STRIP AND THE BARS NEVER SHRINK. Both used to be ordinary flex
   children of this column, at the default flex-shrink:1, and the bar heights are percentages of
   the column — so in the two tallest columns the bars plus the label wanted more than the 190px
   there is. Flex resolved that by shrinking the bars in proportion, which is the one thing they
   must not do: the grey segment is the reader's own database and is deliberately the SAME height
   in every column from 2021 on, frozen, while the cyan above it climbs. Instead it lost 5px in
   2025 and 8px in 2026, so the part that is meant to stand still visibly shrank in the last two
   years and the chart argued against itself. Reserving the label's strip as padding takes it out
   of the flex line altogether, and percentage heights then resolve against a content box that
   holds nothing else. */
.since__col { flex:1; display:flex; flex-direction:column; justify-content:flex-end; height:100%;
              position:relative; box-sizing:border-box; padding-bottom:24px; }
/* Two stacked segments per column: what the reader's database already holds, and what has arrived
   since. Grey freezes; cyan keeps climbing. */
.since__bar { min-height:2px; flex:0 0 auto; }
.since__bar--have { background:var(--cb-line); }
.since__bar--miss { background:var(--cb-cyan); border-radius:2px 2px 0 0; }
.since__col > .since__bar--have:first-child { border-radius:2px 2px 0 0; }
.since__year { position:absolute; left:0; right:0; bottom:0;
               font-family:var(--mono); font-size:12px; line-height:18px; color:var(--cb-grey);
               text-align:center; }
.since__foot { font-size:13px; color:var(--cb-grey); margin:var(--sp-md) 0 0; grid-column:1/-1; }
@media (max-width:820px) { .since { grid-template-columns:1fr; gap:var(--sp-lg); } }


/* ---- Monte Carlo panel ----
   Sits under the board because that is where its other half is: the number is here, the piece
   paths from the same games are on the squares above. */
.oremc { margin-top:var(--sp-lg); border-top:1px solid var(--cb-hairline); padding-top:var(--sp-md); }
.oremc__btn { width:100%; }
.oremc__body { margin-top:var(--sp-md); }
/* THE BAR GAINS COLOUR AS THE RESULT BECOMES TRUSTWORTHY — grey below 25 games, colouring up from
   there, after the program's CalculateSaturationFromGameCount. Confidence is drawn rather than
   asserted, which is a better argument than any caption: a bar that starts grey tells the reader
   what a small sample is worth without a word.
   --sat is set per frame from JavaScript and mixes each segment toward Grau hell. */
/* WHITE, GREY, BLACK — THE SAME THREE AS THE ERÖFFNUNGSREPORT'S .orewdl BAR. This bar used to be
   navy / grey / cyan, which meant the page drew the same quantity — how a position's games split
   between White, draws and Black — in two different colour schemes a few hundred pixels apart, and
   the reader had to learn each one. Now the Monte Carlo playouts are painted in the colours of the
   pieces that won them, exactly as the report above does it, and the legend is the chessboard.
   THE BORDER IS GRAU MITTEL, NOT THE HAIRLINE, and the segments are ruled off from each other.
   That is not decoration. The White segment ends up at #E9EBEB — every shipped pool is a 500-game
   sample, so --sat settles at 0.55 — against a .report panel that is #FFFFFF: 1.20:1, and the
   hairline #E4E6E6 that used to bound it is 1.25:1 against the panel and 1.05:1 against the
   segment, so it drew no edge at all. White's ~42% of the bar simply had no visible left or right
   end, and it got worse as the sample grew, which is the confidence cue running backwards. WCAG
   1.4.11 asks 3:1 of a graphical object you need to see to read the content. Grau mittel #637178 on
   #FFFFFF is 5.04:1, and the same rule between segments gives the White|Draws join a real edge
   where the two colours only manage 2.12:1 on their own. */
.oremc__bar {
  /* Rounded like .orewdl above, and the end segments round themselves for the same reason — see
     the note there. */
  display:flex; height:26px; border-radius:var(--radius); overflow:hidden;
  background:var(--cb-hairline);
  border:1px solid var(--cb-grey); box-sizing:border-box;
}
.oremc__seg {
  font-family:var(--mono); font-size:12px; color:var(--cb-black);
  display:flex; align-items:center; justify-content:center;
  transition:width .08s linear;
}
.oremc__seg + .oremc__seg { border-left:1px solid var(--cb-grey); }
.oremc__seg:first-child { border-radius:calc(var(--radius) - 1px) 0 0 calc(var(--radius) - 1px); }
.oremc__seg:last-child  { border-radius:0 calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0; }
/* --sat is the confidence cue: a small sample washes every segment toward Grau. It now runs from
   50% to 90% rather than 0% to 80%. The floor is what the Black segment's label stands on: its
   number is reversed out in white, because dark text on it falls to 1.18:1 at the top of the ramp —
   and at --sat 0 every segment collapsed to the identical Grau hell #9CA4A7, where white text is
   2.54:1 and fails AA. (Dark text there is 6.86:1 and would have been fine; it is specifically the
   white-on-grey case that failed.) From 50% up, dark-on-White, dark-on-Grau and white-on-Black all
   clear 4.5:1 across the whole ramp, and a washed-out bar still reads as washed out. */
.oremc__seg--w { background:color-mix(in srgb, var(--cb-white) calc(50% + var(--sat) * 50%), var(--cb-line)); }
.oremc__seg--d { background:var(--cb-line); }
.oremc__seg--l { background:color-mix(in srgb, var(--cb-black) calc(50% + var(--sat) * 50%), var(--cb-line)); color:var(--cb-white); }
/* Without color-mix the segments simply keep their full colour: the bar loses the confidence cue
   and nothing else, which is the right thing for it to lose. Both edges still hold, because the
   Grau mittel border and the rules between the segments do not depend on color-mix.
   Only the two mixed segments are restated. The Draw segment is var(--cb-line) unconditionally
   above and repeating it here changed no rendering, while reading as though the fallback put
   something back. */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .oremc__seg--w { background:var(--cb-white); }
  .oremc__seg--l { background:var(--cb-black); }
}
.oremc__read { font-family:var(--mono); font-size:13px; color:var(--cb-grey); margin:var(--sp-sm) 0 0; }
.oremc__read b { color:var(--cb-navy); }
.oremc__note { font-size:13px; color:var(--cb-grey); margin:var(--sp-sm) 0 0; }
.oremc.is-running .oremc__btn { opacity:.65; }
/* The paths are drawn straight onto the board overlay; colour comes from JavaScript because it is
   a value on a scale, not one of a fixed set.
   EVERY MARK IS DRAWN TWICE: a white casing 0.04 squares proud on every side, then the coloured
   mark on top. The score ramp had to go dark to clear 3:1 against the dark square, and a dark ramp
   is nearly iso-luminant — so without the casing two crossing routes would read 1.0:1 against each
   other, and a capture head landing on a piece 1.3:1. The casing makes white the adjacent colour
   in both cases. */
.orepath line { stroke-linecap:round; }
.orepath__case { opacity:.9; }
.orepath__case line { stroke:var(--cb-white); }
/* THE CASING'S ARROWHEAD IS STROKED AS WELL AS FILLED, and the width is explicit: an unsized
   stroke here is 1 user unit, which is one whole square. 0.08 dilates the triangle by 0.04 on
   every edge — the cheapest uniform dilation without offsetting the polygon in JavaScript — and
   the round join keeps the tip from spiking past the square it points at. */
.orepath__case polygon { fill:var(--cb-white); stroke:var(--cb-white); stroke-width:.08;
                         stroke-linejoin:round; stroke-linecap:round; }
.orepath__case circle { fill:var(--cb-white); }
.orepath__core polygon, .orepath__core circle { stroke:none; }

/* (The CB17 page's jumping knight lived here. There is no knight on this page.) */

/* ---- variant C: the price leads ----
   The offer sits directly under the headline, so the saving and the reference price are read as
   one line rather than hunted for further down. The saving is a filled brand-red block (the CI's
   rule: brand red is always a solid block, never a thin edge); the old price is quiet beside it. */
.price-line { display:flex; align-items:baseline; flex-wrap:wrap; gap:var(--sp-md);
              margin:var(--sp-md) 0 0; }
.price-line__save { font-family:var(--sans); font-weight:700; font-size:15px; color:var(--cb-white);
                    background:var(--cb-red); padding:6px 12px; border-radius:var(--radius); }   /* was --radius-sm, undefined: square corners */
.price-line__was { font-size:15px; color:var(--cb-grey); }
/* THE AMOUNT IS STRUCK, THE LABEL IS NOT. "Neupreis ab" is still true — it is the price that no
   longer applies to this reader, so <s> carries the meaning and the line is only drawn through
   the figure.
   skip-ink is switched off on purpose: left to itself the browser breaks the rule around the
   comma and the euro sign, and a price with gaps in its strike looks like a rendering fault
   rather than a struck-out price. */
.price-line__was s {
  text-decoration-line:line-through;
  /* The rule is red and the figure stays grey. Colouring the figure red as well would make the
     price that no longer applies the loudest thing in the line, competing with the headline's own
     red price; the rule alone is enough to say "not this one". 2px, because a 1.5px line in a
     different colour from the text under it reads as a rendering artefact at 15px. */
  text-decoration-color:var(--cb-red);
  text-decoration-thickness:2px;
  text-decoration-skip-ink:none;
}

/* ========================================================== 4b. the lightbox ---- */
/* Click a screenshot, see it big. Seven pictures, one overlay, one gallery in document order.

   NO NEW COLOUR. Everything here is a token or the navy's own channels in rgba form, which is
   how --shadow-s/m/l are already written. No hex, no gradient, no mask-image, no image request:
   the four icons are borders and pseudo-elements, so the page still makes zero external
   requests and the artifact build has nothing extra to inline.

   NO ANIMATION AT ALL. There is no fade in and no slide, so prefers-reduced-motion is honoured
   by construction; the one transition below (a button's hover tint) is already neutralised by
   the global reduce block near the top of this file. The stage carries scroll-behavior:auto so
   the page's smooth scrolling can never leak into a keyboard pan. */

/* ---- the opener --------------------------------------------------------------------------
   cb-landing.js wraps each of the seven pictures in one of these at load. It is a real button,
   so it is reachable by keyboard and it is what focus returns to on close — the thing CB14's
   markup looks like it does and does not, because its opener is a bare picture with a click
   listener and document.activeElement at click time is therefore the page body. */
.lb-open {
  display:block; width:100%; margin:0; padding:0; border:0; background:none;
  font:inherit; color:inherit; text-align:left; position:relative; cursor:zoom-in;
  -webkit-appearance:none; appearance:none;
}
/* THE FOCUS RING HAS TO BE DRAWN INSIDE THE BOX. Every opener sits in a parent with
   overflow:hidden — .feature__art and .datacard both have it — and fills that parent exactly,
   so the page's outset :focus-visible ring is clipped away to nothing: measured at 3px outside
   a clip rect that starts at the button's own border box. A pseudo-element ring is inside the
   clip. It carries a white inner hairline as well, because these buttons sit on screenshots
   that are white in some places and dark in others and a bare red line can vanish on both. */
.lb-open:focus-visible { outline:none; }
.lb-open:focus-visible::after {
  content:""; position:absolute; top:0; right:0; bottom:0; left:0; pointer-events:none;
  border:3px solid var(--cb-red);
  box-shadow:inset 0 0 0 2px var(--cb-white);
}
@media (forced-colors: active) {
  .lb-open:focus-visible::after { border-color:Highlight; }
}

/* THE AFFORDANCE IS THE CURSOR (2026-09-11). There was a navy disc with a white magnifier in
   the corner of every picture; it was a second magnifier drawn over the product, and the
   product is what the picture is for. .lb-open carries cursor:zoom-in, which says the same
   thing and covers the whole picture instead of one corner of it. */

/* ---- the overlay --------------------------------------------------------------------------
   z-index 300 clears everything the page already stacks: the why bar at 70, the sticky header
   at 100, the skip link at 200. No backdrop blur — the ground is 94% opaque, so a blur behind
   it is a full-viewport composite for an effect nobody can see. */
.lb {
  position:fixed; top:0; right:0; bottom:0; left:0; z-index:300;
  background:rgba(11,22,38,.94);
  -webkit-tap-highlight-color:rgba(0,0,0,0);
}
.lb__box {
  position:absolute; top:0; right:0; bottom:0; left:0;
  display:flex; flex-direction:column; padding:8px;
}

/* THE STAGE IS THE ONLY SCROLLER. It is a flex container so the picture can be centred with
   auto margins — which, unlike justify-content:center, resolve to zero when the picture is
   bigger than the frame, so a zoomed picture starts at the frame's edge and every part of it
   can be reached. overscroll-behavior stops a pan that hits the end from chaining anywhere. */
.lb__stage {
  flex:1 1 auto; min-width:0; min-height:0;
  display:flex; overflow:auto;
  scroll-behavior:auto;
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
}
/* flex:0 0 auto IS LOAD-BEARING. A flex item defaults to flex-shrink:1, which shrinks a
   natural-size picture straight back to the width of the frame — the zoom control then flips
   its own label and its own cursor and the picture does not move. max-width still clamps the
   fitted state, because a maximum applies after flexing. */
.lb__img {
  flex:0 0 auto; margin:auto; display:block;
  width:auto; height:auto; max-width:100%; max-height:100%;
  background:var(--cb-white);
}
.lb.is-zoomable .lb__img { cursor:zoom-in; }
.lb.is-zoom .lb__img { max-width:none; max-height:none; cursor:zoom-out; }

/* ---- the control bar ----------------------------------------------------------------------
   One row, below the picture, never over it and never inside the scroller. That is what keeps
   a close button on screen when a 1100px picture at 1:1 covers a 390px phone — the case where
   an overlay with only a backdrop-tap and an Escape key leaves a phone reader with no way out.
   Spacing is margin rather than flex gap: gap in flexbox is younger than the oldest browser
   this page is written for, and a collapsed gap would put two 48px circles edge to edge. */
.lb__bar {
  flex:0 0 auto; display:flex; flex-wrap:nowrap; align-items:center;
  min-height:48px; padding:8px 4px 4px;
}
.lb__bar > * + * { margin-left:8px; }
.lb__meta {
  flex:1 1 auto; min-width:0; margin:0; display:flex; align-items:center;
  color:var(--cb-white);
}
.lb__count {
  flex:0 0 auto; margin-right:10px;
  font-family:var(--mono); font-variant-numeric:tabular-nums;
  font-size:14px; font-weight:600; line-height:1.3;
  padding:4px 10px; border-radius:999px;
  background:rgba(255,255,255,.14); color:var(--cb-white);
}
.lb__title {
  flex:1 1 auto; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-size:15px; font-weight:700; line-height:1.3; color:var(--cb-white);
}

.lb__btn {
  flex:0 0 auto; position:relative;
  width:48px; height:48px; padding:0; border:0; border-radius:50%;
  background:var(--cb-white); color:var(--cb-navy); cursor:pointer;
  -webkit-appearance:none; appearance:none;
  transition:background-color .12s;
}
.lb__btn:hover { background:var(--cb-cream-deep); }
/* White, not the page's red: the ring sits in a 3px gap that shows the navy ground behind the
   button, where red measures under 3:1 and white measures over 15:1. */
.lb__btn:focus-visible { outline:3px solid var(--cb-white); outline-offset:3px; }
@media (forced-colors: active) {
  .lb__btn:focus-visible { outline-color:Highlight; }
}

/* ---- the four glyphs, drawn in CSS -------------------------------------------------------- */
.lb__i { position:absolute; left:50%; top:50%; }
.lb__i--prev, .lb__i--next {
  width:12px; height:12px;
  border-left:3px solid currentColor; border-bottom:3px solid currentColor;
}
/* The ink of an L-shape is not centred in its own box; the margin puts the chevron's visual
   centre on the button's centre after the rotation. */
.lb__i--prev { transform:translate(-50%,-50%) rotate(45deg); margin-left:3px; }
.lb__i--next { transform:translate(-50%,-50%) rotate(-135deg); margin-left:-3px; }

.lb__i--close { width:20px; height:20px; transform:translate(-50%,-50%); }
.lb__i--close::before,
.lb__i--close::after {
  content:""; position:absolute; left:0; top:9px; width:20px; height:2px;
  background:currentColor; border-radius:1px;
}
.lb__i--close::before { transform:rotate(45deg); }
.lb__i--close::after  { transform:rotate(-45deg); }

.lb__i--zoom { width:22px; height:22px; transform:translate(-50%,-50%); }
.lb__i--zoom::before {
  content:""; position:absolute; left:0; top:0; width:16px; height:16px;
  border:2px solid currentColor; border-radius:50%;
}
.lb__i--zoom::after {
  content:""; position:absolute; left:14px; top:14px; width:9px; height:2px;
  background:currentColor; border-radius:1px;
  transform:rotate(45deg); transform-origin:0 50%;
}
/* The plus inside the lens. aria-pressed drives the picture as well as the announcement, so
   what is seen and what is spoken cannot drift apart: pressed drops the upright and the plus
   becomes a minus. */
.lb__btn--zoom::before,
.lb__btn--zoom::after {
  content:""; position:absolute; left:50%; top:50%;
  background:currentColor; border-radius:1px;
}
.lb__btn--zoom::before { width:8px; height:2px; margin:-4px 0 0 -7px; }
.lb__btn--zoom::after  { width:2px; height:8px; margin:-7px 0 0 -4px; }
.lb__btn--zoom[aria-pressed="true"]::after { display:none; }

/* A rotated phone and any short window: drop the name from the bar so the row stays one line
   and the picture keeps the height. The counter stays — it is four characters. */
@media (max-height:520px) {
  .lb__box { padding:4px; }
  .lb__bar { padding:4px 2px 2px; }
  .lb__title { display:none; }
}

/* ---- the scroll lock ----------------------------------------------------------------------
   On the root element AND on the page body, never position:fixed on the body: the why bar's
   pad() owns body's bottom padding and rewrites it on load, on resize and again on fonts.ready,
   and a fixed body throws away the scroll position, which would move the knight and land the
   reader somewhere else when the overlay closes.

   The scrollbar's width is handed back as padding on the root so the in-flow content does not
   re-wrap — and the why bar is pinned with it, because it is position:fixed with right:0 and
   its containing block is the viewport, which the padding does not touch. It is the only
   fixed box on the page that stretches; the knight is placed in measured pixels and does not
   move. Touch is locked separately, in the script, because overflow:hidden alone has never
   held on iOS Safari. */
html.lb-locked,
html.lb-locked body { overflow:hidden; }
html.lb-locked .whybar { right:var(--lb-sbw, 0px); }


/* ============================================================================================
   ChessBase for Mac — the Mac look.
   Asked for on 2026-09-08: "make it look more like Apple/Mac". What that means here: the system
   typeface (SF on a Mac or iPhone, Segoe UI on Windows) in place of the campaign's condensed
   poster face; big centred headlines with tight tracking; white and light-grey bands; rounded
   tiles; pill buttons; one near-black band; a drawn MacBook as the hero object. What it does not
   mean: the brand stays. ChessBase red is the one accent and takes every call to action, every
   colour is the CI's or sanctioned in check-ci.js, and there is still no gradient, no external
   request and no picture of Apple's — the laptop is drawn in CSS.
   The CB17 stylesheet above is left as it is; these rules come later and win.
   ============================================================================================ */

/* ----------------------------------------------------------------- tokens ---- */
:root {
  --display:'Roboto',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  --sans:'Roboto',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  --radius:12px; --radius-lg:24px;
  --head-h:56px;
  --chequer:none;
}
body { font-size:17px; line-height:1.47; background-image:none; }
h1,h2 { font-weight:600; letter-spacing:-.015em; line-height:1.07; color:var(--cb-navy); }
h1 { font-size:clamp(44px,7vw,84px); }
h2 { font-size:clamp(34px,4.6vw,56px); }
h3 { font-weight:600; letter-spacing:-.01em; }

/* ----------------------------------------------------------------- header ---- */
/* Apple's bar: thin, translucent, saturated blur. The nav sits centred between the brand and the
   button. The wordmark keeps the condensed face — it is the one piece of the campaign type that is
   a brand signature rather than a style. */
.site-head { background:rgba(255,255,255,.82); border-bottom:1px solid var(--cb-hairline); }
@supports (backdrop-filter:blur(20px)) or (-webkit-backdrop-filter:blur(20px)) {
  .site-head { -webkit-backdrop-filter:saturate(180%) blur(20px); backdrop-filter:saturate(180%) blur(20px); }
}
.brand__mark { width:28px; height:28px; }
.brand__word { font-family:var(--sans); font-weight:700; font-size:20px; letter-spacing:-.01em; color:var(--cb-black); }
.site-nav { margin:0 auto; gap:var(--sp-xl); }
.site-nav a { font-size:15px; font-weight:500; color:var(--cb-black); opacity:.8; }
.site-nav a:hover { color:var(--cb-black); opacity:1; }
.langswitch a,.langswitch span { border-radius:980px; font-weight:600; }

/* ---------------------------------------------------------------- buttons ---- */
.btn { border-radius:980px; font-weight:600; font-size:17px; padding:12px 24px; min-height:48px; border-width:1px; }
.btn--primary { box-shadow:none; }
.btn--big { font-size:19px; padding:14px 30px; min-height:56px; }
.btn--head { font-size:15px; padding:0 18px; min-height:44px; }
/* Apple's "Learn more ›": a plain link with a chevron, next to the one filled button. */
.more { display:inline-flex; align-items:center; min-height:44px; padding:0 4px;
        font-weight:500; font-size:19px; color:var(--cb-red); text-decoration:none; }
.more:hover { text-decoration:underline; text-underline-offset:3px; color:var(--cb-red-dark); }

/* --------------------------------------------------------------- sections ---- */
.sec { padding:clamp(72px,9vw,128px) 0; background:var(--cb-white); }
.sec--grey { background:var(--cb-tint); }
.sec--dark { background:var(--cb-black); color:#DDE2E8; }
.sec--dark h2 { color:var(--cb-white); }
.sec--dark .sub,.sec--dark .fine { color:#AEB8C4; }
.sec__head { text-align:center; max-width:840px; margin:0 auto var(--sp-2xl); }
.sec__head h2 { margin:0 0 var(--sp-md); }
.sub { font-size:clamp(19px,2vw,24px); line-height:1.4; color:var(--cb-grey); max-width:34em; margin:0 auto; }
.fine { font-size:15px; line-height:1.5; color:var(--cb-grey); text-align:center; max-width:60ch; margin:var(--sp-xl) auto 0; }
/* Wide enough for "ChessBase GmbH, Osterbekstrasse 90a, 22083 Hamburg, Germany." to stay on one
   line under the "Questions?" line (review 2026-09-09); on a phone it wraps like any text. */
.fine--wide { max-width:76ch; }
.wrap--form { max-width:720px; }

/* ------------------------------------------------------------------- hero ---- */
/* Centred type, then the product — large, alone, in the middle. The section grows with what it
   holds; no screen-height claim. */
.mhero { padding:clamp(56px,8vw,112px) 0 clamp(56px,8vw,112px); background:var(--cb-white); text-align:center; overflow-x:clip; }
.mhero .wrap--hero { max-width:940px; }
.mhero h1 { margin:0 auto var(--sp-lg); }
.mhero .sub { margin-bottom:var(--sp-xl); }
.cta-row { display:flex; justify-content:center; align-items:center; gap:var(--sp-lg); flex-wrap:wrap; margin:0; }


/* ------------------------------------------------------------- mac window ---- */
/* A macOS window frame drawn in CSS around every screenshot and around the clip. THE THREE WINDOW
   CONTROLS ARE IN macOS's OWN COLOURS, sanctioned in check-ci.js the way CB26's board colours were
   on the CB17 page: they are what the reader's own screen shows, they live only inside this drawn
   chrome, and a 12px dot is never mistaken for a call to action. */
.macwin { border-radius:12px; overflow:hidden; background:var(--cb-white);
          border:1px solid var(--cb-hairline); box-shadow:var(--shadow-m); }
.macwin__bar { display:flex; align-items:center; gap:8px; height:38px; padding:0 14px; flex:none;
               background:var(--cb-tint); border-bottom:1px solid var(--cb-hairline); }
.macwin__dot { width:12px; height:12px; border-radius:50%; flex:none; background:var(--cb-line); }
.macwin__dot:nth-child(1) { background:#FF5F57; }
.macwin__dot:nth-child(2) { background:#FEBC2E; }
.macwin__dot:nth-child(3) { background:#28C840; }
.macwin__title { flex:1; text-align:center; font-family:var(--sans); font-weight:500; font-size:13px;
                 color:var(--cb-grey); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; padding-right:60px; }
.macwin__media, .macwin > img { display:block; width:100%; height:auto; }
video.macwin__media { background:var(--cb-navy); }

/* ------------------------------------------------------------------ tiles ---- */
/* Apple's bento: rounded tiles on the opposite ground, the copy at the top, the window frame
   standing on the tile's bottom edge and clipped by it. Tiles in a row share a height, so the
   frames line up along the bottom whatever the copy above them runs to. */
.tile { background:var(--cb-tint); border-radius:var(--radius-lg); overflow:hidden; }
.sec--grey .tile { background:var(--cb-white); }
.bento { display:grid; grid-template-columns:1fr 1fr; gap:var(--sp-lg); }
/* Even air on all four sides. The first cut stood the window on the tile's bottom edge, Apple
   bento style, and the review asked for space between the picture and the tile's border. */
.feature-tile { display:flex; flex-direction:column; padding:40px; }
.tile__text { margin-bottom:var(--sp-xl); }
.tile__title { font-size:clamp(24px,2.4vw,30px); margin:0 0 var(--sp-sm); color:var(--cb-navy); }
.tile__text p { margin:0; color:var(--cb-grey); font-size:17px; max-width:none; }
.tile__shot { margin-top:auto; display:flex; flex-direction:column; aspect-ratio:16/10;
              border-radius:12px; box-shadow:0 12px 36px rgba(18,35,58,.12); }
.tile__shot > img, .tile__shot > .lb-open { flex:1 1 0; min-height:0; }
/* The pictures are cut to 1.85:1 by tools/build-art.py, so the cover crop here is a few pixels at
   the foot, never a third of the picture. */
.tile__shot img { display:block; width:100%; height:100%; object-fit:cover; object-position:center top; }
.tile__shot .lb-open { display:block; width:100%; border-radius:0; box-shadow:none; }
/* A fifth tile spans the row (2026-09-09, tactics puzzles): copy on the left, the picture on the
   right, so the odd one out reads as the section's finale rather than a leftover. */
.feature-tile--wide { grid-column:1 / -1; display:grid; grid-template-columns:1fr 1.7fr; gap:var(--sp-xl); align-items:center; }
.feature-tile--wide .tile__text { margin-bottom:0; }
.feature-tile--wide .tile__shot { margin-top:0; }
/* A second wide tile, mirrored: picture left, copy right (Databases, 2026-09-11). */
.feature-tile--flip { grid-template-columns:1.7fr 1fr; }
.feature-tile--flip .tile__shot { order:-1; }
/* A CROP of a capture — a window corner, not the whole window — gets its own rounded frame and the
   section's soft shadow, like the clip; the whole-window tiles carry the window's own shadow. */
.tile__shot--crop img, .tile__shot--crop .lb-open img { border-radius:12px; box-shadow:0 12px 36px rgba(18,35,58,.12); }
@media (max-width:760px) {
  .bento { grid-template-columns:1fr; }
  .feature-tile { padding:24px; }
  .feature-tile--wide { display:flex; flex-direction:column; }
  .feature-tile--wide .tile__text { margin-bottom:var(--sp-xl); }
  .feature-tile--flip .tile__shot { order:0; }        /* stacked: text first, like every tile */
}

/* -------------------------------------------------------- what happens next ---- */
/* The "Hello Mac." greeting (icon and bubble, from the light poster) opens this band, above the
   three steps — moved here from the sign-up section on 2026-09-09; a booth photograph that stood
   here for an hour was taken out again. The bubble stays white with its black outline, a sticker
   on the dark ground. */
.sec--dark .hello { margin-bottom:var(--sp-2xl); }
/* Three steps from the booth posters, in time order — booth, the list, release — as quiet cards
   on the dark ground. An ordered list, because the order is the information. */
.facts { display:grid; grid-template-columns:repeat(3,1fr); gap:var(--sp-lg); list-style:none; margin:0; padding:0; }
.fact { margin:0; padding:var(--sp-xl); border-radius:var(--radius-lg);
        background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.14); }
.fact__when { font-size:15px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
              color:var(--poster-cyan); margin:0 0 var(--sp-sm); }
.fact h3 { color:var(--cb-white); font-size:24px; margin:0 0 var(--sp-sm); }
.fact p { color:#C6CEDA; font-size:17px; margin:0; max-width:none; }
@media (max-width:900px) { .facts { grid-template-columns:1fr; } }

/* ------------------------------------------------------------------- form ---- */
.formcard { border-radius:var(--radius-lg); padding:40px; border:0; box-shadow:0 20px 60px rgba(18,35,58,.08); }
.field > label { font-weight:600; font-size:15px; margin-bottom:8px; }
.field input[type="text"],.field input[type="email"],.field select {
  border-radius:var(--radius); border-color:var(--cb-line); font-size:17px; padding:14px 16px; min-height:52px; }
.checkline input[type="checkbox"] { border-radius:6px; }
.formcard .btn { width:100%; }
#form-wrap h2 { font-size:28px; letter-spacing:-.01em; }
.formstatus { border-radius:var(--radius); }
.field--check .checkline { margin:0; }
.field--check .field__error { margin-top:2px; }
.field--check.is-invalid .checkline span { color:#7A1A12; }
@media (max-width:620px) { .formcard { padding:28px 20px; } }

/* -------------------------------------------------------- native windows ---- */
/* THE REAL MAC APP, 2026-09-09. The captures are whole macOS windows — title bar, traffic lights,
   rounded corners and the system's own shadow, on a transparent ground — so the drawn window frame
   would be a second title bar on top of a real one. These rules take the frame away and let the
   picture keep its own shape. */
.tile__shot--native { aspect-ratio:auto; border:0; border-radius:0; box-shadow:none; background:transparent;
                      overflow:visible; display:block; }
.tile__shot--native img, .tile__shot--native .lb-open img { height:auto; object-fit:fill; }
.tile__shot--native .lb-open { flex:none; }
/* A screen recording in a tile (tactics, 2026-09-09). A video cannot carry the transparent
   corners the stills have, so the window shape is given back to it here: the corner radius and
   the shadow of a macOS window. */
.tile__shot--video video { display:block; width:100%; height:auto; border-radius:12px; box-shadow:var(--shadow-m); }

/* ------------------------------------------------------------- the chapters ---- */
/* A SCREEN EACH (2026-09-11). The features were a 2x2 grid of stills, then six rows with a clip in
   each, and rows meant three or four clips on screen at once — busy to look at and four decoders
   running for no reason. Each feature now gets a chapter the height of the window: a heading, one
   sentence, and the recording, centred, with room around it. A reader meets one thing at a time,
   and cb-landing.js only ever plays the one being looked at.

   The clip is sized by BOTH edges — max-width for narrow windows, max-height so a tall window
   cannot blow it up past what is comfortable — with width and height left auto so the browser
   keeps the recording's own shape from the width/height attributes. No aspect-ratio to maintain
   per clip, and no letterboxing. */
.chapter { min-height:100svh; display:grid; align-content:center; justify-items:center;
           text-align:center; gap:clamp(24px,4vh,48px); padding:clamp(56px,9vh,104px) 0; }
.chapter + .chapter { border-top:1px solid var(--cb-hairline); }
/* The heading gets a wider column than the sentence under it. 44ch is measured in the PARAGRAPH's
   type, about 420px, and the heading is set at 44px — "Für den Mac gemacht." alone wants 440 and was
   wrapping to a third line while the English, whose words are shorter, sat on two. The sentence
   keeps its own narrow measure below. */
.chapter__text { max-width:min(100%, 640px); }
.chapter__title { font-family:var(--display); font-weight:700; letter-spacing:-.015em;
                  font-size:clamp(28px,3.4vw,44px); line-height:1.1; color:var(--cb-navy);
                  margin:0 0 var(--sp-md); text-wrap:balance; }
.chapter__text p { margin:0 auto; max-width:44ch; font-size:clamp(17px,1.5vw,20px);
                   line-height:1.55; color:var(--cb-grey); text-wrap:pretty; }
.chapter__media { margin:0; width:100%; display:flex; justify-content:center; }
/* 52vh, not more. A chapter is one screen, and the heading, the sentence, the gap and the padding
   have to fit on it too — at 62vh the clip ran off the bottom of a 900px window, which is the most
   ordinary window there is. */
.chapter__media video { display:block; width:auto; height:auto; margin-inline:auto;
                        max-width:100%; max-height:min(52vh, 600px); }
/* AND CENTRED INSIDE WHATEVER WRAPS IT. The lightbox puts every picture in a button of its own, and
   .tile__shot .lb-open is width:100% — so the button filled the row while the clip, sized by its
   own height limit, sat flush against the button's left edge with a third of the row empty beside
   it. Measured at 1384px: a 1132px row with an 816px clip starting at 0. */
.chapter__media .lb-open { display:flex; justify-content:center; }

/* The reveal. cb-landing.js puts .is-off on the chapters that start below the fold — never on one
   already painted, which would flash — and takes it off as each comes up. No script, no .is-off,
   and the chapters simply stand there. */
.chapter { transition:opacity .7s ease, transform .7s ease; }
.chapter.is-off { opacity:0; transform:translateY(26px); }
@media (prefers-reduced-motion: reduce) {
  .chapter { transition:none; }
  .chapter.is-off { opacity:1; transform:none; }
}
/* A short window has no screenful to give: fall back to room instead. */
@media (max-height:760px) {
  .chapter { min-height:0; padding:var(--sp-xl) 0; }
  .chapter__media video { max-height:none; }
}
/* A PHONE DOES NOT GET A SCREEN PER CHAPTER. On a desktop a full screen each is what keeps one clip
   in view at a time; on a phone the clip is small and the screen tall, so the same rule leaves a
   field of white above and below every one of them and turns the section into a long walk. Room,
   then, not a screenful — and the script no longer depends on the spacing for its "one at a time":
   it plays whichever clip is showing MOST, however close together they sit. */
@media (max-width:860px) {
  .chapter { min-height:0; padding:clamp(32px,7vw,56px) 0; gap:var(--sp-md); }
  .chapter.is-off { transform:translateY(16px); }
  .chapter__media video { max-height:min(52vh, 460px); }
}
/* Screenshots from the current development version; a line of small print says so. */


/* ----------------------------------------------------------------- footer ---- */
/* Apple's footer is quiet: small grey type on the light-grey ground, dark links. */
.site-foot { background:var(--cb-tint); color:var(--cb-grey); border-top:1px solid var(--cb-hairline);
             padding:var(--sp-xl) 0; font-size:15px; }
.site-foot a { color:var(--cb-black); }
.site-foot__brand img { width:32px; height:32px; }
.site-foot__tag { font-family:var(--sans); font-weight:500; font-size:15px; letter-spacing:0; text-transform:none; color:var(--cb-grey); }
.site-foot__brand:hover .site-foot__tag { color:var(--cb-black); }
.site-foot__links { font-weight:500; }
.site-foot__links a:hover { color:var(--cb-red); }
.site-foot__base { border-top:1px solid var(--cb-hairline); }
.site-foot .small { color:var(--cb-grey); }
/* Two targets that shrank with the smaller type and the smaller footer mark. 44px is the floor
   check-page.js holds every control to. */
.site-foot__brand { min-height:44px; }
.cb-skip { display:inline-flex; align-items:center; min-height:44px; }

/* ============================================================================================
   The booth campaign — Pascal's "CB on Mac" artwork (2026-09-03), brought onto the page on
   2026-09-09. The dark poster (#4) becomes the hero, so the page a visitor opens from the
   poster's QR code is the poster, continued: navy-to-black ground, the giant red CHESSBASE,
   "Finally on Mac." in cyan Merriweather, the release month in thin capitals. The light poster
   (#1) lends its app icon and "Hello Mac." bubble to the sign-up section.
   The ground and the wordmark are the artwork's own files (tools/build-poster-art.py); the type
   is set live so the German page can say "Endlich auf dem Mac.".
   ============================================================================================ */

:root { --poster-cyan:#00A6DD; }

/* ----------------------------------------------------------------- header ---- */
/* The bar stays LIGHT over the dark hero (review 2026-09-09) — the translucent white bar with the
   black wordmark defined in the Mac-look section above. A dark variant lived here for a day. */

/* ------------------------------------------------------------- the poster ---- */
.poster { background:var(--cb-black) url('art/poster-bg.svg') center / cover no-repeat; color:var(--cb-white); text-align:left; }
.poster .wrap--hero { max-width:var(--maxw); }
/* max-width:none, because the base rule caps every <p> at 64ch and that held the wordmark to 580px
   on a desktop. On the poster it spans nine tenths of the sheet. */
.poster__word { margin:0 0 var(--sp-lg); max-width:none; }
.poster__word img { display:block; width:min(100%, 1000px); height:auto; }
.poster__tag { font-family:var(--display); font-weight:700; letter-spacing:-.015em; line-height:1.05;
               color:var(--poster-cyan); font-size:clamp(34px,6.4vw,72px); margin:0 0 var(--sp-sm); text-wrap:balance; }
.poster__date { font-family:var(--sans); font-weight:300; font-size:clamp(15px,1.6vw,20px); letter-spacing:.14em;
                text-transform:uppercase; color:#DDE2E8; margin:0 0 var(--sp-lg); }
.poster .sub { font-family:var(--sans); color:#DDE2E8; margin:0 0 var(--sp-xl); max-width:38em; font-size:clamp(18px,1.8vw,22px); }
.poster .cta-row { justify-content:flex-start; }
.more--light,.more--light:hover { color:var(--cb-white); }
/* Mobile & tablet hero order: brand wordmark, then the Mac, then the pitch. display:contents lifts
   the copy's children into the flex column so the Mac can sit between the wordmark and the tagline.
   The wide (>=1100px) grid below turns this off and lays the copy out as one right-hand column. */
.poster__grid { display:flex; flex-direction:column; }
.poster__copy { display:contents; }
.poster__word { order:1; }
.poster__art  { order:2; margin:0 0 var(--sp-lg); }
.poster__tag  { order:3; }
.poster__date { order:4; }
.poster .sub  { order:5; }
.poster .cta-row { order:6; }
.poster .macbook3d { margin-top:0; }
/* The German tagline ("Endlich auf dem Mac.") runs longer than the English one; centre it on the
   narrow stacked hero (wide screens keep it left-aligned in the right-hand column). */
@media (max-width:1099px) { html[lang="de"] .poster__tag { text-align:center; } }
@media (max-width:760px) {
  .poster__tag { font-size:clamp(30px,9vw,44px); }
}
/* WIDE SCREENS: two columns, the Mac on the left and the poster copy on the right, and the band
   fills the screen (review 2026-09-09: "so that the hero fills out the screen completely"). Below
   1100px it stacks, copy first, as before — that is the phone layout, which was right. */
@media (min-width:1100px) {
  .poster { min-height:calc(100vh  - var(--head-h)); min-height:calc(100dvh - var(--head-h));
            display:flex; align-items:center; padding:clamp(32px,5vh,72px) 0; }
  /* Wider than the page's 1180px band, so on a 1920px screen the Mac and the copy fill the width
     rather than sitting in the middle third. */
  .poster > .wrap { width:100%; max-width:1520px; }
  .poster__grid { display:grid; grid-template-columns:1.08fr .92fr; column-gap:clamp(40px,4.5vw,88px); align-items:center; }
  .poster__copy { display:block; }
  .poster__art { order:-1; margin:0; }
  .poster .mac { margin:0; width:100%; }
  .poster__word img { width:100%; }
  .poster__tag { font-size:clamp(36px,3.8vw,60px); }
  .poster__date { font-size:clamp(15px,1.2vw,18px); }
  .poster .sub { font-size:clamp(17px,1.35vw,21px); }
}

/* The other dark band takes the same ground, so the page has one dark. */
.sec--dark { background:var(--cb-black) url('art/poster-bg.svg') center / cover no-repeat; }

/* --------------------------------------------------------------- hello mac ---- */
/* From the light poster (#1): the app icon greeting the Mac. Above the sign-up heading. */
.hello { display:flex; align-items:center; justify-content:center; gap:var(--sp-lg); margin:0 auto var(--sp-xl); }
.hello__icon { width:clamp(76px,10vw,116px); height:auto; display:block; flex:none; }
.hello__bubble { position:relative; margin:0; font-size:clamp(26px,4vw,40px); line-height:1.1;
                 color:var(--cb-black); background:var(--cb-white); border:2px solid var(--cb-black);
                 border-radius:22px; padding:14px 26px; white-space:nowrap; }
.hello__bubble::before,.hello__bubble::after { content:""; position:absolute; top:50%; transform:translateY(-50%);
                 border:12px solid transparent; border-left:0; border-right-color:var(--cb-black); left:-24px; }
.hello__bubble::after { left:-20px; border-right-color:var(--cb-white); }

/* ------------------------------------------------------------ the macbook ---- */
/* A RENDER, NOT A MOCKUP (2026-09-11). The laptop used to be a drawn frame with the app laid
   over a hole in it, held there by four percentages that had to be measured again whenever
   the mockup changed. It is one picture now, out of tools/build-hero-3d.py: a 3D model with
   the app already on its glass. Nothing to align, so there is almost nothing to style. */
.macbook3d { display:block; width:100%; position:relative; }
.macbook3d__img { display:block; width:100%; height:auto; }
/* THE APP, PLAYING ON THE SCREEN OF THE RENDER (2026-09-11). It can be a plain absolutely
   placed box because the render is shot face-on with a level lens: the display is parallel to
   the film, so it projects to a true axis-aligned rectangle. No transform, no skew.
   It starts invisible and is shown only once it is playing; underneath it, baked into the
   picture, is the clip's own first frame, so nothing jumps when it appears and nothing looks
   broken when it never arrives. */
.macbook3d__screen { position:absolute; z-index:1; display:block; object-fit:fill;
                     background:var(--cb-navy); opacity:0; transition:opacity .3s linear; }
.macbook3d__screen.is-on { opacity:1; }
@media (prefers-reduced-motion: reduce) { .macbook3d__screen { transition:none; } }
/* hero-screen-rect: WRITTEN BY tools/build-hero-3d.py. Do not edit between the
   markers — where the clip sits on the picture is measured ON the picture, not by eye. */
.macbook3d__screen { left:10.117%; top:8.545%; width:79.767%; height:74.642%; }
/* end hero-screen-rect */

/* The lightbox's player (cb-landing.js builds it): the picture's own sizing rules, no white ground
   behind a clip, and the tile's window rounding kept. */
.lb__video { background:transparent; border-radius:12px; }
.lb.is-zoom .lb__video { max-width:100%; max-height:100%; }

/* System requirements (Rainer, 2026-09-10): a compact card row on the light ground. */
.reqs { list-style:none; margin:0; padding:0; display:grid; gap:var(--sp-md);
        grid-template-columns:repeat(auto-fit, minmax(230px, 1fr)); }
.reqs li { background:var(--cb-white); border-radius:14px; padding:var(--sp-lg); box-shadow:var(--shadow-m);
           font-size:17px; line-height:1.45; color:var(--cb-black); }
.reqs li strong { display:block; font-weight:700; color:var(--cb-navy); margin-bottom:var(--sp-xs); }
