/* Second layout of the same page, reached at /?v=2 (or /v2/). Everything here is authored against
   `html.v2` so the two versions share one document and cannot drift apart. */

/* The version switch. Not part of the page — part of looking at it. */
.v-switch {
  position: fixed;
  right: 14px;
  bottom: 14px;
  z-index: 9999;
  padding: 6px 12px;
  border: 1px solid rgba(26, 0, 90, .18);
  border-radius: 999px;
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  font: 500 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .04em;
  color: #1a005a;
  text-decoration: none;
  opacity: .5;
  transition: opacity .2s ease;
}
.v-switch:hover { opacity: 1; }

/* The supporting Yaniverse trial note is not part of the compact case copy. */
html.v2 body.ab-on #cases .case-row:has([data-pill-stack="yaniverse"]) .points {
  display: none;
}

/* ---------- the module ----------
   The second version measures the page in screens rather than in the 400x320 frame the artwork was
   drawn in. One module is the screen's own height: a band is exactly what the reader can see, so a
   case is read as a place they are inside of rather than as a card passing by, and the next seam
   arrives precisely at the edge of the view instead of showing a strip of the following band under
   it. With the 24px of paper between rows that seam is a full stop rather than a join. Every band on
   the page is set to the same module, so
   the page scrolls in steps of one height instead of in whatever height each band's contents
   happened to add up to. The unit is `lvh`: the screen measured as if the mobile browser's
   retractable chrome were already gone. `svh` measures it with the chrome showing, which costs the
   band the height of the address bar on every phone and — because the module is then a live value
   that the browser recomputes as the bar hides and returns mid-scroll — moves every seam below the
   fold while the reader is scrolling, so scrolling back up does not land where it left. `dvh` is
   worse still, since it tracks the chrome continuously. `lvh` is fixed for the life of the page,
   which is the whole point: the geometry a scroll position was taken against is the geometry it is
   restored against. The price is that with the bar showing, one bar's height of the band sits under
   it — harmless, because a band is edge-to-edge artwork rather than something to read to the last
   pixel. Plain `vh` above it is what browsers without `lvh` fall back to, and on a desktop the two
   are the same number. The module is a floor, never a ceiling: a band whose contents outgrow it
   keeps growing, and its contents stay centred in whatever height it ends up with. */
html.v2 { --v2-module: 100vh; }
@supports (height: 1lvh) {
  html.v2 { --v2-module: 100lvh; }
}

/* The paragraph above calls the price of `lvh` harmless — a bar's height of the band sitting under
   the address bar, on artwork nobody reads to the last pixel. On a phone it is not harmless, and
   this is the correction. The band is also the snap target, and a snap target taller than the
   scrollport cannot be snapped to: the spec lets the reader scroll freely inside an oversized
   area, which is the only way a section taller than the screen could ever be read. Measured on
   this page at 390x844: `.skillset` is 969 tall and `.experience` 2416, and every scroll that
   lands inside either comes to rest at an arbitrary offset while every 844-tall band lands exactly
   on its own top. With the address bar showing, the scrollport is `svh` while the band is `lvh`,
   so every band is oversized by the height of the bar and the whole ribbon scrolls freely — the
   discrete scroll does not degrade, it disappears, and it comes back the moment the bar retracts
   and the two heights agree again. That is the "it was stepping and then it stopped" report.
   So on the phone the band is sized with `svh`: never taller than the scrollport, so always
   snappable. The cost is the one `lvh` was chosen to avoid — when the bar retracts, a strip of the
   next band shows under the current one — and between a visible seam and a scroll that stops being
   discrete, the seam is the cheaper loss. The wide layout keeps `lvh`: it has no retractable
   chrome, the two units are the same number there, and nothing about scroll restoration changes. */
@supports (height: 1svh) {
  @media (max-width: 720px) {
    html.v2 { --v2-module: 100svh; }
  }
}

/* ---------- selected cases: two halves of the page, stitched into one column ----------
   The first version sets the cases as a centred column of rows: a 400 px plaque that bleeds out to
   its own viewport edge, a caption beside it, and 140 px of paper between rows. The second version
   takes the band seriously — each case owns exactly one half of the page, and the halves meet with
   no paper between them, so the section reads as a single ribbon that changes sides at every row
   instead of a list of separate plates. */

/* The section's name stays in the accessibility tree: `#cases` points at this heading through
   `aria-labelledby`, and `display: none` would take the section's name away with it. Clipping it
   out of the flow leaves the name in place and leaves no gap where the heading used to sit. */
html.v2 #selected-cases-title {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The rows have to reach both edges of the page, so the section gives up the centred 1120 px
   container and its gutters; the caption column takes the gutter back on itself below. The top
   padding goes with them: the first band now starts where the hero band ends, edge on edge. */
html.v2 body.ab-on #cases {
  display: block;
  max-width: none;
  padding-top: 0;
  padding-inline: 0;
}

/* Two bands of colour that touch along a straight line read as one band that changed its mind, and
   the reader loses the count of cases. Forty-eight pixels of paper between rows is a seam rather
   than the 140 px of air the first version puts between its plates: enough to say where one case
   ends, too little to let the ribbon fall apart into a list again. The one seam that stays closed is
   the topmost one — the hero's band ends exactly where the first case's band starts, and that pair
   is meant to read as a single colour carried over the fold. It is not a row gap at all (it falls
   between `#top` and `#cases`), so it is untouched by the line below and needs no exception. */
/* 48px read as a hole once the bands went full-bleed: two saturated edge-to-edge colours with a
   strip of paper between them make the gap the loudest thing on the seam. 24px still stops the
   ribbon from reading as one continuous run without announcing itself. */
html.v2 body.ab-on #cases .rows { row-gap: 24px; }

/* The plaque is the bubble's own box now. In the first version it is pushed past the bubble by the
   measured distance to the viewport edge (--ab-left/--ab-right) and by 30 px of overshoot top and
   bottom (--ab-grow); here the bubble itself is half the page and the full height of its row, so the
   plaque has nowhere left to go. Zeroing --ab-grow keeps the parts that read it — the video, the
   gear — in step with the plaque. */
/* The radius only ever applies to the two inner corners — the ones facing the caption; the outer
   pair sits against the edge of the page and stays square. Four pixels is enough to keep the band
   from reading as raw bleed and to answer the rest of the page's geometry, and small enough that
   the seam with the band above and below still closes. */
html.v2 body.ab-on #cases .ab-wide { --ab-grow: 0px; --ab-r: 4px; }

/* One corner is the exception: the top inner corner of the first case. The hero's own band ends
   exactly there, so those two bands share an edge rather than meet at a gap, and a radius would
   open a notch of paper in the middle of a colour that is supposed to be continuous. The other
   three corners of that plaque keep the radius. The capsule and the phone are clipped to the same
   shape as the plaque, so their rounding has to be taken off that corner too, or the artwork would
   curve away from a square edge. */
html.v2 body.ab-on #cases .case-row:first-child .ab-wide[data-ab-side="left"] .ab-plaque {
  border-top-right-radius: 0;
}
html.v2 body.ab-on #cases .case-row:first-child .ab-wide[data-ab-side="left"] .capsule {
  clip-path: inset(0 0 0 -100vw round 0 0 calc(var(--ab-r) / var(--ab-scale)) 0);
}
html.v2 body.ab-on #cases .case-row:first-child .ab-wide[data-ab-side="left"] .phone-mask {
  clip-path: inset(var(--ab-phone-reach) round 0 0 calc(var(--ab-r) / var(--ab-scale)) 0);
}
html.v2 body.ab-on #cases .ab-wide .ab-plaque { inset: 0; }

@media (min-width: 721px) {
  /* A case is a screen of its own: the row is one module tall, and the plaque fills it. The height
     is a minimum, so a caption longer than the module pushes the row open instead of spilling out
     of the colour; the row centres both of its halves, so the caption sits in the middle of a band
     that grew. */
  html.v2 body.ab-on #cases .case-row {
    gap: 0;
    --v2-art-h: var(--bubble-h);
    --v2-row-h: max(var(--v2-module), var(--v2-art-h));
    min-height: var(--v2-row-h);
  }
  /* Half of the row, and the row is the full width of the page — so the plaque is half the page
     measured against the same box the browser lays the page out in. A `50vw` would have been half
     the viewport including a classic scrollbar, which is a different number on the machines that
     have one. The bubble stretches to the row's height so the plaque covers the row completely and
     meets its neighbours along the whole seam. */
  html.v2 body.ab-on #cases .case-row > .bubble {
    flex: 0 0 50%;
    width: 50%;
    max-width: none;
    height: auto;
    align-self: stretch;
    /* The page's reveal raises every item from 22 px below its place. On a column of separate plates
       that reads as the plate settling; in a ribbon it is a 22 px white slit that opens along the
       seam and closes again, once per row, as the reader scrolls. The reveal keeps its fade — only
       the travel goes, through the same `--reveal-y` the sticky header already zeroes for itself. */
    --reveal-y: 0px;
  }

  /* The artwork keeps the 400x320 frame it was drawn in: every layer inside .stack is placed in
     percentages of that frame, so letting it follow the widened bubble would stretch the drawing.
     It stays pinned to the inner edge of the band, the edge --ab-scale grows it away from, and
     centred in a band that is now as tall as the row. */
  html.v2 body.ab-on #cases .ab-wide > .stack {
    width: var(--bubble-w);
    height: var(--bubble-h);
    top: calc(50% - var(--bubble-h) / 2);
    bottom: auto;
  }
  html.v2 body.ab-on #cases .ab-wide[data-ab-side="left"] > .stack { right: 0; left: auto; }
  html.v2 body.ab-on #cases .ab-wide[data-ab-side="right"] > .stack { left: 0; right: auto; }

  /* The row has no gap left to keep the text off the plaque, so the caption carries its own margins:
     the section's gutter on the screen side, a case-row gap's worth on the plaque side. */
  html.v2 body.ab-on #cases .case-row > .copy { padding: 0 var(--gutter) 0 var(--space-12); }
  html.v2 body.ab-on #cases .case-row.flip > .copy { padding: 0 var(--space-12) 0 var(--gutter); }

  /* The UULA diagram is sized from the width it is given and takes its height from the 3458x2015 of
     its own file. Half a page is wide enough to let it hit its 660 px ceiling, and at that width it
     stands 385 px tall — taller than the row on a screen too short for the module to cover it, and
     in the first version the band was never that wide, so the drawing always fit. It is a picture,
     so it cannot be squashed back into the row; the width comes down instead, by the height the row
     will actually give it, read through the file's own proportions. */
  html.v2 body.ab-on #cases .ab-wide > .ab-diagram { max-width: calc(var(--v2-row-h) * 3458 / 2015); }

  /* The two ends of the ribbon are held to the same module as the bands between them: the hero
     because its colour band is the first thing the seam is measured against, the contact block
     because a page that steps by one screen should not end on a half one. Both are grids, so the
     module is given to them as a floor and their rows are centred in it — the hero's own band is
     positioned against the section box and follows the new height by itself. */
  html.v2 body.ab-on #top,
  html.v2 body.ab-on #contact {
    min-height: var(--v2-module);
    align-content: center;
  }
}

/* Above 900 px the artwork is scaled up by --ab-scale (1.1875 in ascii-blocks.css). The module is
   taller than that on any ordinary screen, but on a short one — a phone held sideways, a window
   dragged down to a strip — the module is less than the drawing, and the drawing would hang
   out of its own colour. The row takes whichever of the two is larger. */
@media (min-width: 900px) {
  html.v2 body.ab-on #cases .case-row { --v2-art-h: calc(var(--bubble-h) * 1.1875); }
}

/* The 721-899 band is the tightest place the metrics row ever has to fit. `ascii-blocks.css` sizes
   it as `repeat(3, auto)` with `justify-content: start`, so uula-ds and yotaphone — the two cases
   with only two metrics — collapse their empty third track to zero and split the whole row between
   two columns; `.value` is `white-space: nowrap`, and `.metric` inherits `min-width: 0`, which lets
   a too-long value ink-overflow its column without ever showing up in any ancestor's scrollWidth.
   Measured at 721 px with a Range over the glyph run: "45% → 75%+" has 10.1 px of headroom, three
   times thinner than any other value in the ribbon, and a space-reserving scrollbar takes it to
   6.3 px. Headless Chrome never actually tipped it negative, so this is insurance on the thinnest
   margin rather than a reproduction of a measured overflow. Closing the gap to 12 px in this band
   alone buys the value about 8 px back and leaves everything from 900 px up byte-identical — that
   is where the wide design is settled, and it must not move. */
@media (min-width: 721px) and (max-width: 899.98px) {
  html.v2 body.ab-on #cases .case-row .copy .metrics { column-gap: 12px; }
}

/* ---------- the discrete scroll: phone only ----------
   A band is a screen on a phone, where a case owns the whole viewport and stepping through the
   ribbon one band at a time is the gesture the layout is built for. On a desktop it is not: a
   mouse wheel, a trackpad and a scrollbar all expect the page to go where they put it, and a
   mandatory container takes that away — it drags the view to the nearest band after every small
   scroll, so reading the middle of a tall text section or stopping between two cases becomes a
   fight. So the whole snap system is held to the phone width. This block briefly ran
   unconditionally; on the desktop the result was rejected on sight, and the wide layout is back to
   free scrolling. There is no sticky chrome on this page (the site header is not part of this
   document), so no `scroll-padding-top` is needed to keep a band's top edge clear.

   Inside the query: `mandatory` goes on the scrolling element itself, and every direct child of
   `body` is a target, not only the cases, so the container never skips a section by finding a
   point nearer than it between two targets. Each case is its own step as well, otherwise a flick
   through the middle of the ribbon lands wherever momentum ran out instead of on the next band.

   `stop: always` on every target, and this is the line the phone layout lives or dies by. It was
   `normal` for two days, from the attempt to run the snap on the desktop as well, and `normal` is
   what "the scroll stopped being discrete after the second block" means: a mandatory container
   still lands on a target, but `normal` lets one flick carry through as many targets as its
   momentum reaches, so the first short flick steps one band and every flick after it — by then
   the gesture has momentum — skips three or four and lands far down the page. The behaviour looks
   like snapping that switched itself off partway down the ribbon; nothing switched off, the
   momentum simply grew. `always` makes a target a wall: one gesture, one band, however hard it is
   thrown. That costs the long flick through the whole ribbon, which is the trade the layout
   wants — a case is a screen here, and screens are turned one at a time. */
@media (max-width: 720px) {
  html.v2 { scroll-snap-type: y mandatory; }

  /* Раскрытый кейс снимает привязку скролла. Почему: при `mandatory` скролл всегда садится на
     ближайшую полосу, а панель кейса — длинный блок между двумя полосами: каждое движение внутри
     неё выбрасывало бы читателя назад на карточку. Класс ставит и снимает аккордеон
     (`components/SelectedCases.astro`); вне мобильной раскладки панели нет, и класса тоже. */
  html.v2.case-open { scroll-snap-type: none; }
  html.v2 body.ab-on > section,
  html.v2 body.ab-on > footer {
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }

  html.v2 body.ab-on #cases .case-row,
  html.v2 body.ab-on #cases .case-row.flip {
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }
}

/* ---------- phone: the band is the screen ----------
   Under 720 px a row cannot be two halves of a page, so it becomes one: the colour runs edge to edge
   and floor to ceiling of the screen, and it is the ground for both the artwork and the caption. The
   caption stops standing on paper below the plaque and becomes a light card lying on it, held off the
   band's edges so the colour frames it on all four sides — the shape a phone asks for, and the
   shape that lets one case own the whole screen instead of half of it with a screen of paper under.
   The module is a ceiling here as well as a floor: a band is exactly one screen, the reader takes it
   in one step, and anything that will not fit is made to fit rather than allowed to push the band
   open. */
@media (max-width: 720px) {
  /* The section keeps a 64 px bottom padding from the page's own section rule, and here that is 64 px
     of paper hanging off the end of the ribbon inside the snap area. A snap area taller than the
     screen can be rested in anywhere, so the scroll that leaves the seventh case stops at 5972 —
     the last band pushed 64 px up with a strip of paper under it — instead of landing on a band top.
     Measured: with the padding the last two landings read `5972* 6941*`, both off-grid. Zeroing it
     makes the section end exactly where the seventh band ends, and the step out of the ribbon is a
     band top again. The wide layout keeps the padding: it does not snap, and there the 64 px is the
     air before the skillset block. */
  html.v2 body.ab-on #cases { padding-bottom: 0; }

  html.v2 body.ab-on #cases .rows {
    /* The colour of one case ends where the colour of the next begins. On the wide layout that seam
       needs 48 px of paper to be seen at all; here two bands are never on the screen together, so a
       gap would only be a strip of paper arriving between two steps of the scroll. */
    row-gap: 0;

    /* How far the card stands off the band's edge, and how round it is. The inset is the frame of
       colour the card floats in, the radius is the one a phone's own surfaces are drawn with. */
    --v2-card-inset: clamp(12px, 3.6vw, 16px);
    --v2-card-r: 28px;
    --v2-card-pad: clamp(16px, 4.4vw, 20px);

    /* How far the drawing's own box stands off the band's left and right edges here — the same step
       back from the screen edge the wide layout gives the card via --ab-edge-gap, scaled down for a
       screen that has far less width to give away. Read by the artwork's width formulas below. */
    --ab-edge-gap-mobile: clamp(10px, 5vw, 24px);
  }

  /* The band. Height is the module exactly — floor and ceiling — so one case is one screen and one
     screen is one step of the scroll. The column puts the artwork's box on top with all the room the
     caption does not take, and the card at the bottom. `overflow: hidden` is the promise that the
     ceiling holds: artwork drawn past its box is cut at the band's edge instead of running into the
     band below. */
  html.v2 body.ab-on #cases .case-row,
  html.v2 body.ab-on #cases .case-row.flip {
    position: relative;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    height: var(--v2-module);
    overflow: hidden;
    /* scroll-snap-align and scroll-snap-stop for this rule are declared in the snap block above,
       in its own max-width: 720px query, so all of the snapping reads in one place. */
  }

  /* The bubble stays in the flow and takes whatever height the caption leaves it: it is the artwork's
     box now, not the colour's. Its aspect ratio goes — the 350x280 pill the first version keeps is
     what the drawing needs, not what the band needs, and the drawing carries that ratio itself below.
     `container-type: size` makes this box the frame the drawing is fitted into, which is the only way
     CSS can say "as large as fits, without distorting" for a box whose height is decided by its
     sibling. */
  html.v2 body.ab-on #cases .case-row > .bubble,
  html.v2 body.ab-on #cases .case-row.flip > .bubble {
    flex: 1 1 0;
    width: auto;
    max-width: none;
    height: auto;
    min-height: 0;
    margin: 0;
    aspect-ratio: auto;
    container-type: size;
    /* The page's reveal raises every item from 22 px below its place. The band is the screen's own
       background here, so that travel is a 22 px strip of paper opening along the top edge of every
       case as it arrives. The fade stays; only the travel goes, the same way the wide layout takes
       it off its half-page plaque. */
    --reveal-y: 0px;
    /* The band reaches the screen's own corners, so the 4 px the wide layout keeps on the inner pair
       would be a nick in the corner of the screen. The capsule and the phone are clipped to the same
       radius, so zeroing it here squares all three at once. */
    --ab-r: 0px;
  }

  /* The plaque is no longer the bubble's box: it hangs from the bubble's top edge — which is the
     band's top edge, the bubble being the first thing in the column — and runs a full module down,
     under the card and to the bottom of the band. This is what carries the colour and the ASCII
     field behind the caption. It comes after the shared `inset: 0` above and takes its `bottom`
     back to `auto` so the height is the one that counts. */
  html.v2 body.ab-on #cases .ab-wide .ab-plaque {
    top: 0;
    right: 0;
    bottom: auto;
    left: 0;
    height: var(--v2-module);
  }

  /* The drawing keeps the proportions of the frame it was made in and is fitted into the box above
     the card: as wide as the band allows, or as wide as the box's height allows it to be, whichever
     comes first. Centred in that box, so the air left over is shared between its head and its feet
     instead of piling up under it. */
  html.v2 body.ab-on #cases .ab-wide > .stack {
    top: 50%;
    right: auto;
    bottom: auto;
    left: 50%;
    width: min(calc(100cqw - 2 * var(--ab-edge-gap-mobile)), calc(100cqh * 350 / 280));
    height: auto;
    aspect-ratio: 350 / 280;
    translate: -50% -50%;
  }

  /* Fit the complete two-column composition, including its negative left overhang. */
  html.v2 body.ab-on #cases .ab-wide > [data-pill-stack="uula-ds"] {
    width: min(calc(80cqw - 2 * var(--ab-edge-gap-mobile)), calc(80cqh * 350 / 280));
    margin-left: min(8.4cqw, 10.5cqh);
  }
  /* No mobile override of the capsule's clip-path here on purpose, even though uula-ds's sidebar and
     right-block layers are deliberately oversized and bleed past the pill. An earlier override
     (clip-path: none; overflow: visible;) opened the capsule up fully, but the pill's rounded corners
     are still drawn underneath by something else on this element, and with the clip gone that drawing's
     corner strokes showed through as small stray dashes at the top-left, top-right and bottom-right of
     the composition during the phone-width entrance and at rest — confirmed by screenshot A/B with and
     without the override. Leaving the base .ab-wide[data-ab-side="left"] .capsule clip-path (an
     asymmetric pill: open on the left bleed side, rounded on the other three corners) in force removes
     those dashes and clips the sidebar/right-block art cleanly at the pill boundary, matching desktop;
     the stack's own width/margin-left above already reserves room for the left overhang, so nothing is
     lost on that side either. */

  /* Animated artwork stands in for the drawing in three cases and is sized off its own box, which is
     now the whole space above the card rather than a 350x280 pill — at that height its 829x380 would
     be twice the width of the screen. It is fitted the same way the drawing is. */
  html.v2 body.ab-on #cases .ab-wide > .ab-video,
  html.v2 body.ab-on #cases .ab-wide[data-ab-side="left"] > .ab-video,
  html.v2 body.ab-on #cases .ab-wide[data-ab-side="right"] > .ab-video {
    top: 50%;
    right: auto;
    bottom: auto;
    left: 50%;
    width: min(calc(100cqw - 2 * var(--ab-edge-gap-mobile)), calc(100cqh * 829 / 380));
    height: auto;
    translate: -50% -50%;
  }

  /* UULA's diagram keeps the wide layout's `left`/`right` edge-pin and the width it reads off
     `--ab-left`/`--ab-right` — the phone-mask's measured distance to the true viewport edge, a
     distance the wide plaque used to close by bleeding past the bubble. On this band the plaque is
     already the full viewport width, so that distance measures near zero once layout has settled, but
     it is read from `bubble.getBoundingClientRect()` on load and on resize (ascii-blocks.js), and a
     read that lands mid-layout — before the column has taken its mobile width — still sets it, and the
     diagram jumps to the corrected size on the next measure. Centring it here, the same way the
     drawing and the video above are centred, bounds the damage: a stale measurement can only make it
     hit the cqw/cqh ceiling early, not stretch it off an edge or slide it sideways. Its own 3458x2015
     is the file the wide rule above already reads this ratio from. */
  html.v2 body.ab-on #cases .ab-wide > .ab-diagram {
    top: 50%;
    right: auto;
    bottom: auto;
    left: 50%;
    width: min(calc(100cqw - 2 * var(--ab-edge-gap-mobile)), calc(100cqh * 3458 / 2015));
    translate: -50% -50%;
  }

  /* Yaniverse's wide scene fades its own right edge into the band — a wide-layout seam, meant for the
     plaque's bleed past the bubble to a colour that is the band showing through. On this band there is
     no seam to dissolve into: the scene sits centred in its own box same as every other case's artwork,
     so the fade instead cuts a soft diagonal bite out of the drawing at roughly the 4/5 mark of its
     width, on a scene that is not pinned to a "right" edge of anything any more. Confirmed live at
     390 px: the masked layer's `<img>` carries this gradient regardless of width. */
  html.v2 body.ab-on #cases .ab-wide[data-ab-side="right"] [data-pill-stack="yaniverse"] .capsule .layer:not(.ab-art-wide) img,
  html.v2 body.ab-on #cases .ab-wide[data-ab-side="left"] [data-pill-stack="yaniverse"] .capsule img {
    -webkit-mask-image: none;
    mask-image: none;
  }

  /* The caption, as a card. It is the last thing in the column, so it sits on the band's floor with
     the inset as its only margin; the colour shows around all four of its sides. The fill is the
     page's own paper, so the card reads as a piece of the page laid on the plaque rather than as a
     white box introduced here. The gutter the wide layout gives it goes: the card's padding is the
     margin now, and a gutter on top of it would leave the text floating in the middle of the card. */
  html.v2 body.ab-on #cases .case-row > .copy,
  html.v2 body.ab-on #cases .case-row.flip > .copy {
    flex: 0 0 auto;
    min-height: 0;
    margin: 0 var(--v2-card-inset) var(--v2-card-inset);
    padding: var(--v2-card-pad);
    border-radius: var(--v2-card-r);
    background: var(--paper, #f8f6fc);
  }

  /* Первые два кейса (Mercuryo, Yaniverse) без карточки: текст лежит прямо на шейдерном фоне, а
     подложка не заменяется ничем (ни тенью, ни стеклом) — просьба была убрать плашку. Колонка текста
     остаётся на прежних полях: внешний отступ карточки переехал в padding (inset + pad слева и снизу,
     inset + pad справа), иначе текст прыгнул бы к краю экрана. */
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] > .copy,
  html.v2 body.ab-on #cases .case-row[data-case="schoolbook-weekly-tales"] > .copy {
    margin: 0;
    padding: var(--v2-card-pad) calc(var(--v2-card-inset) + var(--v2-card-pad)) calc(var(--v2-card-inset) + var(--v2-card-pad));
    border-radius: 0;
    background: none;
  }

  /* Mercuryo: фон фиолетово-чёрный, поэтому весь текст белый. Акцент --accent (#1A00E0) и --muted
     (#4a3f5e) рассчитаны на светлую бумагу и на тёмном фоне не читаются, поэтому и они белые.
     `!important` нужен: ascii-blocks.css красит весь текст главной в #000 через `!important` с длинной
     цепочкой :not(); перебить его можно только таким же флагом и #cases в селекторе (id весит больше
     любого числа классов). Yaniverse не трогаем: на жёлтом тот же #000 уже читается. */
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] > .copy,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy h3,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy p.company,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy .descriptor,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy .hero-role .label,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy .hero-role .role,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy .metric .value,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy .metric .label,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy .read-more a,
  html.v2 body.ab-on #cases .case-row[data-case="mercuryo-visual-language"] .copy .read-more a * {
    color: #fff !important;
  }

  /* The card has to fit inside the screen it is lying on, and the wide layout's 20 px body was set
     for a column half a page wide. Every step down below is what buys the room: the title and the
     company line come to 18 px, the descriptor to 17 px, the figures to 18/13, and the gaps above
     the figures and the link come down a step each. Measured at 360 px — the narrowest width tested,
     and the one the longest of the seven captions is worst at: the card stands 568 px untrimmed and
     482 px trimmed in a band of 740 px, which is the difference between 163 px left for the drawing
     and 245 px. */
  html.v2 body.ab-on #cases .case-row .copy h3,
  html.v2 body.ab-on #cases .case-row .copy p.company {
    font-size: 18px;
  }
  html.v2 body.ab-on #cases .case-row .copy p.descriptor {
    font-size: 17px;
    line-height: 1.3;
  }
  html.v2 body.ab-on #cases .case-row .copy .metrics {
    margin-top: var(--space-4);
    column-gap: var(--space-4);
    row-gap: var(--space-3);
  }
  html.v2 body.ab-on #cases .case-row .copy .metrics .metric .value { font-size: 18px; }
  html.v2 body.ab-on #cases .case-row .copy .metrics .metric .label { font-size: 13px; }
  html.v2 body.ab-on #cases .case-row .copy .read-more { margin-top: var(--space-3); }

  /* Both ends of the page are held to the module the same way the wide layout holds them, so the
     hero and the contact block are steps of the same size as the seven bands between them. */
  html.v2 body.ab-on #top,
  html.v2 body.ab-on #contact {
    min-height: var(--v2-module);
    align-content: center;
  }
  /* scroll-snap-type and the section/footer scroll-snap-align are declared in the snap block
     above, which carries its own max-width: 720px query so the whole behaviour reads together. */
}

/* ─── Раскрытый кейс: шейдер ряда продолжается в верх панели ───
   Правило (03.10): вводные блоки кейса стоят на анимированном шейдерном фоне ряда, а поле кейса
   начинается ниже — у Mercuryo с блока «UI as…», у Tales of the Yaniverse сразу после «The brief».
   Почему плашка ряда, а не второй шейдер на панели: браузер держит около шестнадцати контекстов
   WebGL на вкладку, главная поднимает девять, и ещё один контекст ради поля, которое уже стоит на
   экране, — лишний риск. Высоту вылета считает скрипт аккордеона и кладёт в --panel-shade.
   `overflow: visible` снимается только с открытого ряда: у остальных потолок ряда держит арт. */
@media (max-width: 720px) {
  html.v2.case-open body.ab-on #cases .case-row[data-open-row] { overflow: visible; }
  /* Высота, а не `bottom`: на мобиле плашка висит от верхней кромки ряда с `bottom: auto` и ростом
     ровно в модуль (правило выше), и нижняя кромка на неё не действует. */
  html.v2.case-open body.ab-on #cases .case-row[data-open-row] .ab-plaque {
    height: calc(var(--v2-module) + var(--panel-shade, 0px));
  }
}
