/**
 * patternpaste/iso-wall: the resting state.
 *
 * This is the finished composition a visitor sees if view.js never loads: the
 * introduction, then a still isometric wall of tiles, every authored tile
 * visible, nothing hidden and nothing waiting to be undone. No opacity: 0, no
 * transform to reverse, no start state. Motion is added on top by view.js and
 * removed again by revert.
 *
 * THE BLOCK STATES NO COLOUR AND NO FONT. Every mark is `currentColor`, mixed
 * down with `color-mix` where a surface is wanted, and the viewport's ground
 * is TRANSPARENT: the composite's band is the ground, so the wall sits in the
 * buyer's colour rather than ours. Every face is the theme's.
 *
 * THE RULES THE EFFECT ACTUALLY DEPENDS ON, and the ones a reskin breaks, are
 * marked. The single least obvious is `pointer-events`: in a `preserve-3d`
 * context the pivot, the plane and the lanes are flat boxes at z = 0 while the
 * tiles are rotated away from that plane, so across most of the frame an
 * ancestor's own surface is IN FRONT of the tile the reader is pointing at and
 * swallows the event. Measured on the source effect before the rule: 22% of
 * sampled points reached a tile; after it, 80%, which is simply the share of
 * the frame the tiles cover.
 */

.wp-block-patternpaste-iso-wall {
  /* Tile size, the gap, and the two angles. The angles are the SAME numbers as
   * block.json's `patternpasteTilt` and `patternpasteSpin` defaults, on purpose and in one
   * direction only: render.php writes an inline `--iso-x` / `--iso-z` ONLY
   * when the author moved the control off its default, so an untouched block
   * carries no inline copy of what is already here, and the no-JS render is
   * angled by this file alone. */
  --iso-card-w: clamp(9rem, 17.5vw, 13.5rem);
  --iso-card-h: calc(var(--iso-card-w) * 0.72);
  --iso-gap: clamp(0.875rem, 1.7vw, 1.375rem);
  --iso-x: 48deg;
  --iso-z: -38deg;
  /* Written by view.js once it has measured the frame. The value here is only
   * what the lanes are before that, and the no-JS lane ignores it. */
  --iso-span: 1800px;
  /* 0 to 1 scroll energy, written by view.js. Drives the sheen and nothing
   * else, so a reader with no script sees no sheen. */
  --iso-vel: 0;
  /* The lane count and the SHORTEST lane's tile count, both written inline by
   * render.php. The values here are only what a wall rendered by something
   * that is not render.php would fall back to. Together they are the plane's
   * own size, which is the whole of the resting arithmetic below: a wall the
   * author wrote more tiles into needs less magnification to fill its band. */
  --patternpaste-lanes: 3;
  --patternpaste-per-lane: 3;
  --pv-plane-w: calc(var(--patternpaste-lanes) * (var(--iso-card-w) + var(--iso-gap)));
  --pv-plane-h: calc(var(--patternpaste-per-lane) * (var(--iso-card-h) + var(--iso-gap)));
  /* The two angles as ratios, sign discarded: `abs()` is younger than the
   * floor this block already sets with `color-mix`, and `max(x, -1 * x)` is
   * the same number in syntax every browser that has `sin()` has. The spin
   * runs -60 to 60 (edit.js; it opened to the positive half on 2026-08-29 for
   * a wall turned the other way, and every use of it here is already through
   * `abs`, so the arithmetic did not move) and the tilt is 8 to 58 degrees
   * (the rail leans 14, and 20 clamped it, 2026-08-29), so the foreshortening
   * is never zero and nothing here divides by nought. The one place a zero can
   * still arrive is `sin(0)` at the middle of the spin's range, and the
   * divisor that reads it is floored away from zero where it is used. */
  --iso-sin: max(sin(var(--iso-z)), -1 * sin(var(--iso-z)));
  --iso-cos: max(cos(var(--iso-z)), -1 * cos(var(--iso-z)));
  --iso-fore: max(cos(var(--iso-x)), -1 * cos(var(--iso-x)));
  /* How deep the frame is. One number, because the resting frame turns it off
   * and the editor turns it back on. */
  --iso-depth: 3600px;
  /* The resting band's height the composition WANTS. Not the height it gets:
   * a band this tall is only affordable while the magnification that fills it
   * stays under the cap below, and past the cap it is the band that gives way
   * rather than the zoom (see the arithmetic further down). */
  --patternpaste-rest-band: clamp(15rem, 25vw, 30rem);
  /* HOW FAR THE STILL WALL MAY BE MAGNIFIED BEFORE THE BAND GIVES WAY. 1.6 is
   * roughly the point where a tile stops reading as one of many and starts
   * reading as a poster: at 1920 the default card is 216px, so 1.6 puts about
   * five and a half tiles across the frame, and 3.4 (what a twelve-tile wall
   * solved to before this cap) put two and a half, which is a zoomed crop of a
   * wall and not a wall.
   *
   * IT IS A PREFERENCE, NOT A PROMISE, and the coverage is the promise: a wall
   * whose plane cannot contain the frame even at the minimum band is magnified
   * past this number anyway, because bare ground under the plane is the one
   * outcome that is never acceptable. What the cap can always do is stop
   * paying for band height with zoom. */
  --patternpaste-rest-cap: 1.6;
  /* The shortest band the composition is still a band at, and it is the
   * WANTED band's own floor above rather than a new number: a wall too thin to
   * afford its band loses the growth (the 25vw), never the band. Below this a
   * full-bleed strip stops reading as a section and starts reading as a rule
   * with texture in it, and the zoom the wall still needs to cover it would be
   * bought for nothing. */
  --patternpaste-rest-band-min: 15rem;
  /* How far past the frame's edge the wall is required to run. A tile cut by
   * the frame reads as a wall that continues; a tile exactly tangent to it
   * reads as the edge of a diagram, and sub-pixel rounding at that tangent is
   * a bare pixel. */
  --patternpaste-rest-bleed: 1rem;
  --pv-gutter: clamp(1.25rem, 4vw, 4rem);

  position: relative;
  margin-block: 0;
}

/* The introduction is OUTSIDE the 3D chain, so its links and buttons are
 * clickable: everything below the viewport is pointer-events: none except a
 * tile. It is also the part that scrolls away before the pin takes hold. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__intro {
  box-sizing: border-box;
  padding-block: var(--wp--preset--spacing--40);
  padding-inline: var(--pv-gutter);
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__intro > :first-child {
  margin-block-start: 0;
}

/* THE PIN TARGET. One viewport tall, clipping its own wall rather than
 * trusting the theme for it, and carrying the perspective. No padding and no
 * border: the box it pins in has to be the box it had, and a pin-spacer
 * reproduces neither.
 *
 * The perspective is long on purpose. Under about 3000px the composition
 * stops looking isometric and starts looking like a 3D close-up. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__viewport {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  height: 100svh;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: transparent;
  perspective: var(--iso-depth);
  /* CENTRED, not the source effect's 50% 46%. The plane is centred on the
   * frame, so an off-centre projection point shifts the whole wall down and
   * leaves the top of the frame short of wall by exactly that offset: measured
   * 2026-08-29 as one bare corner that no amount of extra lane fixed, because
   * the shortfall was in the projection and not in the plane. */
  perspective-origin: 50% 50%;
}

/* THE SOFT FRAME (`patternpasteFade`, added 2026-08-30, before release). Off,
 * the frame is a cut: the wall stops at four straight edges, which is the
 * composition the block shipped with and what a band with a stated height
 * wants. On, the top and the bottom dissolve, and the lanes read as running
 * on past the section instead of being stopped by it.
 *
 * TOP AND BOTTOM ONLY (Jake, 2026-08-30). It faded all four edges first, and
 * on a full-bleed wall the side fades added nothing and cost something: they
 * pull the wall in off the section's own edges and leave a full-width mosaic
 * looking narrower than the page it is on. The two edges worth fading are the
 * two the reader carries the frame past.
 *
 * IT IS A MASK, NOT A PAINTED GRADIENT, and that is the portability rule
 * rather than a preference: a gradient to the section's ground is a colour the
 * block does not have and cannot guess, and would be a visible band in every
 * theme it guessed wrong. A mask makes the frame's edges TRANSPARENT, so what
 * shows through is whatever the theme painted behind the section, and the
 * block states nothing.
 *
 * THE OPAQUE STOP IS `currentColor` AND IT IS NOT PAINT. A mask reads the
 * ALPHA channel; the hue never reaches a pixel, and the contract's one
 * approved token is the honest thing to write for "opaque here". It also
 * means the fade behaves like the rest of this stylesheet in the one theme
 * that would notice: a theme whose ink is itself translucent already gives
 * the tile grounds and hairlines that alpha, and now gives the fade the same.
 *
 * Not in the editor canvas: there is no viewport element there, and a faded
 * edge in a workspace is a tile a buyer cannot see to type into. */
.wp-block-patternpaste-iso-wall[data-patternpaste-fade='true'] .patternpaste-wall__viewport {
  --iso-fade: 14%;
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    currentColor var(--iso-fade),
    currentColor calc(100% - var(--iso-fade)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    currentColor var(--iso-fade),
    currentColor calc(100% - var(--iso-fade)),
    transparent 100%
  );
}

/* Centring only, and absolute on purpose: the plane is deliberately larger
 * than the frame, and a grid or flex item that overflows its container is
 * aligned to the start rather than centred, which parks the whole wall in one
 * corner. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__pivot {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  transform-style: preserve-3d;
}

/* NOTHING IN THE 3D CHAIN IS HIT-TESTABLE EXCEPT A TILE AND ITS PULL. See the
 * header: this is the difference between a wall that reacts and one where some
 * tiles react and some do not. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__pivot,
.wp-block-patternpaste-iso-wall .patternpaste-wall__plane,
.wp-block-patternpaste-iso-wall .patternpaste-wall__lane {
  pointer-events: none;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__tile,
.wp-block-patternpaste-iso-wall .patternpaste-wall__pull {
  pointer-events: auto;
}

/* The one rotated element. Miss `preserve-3d` on any link in the chain and the
 * wall flattens: the tiles stop sorting by depth and the raise stops reading
 * as coming toward the reader. This transform is also the whole of the no-JS
 * fallback, which is why the angles live in custom properties. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__plane {
  display: flex;
  align-items: center;
  /* NO GAP HERE, AND THAT IS THE HIT-TESTING RULE, not a spacing preference.
   * The gap between tiles is drawn by a transparent border on the tile itself
   * (see `.patternpaste-wall__tile`), so a tile's BOX covers its share of the gap while
   * the card the reader sees is inset from it. A real gap is a corridor of
   * dead pixels running the whole height and width of the wall: the pointer
   * crosses it between every pair of tiles, and at the frame's corners it is
   * the difference between the wall covering the frame and showing through to
   * nothing. */
  gap: 0;
  transform: rotateX(var(--iso-x)) rotateZ(var(--iso-z));
  transform-style: preserve-3d;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__lane {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0;
  width: calc(var(--iso-card-w) + var(--iso-gap));
  transform-style: preserve-3d;
}

/* Live layout: the lane becomes a fixed-length runway and each tile is placed
 * from its centre line by view.js. Without the script the lane stays a plain
 * flex column, which is why the no-JS page is a readable wall rather than a
 * blank frame. */
.wp-block-patternpaste-iso-wall.patternpaste-wall--live .patternpaste-wall__lane {
  display: block;
  height: var(--iso-span);
}

/* THE RESTING WALL FILLS ITS FRAME BY ARITHMETIC, and this is the arithmetic.
 *
 * The live wall covers its frame by MEASURING it and cloning: whole lanes
 * across, tiles along each lane, until the wall runs corner to corner however
 * the reader flicks it. At rest there is no script and therefore no clones.
 * The plane is exactly as big as the tiles the author wrote, and a plane that
 * is smaller than its frame and turned 38 degrees inside it is not a wall with
 * a bit missing: it is a diagonal band with a bare wedge at two opposite
 * corners, which is what the sheet showed at every theme and every width
 * (measured 2026-08-29 by hit-testing a 49 by 25 grid over the frame: 21.3% of
 * the frame on bare ground at 1920, 7.4% at 1280, 6.4% at 820, 0% at 390).
 *
 * So the resting plane is magnified until it CONTAINS the frame, and the
 * factor is solved rather than guessed. Undo the projection and the question
 * is flat: rotateX foreshortens the vertical by cos(tilt) and nothing else, so
 * the frame, taken back through it, is a rectangle 100cqw wide and
 * band / cos(tilt) tall; rotateZ turns the plane under it, so that rectangle,
 * taken back through the spin, has a bounding box in the plane's own axes of
 *
 *   across the lanes  |cos(spin)| * half-width + |sin(spin)| * half-height
 *   along the lanes   |sin(spin)| * half-width + |cos(spin)| * half-height
 *
 * and the plane must be at least that big in each. Twice each over the plane's
 * own width and height are the two magnifications that would just do it, and
 * the scale is the larger, never less than 1.
 *
 * IT IS THE LANE LENGTH THAT BINDS, not the lane count. `|sin(spin)| *
 * half-width` alone is 591px of lane at 1920 at the default spin, which is
 * more than three tiles: turn a wall 38 degrees and it is the run of a lane,
 * not the number of lanes, that has to reach the far corners. That is why a
 * twelve-tile wall solved to about 3.4 at 1920 and about 2.3 at 1280, and why
 * a wall the author wrote thirty tiles into solved to 2.5 and 1.7: more tiles
 * to a lane is more wall, and more wall is less zoom.
 *
 * AND 3.4 IS TOO MUCH ZOOM TO ACCEPT. Solved for the band the composition
 * asked for, a twelve-tile wall at 1920 put two and a half tiles across the
 * frame: every tile a poster, none of them a tile in a wall. So the
 * magnification is CAPPED at `--patternpaste-rest-cap`, and when the cap binds it is the
 * BAND that gives way: the same two conditions, turned round and solved for
 * the frame's half-height instead of for the scale, say how tall a band this
 * much wall can actually cover, and the frame takes that height. A short wall
 * therefore gets a short band rather than a tall band full of one tile.
 *
 * THE CAP IS A PREFERENCE AND THE COVERAGE IS THE PROMISE. `|sin(spin)| *
 * half-width` does not go away when the band does: at 1920 the width term
 * alone needs 2.26 of a four-lane, three-deep plane with the band at nothing,
 * so a wall that thin is magnified past the cap even at `--patternpaste-rest-band-min`,
 * and the floor is what decides. That is not the cap failing; it is the wall
 * reporting that it has too little in it, and one more row of tiles is the fix
 * (README.md: the shortest lane wants four or more tiles). The alternative was
 * never a smaller number, it was bare ground.
 *
 * `--patternpaste-per-lane` is the SHORTEST lane, floor and never ceiling. Thirty tiles
 * over seven lanes is two lanes of five and five of four, the lanes are
 * centred on the plane, and the rectangle every lane actually fills is
 * therefore four tiles tall. Sized on the ceiling instead, the same wall left
 * 4.6% of the frame bare at 1920 where the short lanes ran out (measured
 * 2026-08-29).
 *
 * AND THE FRAME IS FLAT AT REST. `perspective` is a live-state affordance: it
 * is what makes a tile raised toward the reader read as coming forward. On the
 * still wall it only converges the far edge, by 16% at the top of the plane at
 * the default tilt, which is exactly the top corner the arithmetic above
 * cannot then promise, and it left 0.2% of the frame bare at 1920 with
 * everything else right. Off, the projection is affine, the solution is exact,
 * and a still isometric composition with no perspective in it is what
 * isometric means. Measured after: 0% bare at 1920, 1280, 820 and 390, at 3,
 * 6, 12, 21 and 30 tiles, and at both ends of both angle controls.
 *
 * Nothing here is a start state and nothing needs undoing: the live wall drops
 * the whole branch the moment `patternpaste-wall--live` lands. */
/* THE BLOCK IS THE CONTAINER, NOT THE FRAME, and that is a container-query
 * rule rather than a preference. Container query units in an element's own
 * declarations resolve against its nearest ANCESTOR container, never against
 * itself, so a frame that is its own container cannot read its own width to
 * decide its own height, which is exactly what the cap below asks it to do:
 * `50cqw` in the frame's `height` would silently fall back to half the WINDOW
 * and size the band off a width the band may not have. The frame is
 * `width: 100%` of this element, so this element's inline size IS the frame's.
 *
 * Containment is on the resting branch alone, because layout containment makes
 * an element a containing block for a fixed-position descendant and the live
 * branch pins the frame inside here. */
/* AND THE UNPINNED WALL TAKES THE SAME FRAME. `patternpaste-wall--field` is view.js
 * saying it built a wall that is NOT pinned (`data-patternpaste-pin="false"`): the band
 * sits in the page at its own height and the reader scrolls straight past it.
 * A live frame is one screen tall only because that is the frame a pin holds,
 * so a field wall would otherwise take a screen it never asked for and jump
 * from its resting band to it the moment the script landed.
 *
 * It takes the SOLVE and not only the height, so `patternpasteHeight` at 0 is the same
 * band live as at rest and there is no jump either way. What it does not take
 * is the plane's magnification or the flat projection below: a live wall
 * covers its frame by cloning whole lanes into it (view.js `layout`), and the
 * perspective is what makes a raised tile read as coming forward.
 *
 * The containment is safe here for the same reason it is unsafe on a pinned
 * wall: layout containment makes this element a containing block for a
 * fixed-position descendant, and a field wall never creates one. */
.wp-block-patternpaste-iso-wall:not(.patternpaste-wall--live),
.wp-block-patternpaste-iso-wall.patternpaste-wall--field {
  container-type: inline-size;
}

.wp-block-patternpaste-iso-wall:not(.patternpaste-wall--live) .patternpaste-wall__viewport,
.wp-block-patternpaste-iso-wall.patternpaste-wall--field .patternpaste-wall__viewport {
  /* HALF THE FRAME, IN THE PLANE'S OWN AXES, and the whole of the solve. The
   * width half is fixed by the band: nothing here can make the frame narrower.
   * The height half is what gives way when the cap binds, so it is chosen
   * here, and the frame's height is read back OUT of it below.
   *
   *   want  the height the composition asked for
   *   cap   the tallest half-height `--patternpaste-rest-cap` can still contain, which
   *         is the two coverage conditions turned round and solved for y:
   *           cos*x + sin*y <= cap * plane-w / 2
   *           sin*x + cos*y <= cap * plane-h / 2
   *         and the smaller of the two, because both must hold. It goes
   *         NEGATIVE on a wall whose plane the cap cannot stretch over the
   *         frame's width alone, and the floor below is then what decides.
   *   min   the shortest band that still reads as a band.
   *
   * The divisors are floored away from zero: the spin control reaches 0, where
   * `sin` is 0, and a division by zero in `calc()` makes the declaration
   * invalid at computed-value time, which would take the frame's height with
   * it. 0.001 is small enough to be the same answer everywhere the ratio is
   * real and large enough that the quotient stays a number. */
  --patternpaste-rest-x: calc(50cqw + var(--patternpaste-rest-bleed));
  --patternpaste-rest-y-want: calc((var(--patternpaste-rest-band) / 2 + var(--patternpaste-rest-bleed)) / var(--iso-fore));
  --patternpaste-rest-y-min: calc((var(--patternpaste-rest-band-min) / 2 + var(--patternpaste-rest-bleed)) / var(--iso-fore));
  --patternpaste-rest-y-cap: min(
    calc(
      (var(--patternpaste-rest-cap) * var(--pv-plane-w) / 2 - var(--iso-cos) * var(--patternpaste-rest-x)) /
        max(var(--iso-sin), 0.001)
    ),
    calc(
      (var(--patternpaste-rest-cap) * var(--pv-plane-h) / 2 - var(--iso-sin) * var(--patternpaste-rest-x)) /
        max(var(--iso-cos), 0.001)
    )
  );
  --patternpaste-rest-y: max(var(--patternpaste-rest-y-min), min(var(--patternpaste-rest-y-want), var(--patternpaste-rest-y-cap)));
  /* THE BAND IS READ BACK OUT OF THE SOLVE. `--patternpaste-rest-y` is the half-height
   * of the frame taken back through the tilt, so the frame's own height is
   * that projected forward again and the bleed taken off both ends. Left at
   * `--patternpaste-rest-band` instead, a capped wall would be a short wall's zoom in a
   * tall wall's band, which is the bare wedge this whole branch exists to
   * refuse. */
  height: calc(2 * (var(--patternpaste-rest-y) * var(--iso-fore) - var(--patternpaste-rest-bleed)));
}

/* THE STILL FRAME IS FLAT AND THE LIVE ONE IS NOT. See the block comment
 * above: at rest the perspective only converges the far edge and takes the
 * exactness of the solve with it (0.2% of the frame bare at 1920), and a still
 * isometric composition with no perspective in it is what isometric means. A
 * field wall is live, so it keeps the depth: the raise and the pull are the
 * whole reason the property is there. */
.wp-block-patternpaste-iso-wall:not(.patternpaste-wall--live) .patternpaste-wall__viewport {
  perspective: none;
}

.wp-block-patternpaste-iso-wall:not(.patternpaste-wall--live) .patternpaste-wall__plane {
  --patternpaste-rest-across: calc(var(--iso-cos) * var(--patternpaste-rest-x) + var(--iso-sin) * var(--patternpaste-rest-y));
  --patternpaste-rest-along: calc(var(--iso-sin) * var(--patternpaste-rest-x) + var(--iso-cos) * var(--patternpaste-rest-y));
  --patternpaste-rest-scale: max(
    calc(2 * var(--patternpaste-rest-across) / var(--pv-plane-w)),
    calc(2 * var(--patternpaste-rest-along) / var(--pv-plane-h)),
    1
  );
  transform: rotateX(var(--iso-x)) rotateZ(var(--iso-z)) scale(var(--patternpaste-rest-scale));
}

/* A tile is a surface mixed out of the reader's own ink, never a colour of
 * ours. `color-mix` with `currentColor` gives a plate that is legible on a
 * dark band and on a light one without either being named. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__tile {
  position: relative;
  box-sizing: border-box;
  display: block;
  /* The BOX is the tile plus its share of the gap; the CARD is the padding box
   * inside a transparent border. `background-clip: padding-box` is what makes
   * the ground stop at the card, and the hairline is an inset shadow rather
   * than a second border, because an element has only one. */
  width: calc(var(--iso-card-w) + var(--iso-gap));
  height: calc(var(--iso-card-h) + var(--iso-gap));
  padding: clamp(0.65rem, 1vw, 1rem);
  border: calc(var(--iso-gap) / 2) solid transparent;
  /* SQUARE, and that is a hit-testing decision as much as a look. A rounded
   * border box is rounded for hit testing too, so where four tiles meet, their
   * four rounded corners leave a diamond of frame showing through that a
   * pointer falls into and `elementFromPoint` reports as bare (measured
   * 2026-08-29: one corner of four, on a different cell each run, however much
   * wall was added). Square corners close it, and a wall of sharp poster tiles
   * is the composition anyway. */
  border-radius: 0;
  background: color-mix(in srgb, currentColor 9%, transparent);
  background-clip: padding-box;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent);
  overflow: hidden;
  backface-visibility: hidden;
  cursor: pointer;
  transition: box-shadow 0.35s ease;
}

/* With the lift off a tile does nothing on click, so it must not promise one:
 * a pointer cursor on an inert surface is a broken control (Jake, 2026-08-29). */
.wp-block-patternpaste-iso-wall[data-patternpaste-lift='false'] .patternpaste-wall__tile {
  cursor: default;
}

/* THE PLATE IS OFF: THE TILE IS A WINDOW AND NOT A CARD (2026-08-29).
 *
 * `patternpastePlate: false` takes away exactly the three things that hold a tile's
 * content away from its own edges: the padding, the ground mixed out of
 * `currentColor` and the inset hairline. What it does NOT take away is the
 * transparent border, because that border IS the gap (see `.patternpaste-wall__tile`
 * above: a real gap is a corridor of dead pixels the pointer falls into), nor
 * the square corners, nor `overflow: hidden`, nor the raise and the pull.
 *
 * So a wall with the plate off is the same wall with the same hit testing and
 * the same behaviour, and a photograph in a tile is the tile. That is only
 * true if the content actually reaches the edges, which is what the next rule
 * is for: the body, the author's own group and the first image in it all take
 * the tile's box, and the image covers it. An image block otherwise draws at
 * its own aspect ratio and leaves the rest of the card bare, which with no
 * ground under it is a hole rather than a margin.
 *
 * `height` and `width` beat the image's `aspect-ratio`, on purpose: a buyer
 * whose photographs are 4:3 and 1:1 and 3:2 gets a level mosaic rather than a
 * ragged one, because `aspect-ratio` only applies while one of the two
 * dimensions is `auto`. The crop is `object-fit: cover`, which is the same
 * word core's own image block writes for its `scale` attribute. */
.wp-block-patternpaste-iso-wall[data-patternpaste-plate='false'] .patternpaste-wall__tile {
  padding: 0;
  background: none;
  box-shadow: none;
}

.wp-block-patternpaste-iso-wall[data-patternpaste-plate='false'] .patternpaste-wall__tile-body,
.wp-block-patternpaste-iso-wall[data-patternpaste-plate='false'] .patternpaste-wall__tile-body > * {
  height: 100%;
}

.wp-block-patternpaste-iso-wall[data-patternpaste-plate='false'] .patternpaste-wall__tile .wp-block-image,
.wp-block-patternpaste-iso-wall[data-patternpaste-plate='false'] .patternpaste-wall__tile .wp-block-image img {
  width: 100%;
  height: 100%;
}

.wp-block-patternpaste-iso-wall[data-patternpaste-plate='false'] .patternpaste-wall__tile .wp-block-image img {
  object-fit: cover;
}

/* AND THE PULL NOW SITS ON THE PICTURE. With a plate under it the mark is a
 * hairline circle on a surface the block itself drew, so the reader's own ink
 * is enough; over a photograph nothing is known about what is behind it. The
 * mark keeps its ink and gains a ground and a rim mixed from the SAME ink, so
 * the control is still a shape the eye separates from the picture without this
 * block naming a colour. It is a mark on an unknown ground, so it is a
 * legibility argument and not a measured ratio: the whole tile is the click
 * target either way, and the label a screen reader reads is unchanged. */
.wp-block-patternpaste-iso-wall[data-patternpaste-plate='false'] .patternpaste-wall__pull-mark {
  width: 1.75rem;
  height: 1.75rem;
  border-color: color-mix(in srgb, currentColor 65%, transparent);
  background: color-mix(in srgb, currentColor 22%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 28%, transparent);
}

.wp-block-patternpaste-iso-wall.patternpaste-wall--live .patternpaste-wall__tile {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__tile-body > :first-child {
  margin-block-start: 0;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__tile-body > :last-child {
  margin-block-end: 0;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__tile .wp-block-image {
  margin: 0;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__tile .wp-block-image img {
  display: block;
  width: 100%;
  height: auto;
}

/* The pull is the disclosure control, in the tile's corner, in the reader's
 * own ink. It is the keyboard route to the square-on state and the only tab
 * stop a tile contributes, so a hundred-tile wall is as many tab stops as the
 * buyer authored. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__pull {
  /* Absolute against the tile's PADDING box, which is the card: the tile's
   * transparent border is the gap, and the control sits inside the card. */
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 52px, and the button itself, not a padded pseudo-element: the responsive
   * gate measures the control's own box, and so does a thumb. The VISIBLE ring
   * is the mark inside it, which is why a target this size does not read as a
   * button the size of a third of the tile.
   *
   * WHY 52 AND NOT 44. A tile sits on a plane rotated away from the reader, so
   * the box a hit test and `getBoundingClientRect` both see is the PROJECTION
   * of the control, not its own size. Measured 2026-08-29 at the default
   * angles: a 44px button projects to 41.9px at its narrowest, which is under
   * the floor by exactly the amount the tilt foreshortens it. */
  width: 3.25rem;
  height: 3.25rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__pull-mark {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
  border-radius: 50%;
}

/* A PLUS, AND NOT A CHEVRON. The mark was a bottom-right border turned -45
 * degrees, which is a `>`, and the expanded state turned it 135, which is a
 * `<`: a tile pulled square to the reader offered them what reads as "go
 * back", and back is not what the control does. A plus says "open this" with
 * no direction in it at all, and the same two strokes turned 45 degrees are a
 * close cross, which is the one glyph a reader never has to be taught.
 *
 * Two strokes in one box rather than a glyph: a character would be a font this
 * block does not state, and a border would be a corner again. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__pull-mark::before,
.wp-block-patternpaste-iso-wall .patternpaste-wall__pull-mark::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  display: block;
  width: 0.5rem;
  height: 1px;
  background: currentColor;
  transform: translate(-50%, -50%) rotate(0deg);
  transition: transform 0.2s ease;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__pull-mark::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__pull[aria-expanded='true'] .patternpaste-wall__pull-mark::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__pull[aria-expanded='true'] .patternpaste-wall__pull-mark::after {
  transform: translate(-50%, -50%) rotate(135deg);
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__pull:focus-visible .patternpaste-wall__pull-mark {
  border-color: currentColor;
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* The control's name for a screen reader, and not a ribbon: the visible mark
 * is the affordance, the words are what a reader without it needs. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__pull-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* THE HOLD. WCAG 2.2.2 Pause, Stop, Hide is Level A, and a wall that drifts on
 * its own has to give the reader a way to stop it. The chip speaks the pull's
 * language on purpose: the same 1.5rem circle in the reader's own ink, the same
 * two strokes drawn rather than typed, the same focus ring. What differs is
 * that it is the FRAME's control and not a tile's, so it sits in the frame's
 * bottom corner rather than in a card's.
 *
 * OUT OF SIGHT UNTIL THE WALL IS LIVE. `patternpaste-wall--live` is added by
 * view.js and removed by its revert, so with no script, or with a reader's
 * reduced-motion setting honoured, nothing moves and the control that would
 * stop it is not drawn. This block's own rule: a control that does nothing
 * must also stop LOOKING like one. It stays in the markup either way, which is
 * what keeps the reverted, the reduced-motion and the JS-off renders identical.
 *
 * CLEAR OF THE FADE. `--iso-fade` is the mask's own opaque stop, inherited from
 * the viewport when `patternpasteFade` is on and 0 when it is not, so the chip
 * sits where the frame is opaque in both. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__hold {
  display: none;
  position: absolute;
  inset-block-end: calc(var(--iso-fade, 0%) + 0.75rem);
  inset-inline-start: 0.75rem;
  /* Above the pivot's stacking context, so no tile is ever in front of the one
   * control that stops them. */
  z-index: 3;
  align-items: center;
  justify-content: center;
  /* 48px: the tap floor check-responsive holds a payload to is 44, and this
   * control is not on the rotated plane, so its box is its own size. */
  width: 3rem;
  height: 3rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  pointer-events: auto;
}

.wp-block-patternpaste-iso-wall.patternpaste-wall--live .patternpaste-wall__hold {
  display: flex;
}

/* A ground under the mark, because the chip sits ON the wall rather than on a
 * card: `currentColor` mixed down, exactly as a tile's plate is. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__hold-mark {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
  border-radius: 50%;
  background: color-mix(in srgb, currentColor 12%, transparent);
}

/* TWO BARS, the pause glyph, drawn and not typed: a character would be a font
 * this block does not state. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__hold-mark::before,
.wp-block-patternpaste-iso-wall .patternpaste-wall__hold-mark::after {
  content: '';
  position: absolute;
  top: 50%;
  display: block;
  width: 1px;
  height: 0.5rem;
  background: currentColor;
  transform: translateY(-50%);
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__hold-mark::before {
  left: calc(50% - 0.125rem);
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__hold-mark::after {
  left: calc(50% + 0.125rem);
}

/* HELD IS FILLED. The state is `aria-pressed`, which the control already has to
 * carry for a screen reader, so there is no second name for it in the markup. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__hold[aria-pressed='true'] .patternpaste-wall__hold-mark {
  border-color: currentColor;
  background: color-mix(in srgb, currentColor 30%, transparent);
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__hold:focus-visible .patternpaste-wall__hold-mark {
  border-color: currentColor;
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* The control's name for a screen reader, and not a ribbon: the same treatment
 * the pull's label takes. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__hold-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.wp-block-patternpaste-iso-wall .patternpaste-wall__tile.is-raised,
.wp-block-patternpaste-iso-wall .patternpaste-wall__tile:focus-within {
  box-shadow: inset 0 0 0 1px currentColor;
}

/* Everything dims while a tile is pulled out of the plane. PER TILE, never per
 * lane: opacity on a lane flattens the 3D context under it. */
.wp-block-patternpaste-iso-wall.patternpaste-wall--focus .patternpaste-wall__tile {
  opacity: 0.34;
}

.wp-block-patternpaste-iso-wall.patternpaste-wall--focus .patternpaste-wall__tile.is-pulled {
  opacity: 1;
}

/* The light channel: one sweep in the reader's own ink, in the document only
 * while the wall is actually moving fast. A permanently composited blend layer
 * is a per-frame cost for nothing, and at rest `--iso-vel` is 0 anyway. */
.wp-block-patternpaste-iso-wall .patternpaste-wall__sheen {
  position: absolute;
  inset: -25%;
  display: none;
  pointer-events: none;
  opacity: calc(var(--iso-vel) * 0.35);
  background: linear-gradient(
    102deg,
    transparent 33%,
    color-mix(in srgb, currentColor 55%, transparent) 47%,
    color-mix(in srgb, currentColor 30%, transparent) 55%,
    transparent 68%
  );
}

.wp-block-patternpaste-iso-wall.is-flicking .patternpaste-wall__sheen {
  display: block;
}

/* A touch pointer gets a shallower wall: the same composition read at arm's
 * length on a narrow frame, where 48deg puts the tiles too near the floor.
 * These are the two properties render.php also writes, so an author who moved
 * either control off its default overrides this deliberately. */
@media (pointer: coarse) {
  .wp-block-patternpaste-iso-wall {
    --iso-x: 42deg;
    --iso-z: -30deg;
  }
}

/* THE EDITOR CANVAS, and it is NOT the front end's markup.
 *
 * Motion never runs in the editor (boot.js returns on the editor body class),
 * so what an author sees is the resting composition. But InnerBlocks owns the
 * children of the plane and cannot be asked to deal them into lane wrappers,
 * so the front end's ONE rotated plane is impossible here: rotating it would
 * rotate the introduction with the tiles, which is what the first cut did, and
 * an eyebrow, a heading and a button laid at 38 degrees are not editable.
 *
 * So the editor inverts it. THE PLANE STAYS FLAT and each tile carries the
 * isometric transform itself, in a grid as many columns wide as render.php
 * would have made lanes. The introduction is every child that is not a group,
 * and it spans the grid, so it stays flat in normal flow above the wall, which
 * is where the front end puts it. The tiles get the same card surface the
 * front end gives them, because a bare rotated paragraph is not a tile.
 *
 * AND IT EDITS FLAT. `.is-angled` is added by edit.js only while the wall is
 * at rest: nothing in it selected, or the "Preview angle" toolbar toggle held
 * down. Everything specific to the angle lives under that class, and the rules
 * above it are the flat workspace, because the flat workspace is the state a
 * buyer types in. Measured 2026-08-29 with the tilt always on: a click on the
 * visual centre of a heading set the caret four characters past where it was
 * aimed, four tiles of fourteen failed every hit probe on their own text
 * because a rotated bounding box hands its corners to a neighbour, and six
 * clipped 39px of content away inside `overflow: hidden`, the empty image
 * block's Upload and Media Library buttons among it.
 *
 * The angled state KEEPS the fixed height and the clip, on purpose: the front
 * end clips a tile exactly this way, so the look an author steps back to is
 * the truth about what a reader will see. The flat state does not, so nothing
 * a buyer typed is ever hidden while they are typing it. */
/* The canvas carries no `patternpaste-wall--live`, so the resting branch's containment
 * would otherwise land on the editor's own block wrapper. The canvas solves
 * nothing (the frame is `height: auto` and the plane is a flat grid), so the
 * container buys it nothing and costs it layout containment on a wrapper the
 * editor drops its own furniture into. */
.patternpaste-wall.is-editing {
  container-type: normal;
}

.patternpaste-wall.is-editing .patternpaste-wall__viewport {
  /* Nothing is clipped and nothing is one screen tall: every tile has to be
   * reachable with a pointer, including the last one. The canvas carries no
   * `patternpaste-wall--live`, so it would otherwise take the RESTING frame, which is a
   * fixed height that clips and a plane magnified past the canvas; and the
   * angled preview wants the depth back. */
  container-type: normal;
  height: auto;
  overflow: visible;
  perspective: var(--iso-depth);
  padding-block: var(--wp--preset--spacing--30);
}

.patternpaste-wall.is-editing .patternpaste-wall__pivot {
  position: static;
  transform: none;
}

/* An empty wall shows its placeholder and not an empty grid. The plane stays
 * MOUNTED behind it, because an unmounted inner block list is not in
 * `getBlockListSettings` and the editor then refuses every insert into this
 * block: "Add tiles" dispatched nine groups into a root that answered
 * `canInsertBlockType: false` and dropped all nine without a word (measured
 * 2026-08-29). `display: none` unmounts nothing. */
.patternpaste-wall.is-editing.is-empty .patternpaste-wall__pivot {
  display: none;
}

.patternpaste-wall.is-editing .patternpaste-wall__plane {
  display: grid;
  grid-template-columns: repeat(var(--patternpaste-lanes, 3), minmax(0, var(--iso-card-w)));
  justify-content: center;
  gap: var(--iso-gap);
  align-items: start;
  /* Flat: the tilt is on the tiles. */
  transform: none;
}

/* The introduction, and the appender: anything that is not a tile spans the
 * grid and stays in ordinary flow. */
.patternpaste-wall.is-editing .patternpaste-wall__plane > :not(.wp-block-group) {
  grid-column: 1 / -1;
}

/* A tile, dressed as the front end dresses it. Upright, and as tall as its
 * content: this is the box a buyer types in. `min-height` rather than `height`
 * so a tile with a photograph and three lines in it shows all three. */
.patternpaste-wall.is-editing .patternpaste-wall__plane > .wp-block-group {
  box-sizing: border-box;
  min-height: var(--iso-card-h);
  padding: clamp(0.65rem, 1vw, 1rem);
  border: 1px solid color-mix(in srgb, currentColor 20%, transparent);
  background: color-mix(in srgb, currentColor 9%, transparent);
  box-shadow: 0 1rem 2.4rem color-mix(in srgb, currentColor 16%, transparent);
  transform: none;
}

/* THE CANVAS DRAWS A PLATE-LESS TILE TOO. `edit.js` puts the same
 * `data-patternpaste-plate` on the wrapper the server writes on the section, so a wall
 * whose tiles are windows is drawn as windows here: no padding, no ground, no
 * rule, and the picture filling the card. An author who cannot see the
 * composition they set is composing something else.
 *
 * The height is the card's rather than a `min-height`, because a picture with
 * nothing beside it has no content height to grow to, and a 1:1 photograph in
 * an auto-height cell would draw a column of full-width images instead of a
 * wall. Nothing else about the flat workspace moves. */
.patternpaste-wall.is-editing[data-patternpaste-plate='false'] .patternpaste-wall__plane > .wp-block-group {
  height: var(--iso-card-h);
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  overflow: hidden;
}

/* `:has(img)` because an image block a buyer has not chosen a photograph for
 * yet is a PLACEHOLDER with Upload and Media Library buttons in it, and
 * stretching that to the card's height inside `overflow: hidden` is how this
 * block once clipped those buttons away (README, "The canvas"). A filled
 * image fills the card; an empty one keeps its own box. */
.patternpaste-wall.is-editing[data-patternpaste-plate='false'] .patternpaste-wall__plane > .wp-block-group > .wp-block-image:has(img),
.patternpaste-wall.is-editing[data-patternpaste-plate='false'] .patternpaste-wall__plane > .wp-block-group > .wp-block-image img {
  height: 100%;
}

.patternpaste-wall.is-editing[data-patternpaste-plate='false'] .patternpaste-wall__plane > .wp-block-group > .wp-block-image img {
  object-fit: cover;
}

/* THE LOOK. The scale keeps a rotated card inside its grid cell: a card turned
 * 38 degrees projects a bounding box a quarter wider than itself. */
.patternpaste-wall.is-editing.is-angled .patternpaste-wall__plane > .wp-block-group {
  height: var(--iso-card-h);
  overflow: hidden;
  transform: rotateX(var(--iso-x)) rotateZ(var(--iso-z)) scale(0.78);
}

/* While angled, a tile is one target and its contents are not targets at all.
 * A click anywhere on it selects the TILE, which is a selection inside the
 * wall, which flattens the wall; the second click lands where it was pointed.
 * Without this, the first click drops a caret at whatever character the 3D
 * projection maps the pointer to, which is not the one under it. */
.patternpaste-wall.is-editing.is-angled .patternpaste-wall__plane > .wp-block-group * {
  pointer-events: none;
}

/* THE APPENDER IS A ROW UNDER THE WALL, not a corner. The editor wraps
 * whatever `renderAppender` returns in `.block-list-appender`, and inside a
 * grid the editor's own rules make that wrapper `position: absolute` and park
 * it over the last cell, where it sat on top of a tile (measured 2026-08-29:
 * inset 877px/1019px, 74px wide, covering "Late Shift"). Putting it back into
 * flow is what makes "Add tile" a row of its own at the end of the wall. */
.patternpaste-wall.is-editing .patternpaste-wall__plane > .block-list-appender {
  position: static;
  grid-column: 1 / -1;
  width: auto;
  margin: 0;
}

/* The appender is never angled and never a target it cannot be clicked on. */
.patternpaste-wall.is-editing .patternpaste-wall__add {
  display: flex;
  justify-content: center;
  padding-block-start: var(--wp--preset--spacing--20, 0.75rem);
  pointer-events: auto;
}

.patternpaste-wall.is-editing .patternpaste-wall__plane > .wp-block-group > :first-child {
  margin-block-start: 0;
}

.patternpaste-wall.is-editing .patternpaste-wall__plane .wp-block-image,
.patternpaste-wall.is-editing .patternpaste-wall__plane .wp-block-image img {
  margin: 0;
  width: 100%;
  height: auto;
}

/* Everything in the editor is editable, including a tile: the front end's
 * pointer-events rules exist to let a click through a flat 3D ancestor to a
 * rotated tile, and there is no such ancestor here. The angled state narrows
 * this to the tile itself, above. */
.patternpaste-wall.is-editing .patternpaste-wall__pivot,
.patternpaste-wall.is-editing .patternpaste-wall__plane,
.patternpaste-wall.is-editing .patternpaste-wall__plane > * {
  pointer-events: auto;
}

/* Print is a page, not a frame: the wall flattens to the tiles in the order
 * they were authored and the viewport stops clipping them. */
@media print {
  .wp-block-patternpaste-iso-wall,
  .wp-block-patternpaste-iso-wall:not(.patternpaste-wall--live),
  .wp-block-patternpaste-iso-wall.patternpaste-wall--field {
    container-type: normal;
  }

  .wp-block-patternpaste-iso-wall .patternpaste-wall__viewport,
  .wp-block-patternpaste-iso-wall:not(.patternpaste-wall--live) .patternpaste-wall__viewport,
  .wp-block-patternpaste-iso-wall.patternpaste-wall--field .patternpaste-wall__viewport {
    container-type: normal;
    height: auto;
    overflow: visible;
    perspective: none;
  }

  .wp-block-patternpaste-iso-wall .patternpaste-wall__pivot {
    position: static;
    transform: none;
  }

  .wp-block-patternpaste-iso-wall .patternpaste-wall__plane,
  .wp-block-patternpaste-iso-wall:not(.patternpaste-wall--live) .patternpaste-wall__plane,
  .wp-block-patternpaste-iso-wall.patternpaste-wall--field .patternpaste-wall__plane {
    display: block;
    transform: none;
  }

  .wp-block-patternpaste-iso-wall .patternpaste-wall__lane {
    display: block;
    width: auto;
    height: auto;
  }

  .wp-block-patternpaste-iso-wall .patternpaste-wall__tile {
    position: static;
    width: auto;
    height: auto;
    box-shadow: none;
    break-inside: avoid;
  }

  .wp-block-patternpaste-iso-wall .patternpaste-wall__pull,
  .wp-block-patternpaste-iso-wall .patternpaste-wall__hold,
  .wp-block-patternpaste-iso-wall.patternpaste-wall--live .patternpaste-wall__hold,
  .wp-block-patternpaste-iso-wall .patternpaste-wall__sheen {
    display: none;
  }
}
