/**
 * corpus2026.css — DESKcorpus (#204:31), the studio one-pager.
 *
 * Loaded on top of `desk2026.css`, which owns the shared base: the `--k`
 * design pixel, `.fl-display` / `.fl-text`, and the white `.fl-nav` used again
 * at the foot of this page. Everything here is the corpus itself.
 *
 * Geometry: one 1520x7950 canvas of absolutely placed nodes, exactly as the
 * frame is drawn. Every offset is the Figma x/y of that node — see
 * `docs/figma-2026-map.md`. Blocks overlap in the source (formatos runs behind
 * `360 Interactivo` and `vision`), so the order here is the source's, not a
 * tidied one.
 */

.fl-corpus {
  /* `--sv` is the progress of `seguir viendo`, 0 closed to 1 open, and it is
     written by corpus.js frame by frame: the drawer is JS-driven so the same
     number can move the layout AND the scroll in the same frame — the
     reader's viewpoint never moves. Opening unfolds downward below the held
     view (the scroll is left alone); closing pins the clicked control and
     folds the upper part away (the scroll follows it). A rAF could follow a
     CSS transition instead, but its layout write and the scroll write would
     land a frame apart and the pinned control would jitter — the constraint
     is same-frame atomicity, one number writing both. And the browser's own
     scroll anchoring cannot do the closing leg either: everything in
     `.fl-corpus` is absolutely positioned, which excludes it as an anchor
     candidate.
     The 986 are NOT IN THE DESIGN: only the collapsed gallery is drawn, so
     what opening adds is derived from the rows' own coordinates, 1849 - 863 —
     986 and not 946 since 27-ago-2026, because with the `2097:318` grid
     Fila004 measures 577 instead of 537 (`Teletrabajo 2` takes 411 from
     offset 166) and the rows end at 1849 = 1272 + 577. Everything the old
     transitions moved rides on `--expand`, so it still reads as one
     movement. */
  --sv: 0;
  --expand: calc(986 * var(--kp) * var(--sv));

  /* What the navigation bar occupies — and it is FIXED: it starts at 39 and is
     51 tall (`.fl-menu` in this same file, `position: fixed` in
     `desk2026.css`), so it ends at 90. The Serena piece reserves that gap at
     the top so the bar does not sit on it: with the block measuring the
     window, its top edge IS the window's top edge, which is exactly where the
     bar floats — and on top of a rotating room (Denis, 25-ago-2026). */
  /* The projects grid goes FULL BLEED and scales UNIFORMLY (Denis,
     25-ago-2026): "max width y height adaptado al scale". `--kp` is the scale
     that makes the canvas's 1520 measure the whole window — below 1520 it
     equals `--k` and changes nothing; above it, where the canvas stays pinned
     at 1520 and the side bands appear, it grows.
     Derived from `--fl-bleed` and NOT from `100vw`: `100vw` counts the
     scrollbar, which the centred canvas does not have, and `viewport.js`
     fails because of that.
     The difference with the `feat/grid-edge-mock` mock-up is exactly this:
     there `--kx` stretched ONLY the width and the photos came out 26% wider
     at the same height. Here both dimensions grow, so the 14 photos keep
     their proportion and their `cover` crops the same as at 1520.

     BACK AND FORTH, so it does not get undone again by mistake: this went in
     on 25-ago, was removed the same day when applying Valerie's rule 3 — the
     file's 41 of margin measured 52 at 1920 and 69 at 2560 when stretched —
     and goes back in that afternoon, at Denis's request: "los projects otra
     vez no van de edge a edge". The full-bleed grid wins over the file
     margin. Measured before touching it: at 1920 it ran from 241 to 1680 over
     1920, with 240px of white on each side.

     AND IT COMES OUT AGAIN on 27-ago-2026 at 14:07, with the reason stated by
     Denis: "el padding horizontal en la web es incorrecto, cuando extiendes
     mucho [...] los projects, los laterales o padding o margenes horizontales
     no cuadran con la parte de comosera y deben de ser los mismos".
     And he is exactly right. At 1520 the three left columns start at 40 —
     `cómo será`, `formatos` and the grid's first photo. At 3440 the first two
     move to 1000 with the canvas and the grid stays at 91, because it was the
     only piece scaling with the WINDOW instead of the canvas.
     `--kp` becomes `--k`, i.e. the grid returns to the canvas and its margins
     match everything else at any width.
     THIS UNDOES the 25-ago request ("los projects otra vez no van de edge a
     edge"), and it is undone at the later instruction's request, not by
     mistake. Going back is ONE line: the bleed formula is right below.
     What is NOT touched are the full-bleed photos — hero, Casa Dolmen, Serena
     and FIN: the instruction speaks of margins that do not line up between
     text columns and grid, and those four have no margin to line up, they go
     to zero by design. Serena was also stretched just twenty minutes ago.
     AND IT IS CORRECTED AT 14:36 THE SAME DAY, which is where this stays.
     Denis: "projects debe de ir edge to edge pero [...] debe de tener lo que
     te pedi antes pero edge to edge con el padding horizontal de la imagen de
     blur de cómo será". That is: both things, and they were not incompatible
     — the two previous ways of doing it were.
     The missing datum, measured: the `cómo será` image (#.fl-solution-img)
     sits 40px CONSTANT from the window's right edge at 1520, 1920, 2560 and
     3440. It is not a scaling margin: it is a fixed padding against the
     window. So the grid runs edge to edge with a fixed 40 on each side, and
     what scales is what is INSIDE. The usable area goes from the file's 1440
     to `window - 80`, so the divisor is 1440 and not 1520: `--kp` maps the
     content width, not the canvas width. The 40s go separately, in `--k`, so
     they do NOT scale — with them inside `--kp` they measured 91 at 3440,
     which is exactly what Denis saw wrong.
     At 1520 `--kp` is 1 and everything lands on the file's coordinates: 40 of
     margin, 1440 of content. Above, 40 of margin and window-80 inside. */
  --kp: calc(var(--k) + 2 * var(--fl-bleed) / 1440);

  /* How much taller the grid grows, which is how far everything below it
     moves down. 863 and not 1898 because it is the closed clip that takes up
     space; what opening adds is already carried by `--expand`, which now
     scales the same way. */
  --grow: calc(863 * (var(--kp) - var(--k)));

  /* ...and ONLY when needed, which is when the block is pinned to the window.
     If the file fits whole, the block is taller than the screen and is
     traversed by scrolling: the bar passes over it as it passes over
     everything else, and reserving a gap only served to pull the piece, its
     caption and `vision` 90px away from their file position (Denis,
     25-ago-2026: 1:1 at the file's resolution, and it adjusts from there).
     The `clamp` is a step, not an interpolation: while the window has the
     1075 the group needs (985 + 90) the first term is negative and yields 0;
     the moment one pixel is missing, that term times a thousand overshoots
     and the full gap comes out. */
  --nav: clamp(0px, calc((1075 * var(--k) - 100svh) * 1000), calc(90 * var(--k)));

  /* The Serena piece: IT FITS AND KEEPS ITS PROPORTION (Denis, 25-ago-2026),
     chosen among the three things that could not all hold at once — reaching
     both edges, keeping the file's proportion and fitting in the window.
     HEIGHT rules, and the width follows it at the file's proportion
     (1115x920), so the room never comes out stretched. The available height
     is the window minus the bar and minus the 65 the caption occupies below
     the photo (985 of the group - 920 of the photo). Never larger than the
     file: when it fits, it is 1:1.
     What this costs, stated: in a wide, short window the block does NOT reach
     both edges — at 1440x820 it measures 806 wide. That is what was traded
     for not deforming the room. */
  --sm-h: min(calc(920 * var(--k)), calc(100svh - var(--nav) - 65 * var(--k)));
  /* 1197 and not 1115 since 27-ago-2026 at 15:36. The file redid this piece:
     `2097:349` goes from `405,5251 1115x920` to `283,5251 1197x920`, i.e. it
     widens by 82 and shifts 122 to the left. Its right edge goes from 1520 —
     the canvas edge — to 1480: THE SAME 40 OF PADDING as the `cómo será`
     image and the projects grid. Read live: 11 nodes moved and NONE with its
     text changed. */
  --sm-w: calc(var(--sm-h) * 1197 / 920);

  /* How much the group shrinks relative to the file, which is how far
     everything below it moves up so no gap is left. */
  --fit: calc(920 * var(--k) - var(--sm-h));

  /* ── EQUALIZED VERTICAL RHYTHM (Denis, 27-ago-2026 16:07) ────────────────
     "igualar los espaciados entre las vistas o pantallas y que no difiera
      mucho entre ellas. lo minimo es que quepa entre las pantallas el navmenu"

     A "screen" here is a group of blocks separated from the next by a
     full-width BLANK BAND. Measured on the pixel at 1520x1117 — not on the
     DOM box, which does not say where the paint ends — the file's seven
     bands were:

       home -> somos     265        projects -> 360     146
       somos -> solucion 189        360 -> faq          119
       solucion -> caso  192        faq -> fin          360
       caso -> projects  315

     From 119 to 360: three times over. The target is 190 and it is NOT an
     invented number: it is the one the file itself repeats in two of the
     seven (189 and 192). It sits well above the floor the brief asks for —
     the navmenu measures 90 — so the "the menu must fit" condition is met
     with double margin.

     Each variable is its screen's ACCUMULATED SHIFT relative to the file.
     They are added to the `top` of the canvas's eleven blocks.

     TO REVERT THIS: `--rhythm: 0`. The seven corrections cancel and the page
     returns exactly to the Figma coordinates. It is one line.

     WHAT IT COSTS, stated: below `somos` the page is no longer at the file's
     vertical coordinates BY INSTRUCTION. Pixel parity still measures —
     `zones-corpus.json` separates `y` (the reference) from `pageY` (the
     page), which is exactly for this — but `geom-corpus.json` no longer
     compares against Figma on the Y axis: it compares against Figma PLUS the
     correction, and the rhythm's owner is `ritmo.js`. */
  --rhythm: 1;
  --r1: calc(var(--rhythm) * -75 * var(--k));   /* somos                      */
  --r2: calc(var(--rhythm) * -74 * var(--k));   /* solucion + about           */
  --r3: calc(var(--rhythm) * -76 * var(--k));   /* cuaderno + caso            */
  --r4: calc(var(--rhythm) * -201 * var(--k));  /* projects + formatos        */
  --r5: calc(var(--rhythm) * -157 * var(--k));  /* 360 + vision               */
  --r6: calc(var(--rhythm) * -86 * var(--k));   /* faq + the lower `let's talk` */
  /* -260 and not -256 since 28-ago-2026, and the reason is measurable: the
     band is measured ON THE PIXEL, and the FIN photo carried
     `filter: blur(2px)`, which paints outside its box. Those ~4px of halo
     entered the band and kept it at 190 with -256. `contact` replaces FIN and
     carries no blur, so with -256 the band came to measure 194 in six of the
     eight windows. That is: the earlier 190 counted a halo; this one counts
     the block. */
  --r7: calc(var(--rhythm) * -260 * var(--k));  /* the last screen            */

  /* THE ABOUT PHOTO IS A SCREEN: it is window-height tall, the width follows
     in proportion and it sits flush against the window's RIGHT edge.
     Three instructions from Denis on 26-ago-2026, in this order:
       19:24  "hazla full height, que detecte el tamaño del height del browser"
       19:27  "la imagen tambien debe de estar alineada a la derecha. al end"
       19:48  "manten altura y el width proporcional... y añade el espacio
               correspondiente entre las dos pantallas, es decir espacio entre
               notebook y this will be. Que sea el mismo tamaño que tiene el
               navbar superior"
     The 19:48 one reverts what I decided at 19:37 — fixed width at the file's
     828 — and rightly his: he decides it. What that reasoning DID get right
     is that the proportional width does not always fit, hence the second cap
     below.

     AND MIND THE RIGHT EDGE: the file puts 40 of margin in both frames
     (1520-40-710 and 1849-40-828), and that margin was settled by Denis
     himself on 25-ago. The "al end" undoes it. LET IT BE SAID THAT THIS IS
     HIS DECISION AND NOT THE FILE'S.

     `--solution-h` is the minimum of three things, and none is an invented number:

       1. `100svh` — what was asked: the window.

       2. THE BAR GAP, TOP AND BOTTOM AND THE SAME (Denis, 20:40: "entre
          cada pantalla hay un espacio arriba abajo, mira el espacio de esta de
          separacion y aplicalo"). The free band between the two neighbouring
          screens runs from 1185 — where the `archviz inmersivo` photo ends —
          to 2499, where `cuaderno` starts: 1314. Leaving 90 per side gives
          1314 - 2 x 90 = 1134.
          1133 AND NOT 1134 SINCE 27-ago-2026 16:07: with the equalized rhythm
          `somos` moves up 75 and `cuaderno` 76, so that band comes to measure
          1313 and the cap drops with it. With 1134 in place the gaps came out
          89.5, and `foto-about.js` demands they be EXACTLY 90.
          The 90 is the top navbar measured in the file: `Menu` #204:35 sits
          at 39 and is 51 tall, so it occupies 0 to 90 from the screen edge —
          the same 90 this file already calls `--nav`.
          Before 20:40 the cap was 1124 and the gaps came out 100 above and 90
          below, because the photo centred on the BLOCK's centre (1847) and
          not the band's (1842). Now it centres on the band and both gaps are
          equal at any window height, not only at the cap.

       3. THAT IT DOES NOT EAT THE ABOUT BODY. The body ends at 628 of canvas
          and the photo grows leftward from its right edge, which since 20:40
          sits at 40 from the window: `1520 + air - 40`. Keeping the 24 of gap
          asks `width <= 828 * k + air`. In height: `x 920/828`.
          And that 828 is no accident, but NEITHER IS IT A FRAME: at a 1520
          window the cap leaves the photo at 828 x 920 with its right edge at
          1480, and that is a COMBINATION of the two frames, not a copy of
          one. The 828 x 920 size is that of `#2220:48` (the 1849 frame); the
          right edge at 1480 is that of `#2015:10` and `#2088:31` (the 1520
          ones), which instead draw the photo 710 x 789. So the 40 of margin
          IS literal to the design window; the size comes from the other
          frame, per the "full height" of 19:24. Let it be said.

     What this costs, stated and not hidden:
       · at 1520 wide the photo stays at its 920 even if the window gives
         more: with the 40 padding the width cannot exceed 828, and the height
         follows. Which is the size the file gives it in the 1849 frame, not
         the 1520 one (710 x 789): for the design window the literal thing is
         the margin, not the size.
       · above 1134 of height it STOPS following the window, because the gap
         between screens rules. At 2560x1440 the photo measures 1134, not 1440.
       · if what is wanted is TRUE full height at any height, everything after
         this block — `caso`, `projects`, `formatos`, the 360, `vision`,
         `faq`, `FIN` — must be pushed down, and that turns the canvas's
         vertical rhythm, fixed today, into something depending on the window
         height. That is another conversation and I do not take it as had. */
  --solution-h: min(100svh,
               calc(1133 * var(--k)),
               calc((828 * var(--k) + var(--fl-bleed)) * 920 / 828));

  position: relative;
  width: 100%;
  max-width: 1520px;
  /* 6833 up to the last screen + the 920 `contact` measures, which since
     28-ago-2026 IS the last one: it replaces FIN, it does not stack behind
     (Denis: "es el sustituto de la ultima pagina"). Before there was a
     `100svh` here, which was the window that ate the footer.
     `--r7` because that slot rises with the equalized rhythm: without it, the
     canvas kept the file's 7950 and the page ended with 252px of dead white
     below. Measured. */
  height: calc(6833 * var(--k) + var(--nav) + 920 * var(--k) + var(--expand) - var(--fit) + var(--grow) + var(--r7));
  margin: 0 auto;
  /* No clipping, so FIN and the full-screen state of ¿Qué somos? can bleed.
     They are the ONLY two blocks that leave this canvas (FIN is already born
     at -41 with 1594 of width), so nothing else escapes, and whatever
     overhangs the sides is cut by body's `overflow-x: hidden`. */
  overflow: visible;
  background: #fff;
}

.fl-corpus > * {
  position: absolute;
  margin: 0;
}

/* Every node is placed by its Figma y, so no element may bring a margin of its
   own — a UA `1em` on a <p> lands the box a whole font-size below the design. */
.fl-corpus :is(h1, h2, p, figure, figcaption) {
  margin: 0;
}

.fl-corpus img {
  display: block;
}

.fl-a11y {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A shared bit of `desk2026.css` that only got its `position` from the home
   frame's `> *` rule; the corpus places it itself. */
.fl-menu {
  position: absolute;
}

/* ── Menu #204:35 — 69,39 · 1382x51 ───────────────────────────────
   The dark twin of the home nav: black text, Light 300 instead of Regular,
   one link in SemiBold, and its own link offsets. The file draws that SemiBold
   on `studio`, which reads as a current-page mark; on a site that is one
   scroll it marks the section under you instead (Denis, 2026-08-05), so it is
   `corpus.js` that says which. At the corpus's own top that is `studio`
   again — the state the file draws. */

.fl-menu {
  left: calc(69 * var(--k));
  top: calc(39 * var(--k));
  width: calc(1382 * var(--k));
  height: calc(51 * var(--k));
}

/* The bar fades out over the last screen. `contact` (#2207:34) is the only
   one in the file that draws its nav `visible: false`, so no bar goes there.
   `--last` is written by `effects.js` with the scroll — 0 while the FAQ still
   shows, 1 when the last screen fills the window — so the fade rides the
   finger and not a clock of its own, which is the rule for the rest of the
   page. `is-out` removes it entirely at the end: an invisible bar that
   still eats clicks is worse than a bar. */
.fl-menu {
  opacity: calc(1 - var(--last, 0));
}

.fl-menu.is-out {
  visibility: hidden;
  pointer-events: none;
}

.fl-menu a {
  position: absolute;
  top: calc(11 * var(--k));
  font-size: calc(16 * var(--k));
  font-weight: 300;
  color: #000;
  text-decoration: none;
  transition: opacity 0.25s ease;
}

.fl-menu a:focus-visible {
  opacity: 0.6;
}

.fl-menu a[data-nav='studio'] { left: 0; }
.fl-menu a.is-here { font-weight: 600; }
.fl-menu a[data-nav='projects'] { left: calc(119 * var(--k)); }
.fl-menu a[data-nav='formatos'] { left: calc(230 * var(--k)); }
.fl-menu a[data-nav='cuaderno'] { left: calc(1192 * var(--k)); }
.fl-menu a[data-nav='contact'] { left: calc(1329 * var(--k)); }

.fl-menu .fl-logo {
  left: calc(620 * var(--k));
  top: 0;
  color: #000;
  font-weight: 400;
}

/* ── the same bar over DESKHome (#2074:53 — 68,16 · 1387x48) ───────
   One menu now serves the whole page, and the two frames draw it in two
   different places: the home at 68,16 with Regular links and nothing marked
   current, the corpus at 69,39 with Light links and `studio` in SemiBold.
   While the hero is what the bar is over, it is drawn as the home draws it —
   `effects.js` toggles the class. Colour is not set here: the sampler already
   paints it white over the photograph, which is what the frame draws.
   No transition on purpose: the links are lens hosts, and a bar that glides
   moves them without an event the lens listens for. */
.fl-menu.fl-menu-fixed.is-home {
  left: calc(var(--fl-bleed) + 68 * var(--k));
  top: calc(16 * var(--k));
  width: calc(1387 * var(--k));
  height: calc(48 * var(--k));
}

.fl-menu.is-home a,
.fl-menu.is-home .fl-logo { color: #fff; }

.fl-menu.is-home a { top: 0; font-weight: 400; }
.fl-menu.is-home a[data-nav='projects'] { left: calc(114 * var(--k)); }
.fl-menu.is-home a[data-nav='formatos'] { left: calc(235 * var(--k)); }
.fl-menu.is-home a[data-nav='cuaderno'] { left: calc(1205 * var(--k)); }
.fl-menu.is-home a[data-nav='contact'] { left: calc(1334 * var(--k)); }
.fl-menu.is-home .fl-logo { left: calc(625 * var(--k)); }

/* Until 28-Aug-2026 the bar LANDED on the footer's nav (`--end`, driven by
   effects.js; Denis, 6-ago-2026: "quítalo y que quede el de la animación").
   `contact` replaced FIN and the handover, its offsets and the footer bar
   left in the final audit of 3-sep-2026 — they rendered nothing. */
.fl-menu.fl-menu-fixed {
  width: calc(1382 * var(--k));
}

/* ── ¿Qué somos? #2026:15 — 283,265 · 954x920 ─────────────────────── */

.fl-studio {
  left: calc(283 * var(--k));
  top: calc(265 * var(--k) + var(--r1));
  width: calc(954 * var(--k));
  height: calc(920 * var(--k));

  /* THE FULL-SCREEN STATE'S HEIGHT STAYS AT THE FILE'S 920, and not out of
     laziness. Denis, 25-ago: "tal vez ajustar el height?" — it was tried,
     twice, and both run into the same thing: above 1520 `--k` is capped at 1,
     so the file's vertical rhythm is fixed and this block has no room of its
     own to grow into. `.fl-studio` occupies 265..1185; above there is 265 of
     air up to the hero and below 267 down to `.fl-solution`. Growing eats one
     of the two, and neither is "free space": they are separations the file
     draws.
       - With the window as ceiling (4cf5d8a): at 2560x1440 the photo ended at
         y=1705 and `Solucion` starts at 1452 — 253px of photo on top of its
         tag and its image. Denis saw it on screen before any check did.
       - Both ways with ceiling 1452: at 3440x1440 it left 7px to the tag.
       - Upward only with ceiling 1185: perfect bottom gap (267), but the
         photo ends up welded to the hero, no cut between two full-bleed
         images.
     Which white gets sacrificed is a design decision and there is no number
     in the file to back it, so it is not invented here. What IS fixed — and
     was the firm part of the brief — is WHERE it crops. */
}

/* The photo is one crop shown through two windows. Page state (#2004:49) takes
   62.76% of the source starting at 17.30%; the full-screen state (#2074:26)
   takes all of it from 0. Rendered at a constant 1520 those fractions are a
   954-wide window starting 263px in, and the whole 1520 from 0 — so the image
   never scales, the window widens and the image slides 263 back to 0. */
.fl-studio-frame {
  position: absolute;
  top: 0;
  height: calc(920 * var(--k));
  overflow: hidden;
}

.fl-studio-img {
  position: absolute;
  top: 0;
  height: calc(920 * var(--k));
  object-fit: cover;
}

.fl-studio-h {
  position: absolute;
  left: calc(285 * var(--k));
  /* Hangs off the frame height in proportion (377/920), not off the fixed
     920, or as the photo grows the title would stay in the top third. With
     the file height the calculation returns exactly its 377: no jump. */
  top: calc(377 * var(--k));
  font-size: calc(48 * var(--k));
  color: #fff;
}

.fl-studio-sub {
  position: absolute;
  left: calc(436 * var(--k));
  top: calc(443 * var(--k));
  font-size: calc(16 * var(--k));
  font-style: italic;
  letter-spacing: var(--fl-track);
  color: #fff;
}

/* ── Solución #2077:115 — 40,1452 · 1440x789 ──────────────────────── */

.fl-solution {
  left: calc(40 * var(--k));
  top: calc(1452 * var(--k) + var(--r2));
  width: calc(1440 * var(--k));
  height: calc(789 * var(--k));
}

/* Raw API (`/v1/files/.../nodes`, which carries what the MCP's simplified tree
   drops): `LAYER_BLUR` radius 11.6 — Figma's radius is twice the CSS one, so
   `blur(5.8px)` — a fill opacity of 0.55, a white stroke at **weight 0** (i.e.
   no stroke at all; it is not drawn here) and a `TEXTURE` grain that is not
   built (see the map). The blur is the *initial* state: the PANTALLA COMPLETA
   section says it clears when the block is fully in view. */
/* The file leaves it at 1480 of 1520 — the block starts at 40 and the photo
   at 730 of its 1440 — i.e. with 40 of margin on the right. That margin IS
   the design. Taking it full bleed to the window edge was tried (Denis,
   25-ago-2026) and withdrawn at his request the same day: the file's side
   margin rules over the bleed. The margin still stands; what changes below is
   the height.

   HEIGHT MATCHED TO `archviz inmersivo`'s (Denis, 26-ago-2026: "el mismo
   height y el width adaptativo al tamaño q salga"). `.fl-studio-img` measures
   920 x `--k` and never moves — not with `--full` nor at full bleed on
   ultrawide — so neither does this height: 789 -> 920. The width comes from
   the photo's own ratio, 710/789, which at 920 gives 827.9.

   828 x 920 is not a rounding of mine: it is LITERALLY what `#2220:48` draws,
   the 1849 variant of `about - solución` that appeared in the file that same
   day — same `imageRef` and same crop matrix, scaled x1.1661 to fill the 920
   of height. The instruction and the file say the same thing.

   The 131 of growth is symmetric about its own centre, -65 up and +66 down,
   which is how `#2220:46` splits it relative to the `this will be` (photo at
   0, tag at 65). That is why the `top` is negative: the photo peeks above the
   block's box.

   AND SINCE 26-ago-2026 19:24 THAT 920 IS ONLY THE CASE OF A 920 WINDOW:
   the height is set by `--solution-h`, i.e. the window. The 920 remains the file's
   value and the one that comes out when the window measures 920 — which is
   exactly the height of the `about - solución` frame, 1849x920. The symmetric
   split does not change. */
.fl-solution-img {
  position: absolute;
  /* AT 40 FROM THE WINDOW, not full bleed (Denis, 26-ago-2026 20:40: "añade
     el espacio que le corresponde a la imagen en su end, el padding"). The 40
     is the margin the file gives it in BOTH frames — 1480 of 1520 and 1809 of
     1849 — so the number is its own, not mine. `.fl-solution` ends at 1480 of
     canvas, so removing the centred canvas's air leaves its right edge
     exactly 40 from the window. Anchored on the right: it grows leftward and
     that edge never moves. */
  right: calc(-1 * var(--fl-bleed));
  /* CENTRED ON THE BAND BETWEEN THE TWO NEIGHBOURING SCREENS, not on the
     block's centre: 1842 is the midpoint between 1185 (where the `archviz
     inmersivo` photo ends) and 2499 (where `cuaderno` starts), and 390 is
     that 1842 seen from the block's top edge. That way the top and bottom
     gaps are equal at any height, which is what was asked; centring on the
     block gave 100 and 90.
     It costs 5px against the file's `-65` (which put it at 1387 at 920 tall,
     and now it lands at 1382). Stated, because it is a departure of mine
     between what the file draws and what was requested.
     `(r1 + r3) / 2 - r2` SINCE 27-ago-2026 16:07: with the equalized rhythm
     its two neighbours do not rise the same — `somos` 75 and `cuaderno` 76 —
     so the band's centre drops 75.5 and the block only 74. With the bare 390
     the gaps came out 198 and 195, and this asks that they be equal. Written
     with the variables and not with a hard-coded -1.5: if the rhythm changes,
     the photo re-centres itself. With `--rhythm: 0` the three terms are 0 and
     this is the 390 of before. */
  top: calc(390 * var(--k) + (var(--r1) + var(--r3)) / 2 - var(--r2) - var(--solution-h) / 2);
  /* Proportional to the height, at the photo's own proportion: 828/920
     (Denis, 19:48). At a 920 window that is the file's 828, exactly. */
  width: calc(var(--solution-h) * 828 / 920);
  height: var(--solution-h);
  object-fit: cover;
}

.fl-solution-h {
  position: absolute;
  left: 0;
  top: calc(87 * var(--k));
  width: calc(657 * var(--k));
  line-height: 1.2; /* lineHeightPx 38.4 in the source, not the 38 I derived */
  color: #000;
}

/* `this will be _`, `cuaderno _`, `fromato` — a Gloock label with a short rule
   set off to its right. Same shape three times, different lengths. */
.fl-tag {
  position: absolute;
  left: 0;
  top: 0;
  height: calc(29 * var(--k));
}

.fl-tag span {
  font-size: calc(24 * var(--k));
  color: #000;
}

.fl-tag::after {
  content: '';
  position: absolute;
  left: calc(var(--rule-x, 138) * var(--k));
  top: calc(var(--rule-y, 24) * var(--k));
  width: calc(45 * var(--k));
  height: calc(var(--rule-w, 2) * var(--k));
  background: #000;
}

/* ── About body — 162,1659 · 466x528 ──────────────────────────────
   The box comes from `about - solución` (#2132:19, 466x528), not from the
   in-page node (#2128:16, 466x552): since 26-ago-2026 the file says two
   different things here and Denis chose the first. 528 = 18 lines of 24 plus
   the four 24 gaps between paragraphs; the new text takes one line less than
   the old one. */

/* AT THE PADDING-START'S 40, and no longer at the file's 162. Denis,
   28-ago-2026 16:39: "quiero q el texto de como sera y el bloque de abajo, en
   vez de alinearse con la img, se aliene a la izquierda, mira el padding
   start de figma".

   Why it read as "aligned with the img": the body started at 162 and is 466
   wide, so it ended at 628, 24px from the photo's left edge (652). The
   full-screen headline also ends at 628 — that is where its travel goes — so
   both blocks hung off the photo on the right while the tag stayed alone in
   the margin. Measured on the page, not deduced.

   The 40 is the file's padding-start and not a number of mine: `2077:115`
   lands at 40 inside `DESKcorpus`, the `this will be` tag lands at 40 inside
   the 1849 `about - solución` (#2220:46) and the photo carries its 40 on the
   other side in all three frames. Same figure on both margins.

   IT IS A DEPARTURE FROM THE FILE, stated: the file draws this body at 162
   (`2128:16` in page, `2132:19` in the 1520 frame) and at 338 in the 1849
   one. The instruction rules. Going back is this number.

   POSITION ONLY. The headline's travel (`.fl-solution-h`, further down) is
   untouched: Denis, 17:06, "en ningun momento [te pedi] que quitases la
   animacion". Consequence, stated: at full screen the headline still stops at
   628 and the body no longer reaches there, so they stop sharing a right
   edge. */
.fl-about {
  left: calc(40 * var(--k));
  top: calc(1659 * var(--k) + var(--r2));
  width: calc(466 * var(--k));
  height: calc(528 * var(--k));
  font-size: calc(20 * var(--k));
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  color: #000;
  white-space: normal;
}

/* THE BODY NO LONGER TRAVELS, and the 24 it travelled has a name: it was the
   headline's growth. Denis, 26-ago-2026 20:40: "el texto pegalo a la misma
   distancia q esta en el figma".

   The arithmetic, and it comes out exact. In the file the gap between
   headline and body is 43 in BOTH states:
     in page  headline 32 -> box 2 x 32 x 1.2 = 76.8, from 87 to 163.8; body
              at 207  ->  43.2
     at full  headline 42 -> box 100.8, from 87 to 187.8; body at 231 -> 43.2
   So the body's 24 was EXACTLY the 24 the headline grew (100.8 - 76.8 =
   24.0): the file keeps the gap, it does not move the paragraph.
   Since 20:25 the headline does not grow, so a body that keeps dropping 24
   opens the gap to 67 — which is what Denis is seeing. Without the travel, it
   returns to 43.2 in both states, which is the file's distance.
   If the 32 -> 42 ever comes back, this travel comes back with it. */

.fl-about p {
  margin: 0 0 1.2em;
}

.fl-about p:last-child {
  margin-bottom: 0;
}

/* ── cuaderno #2152:43 / :46 / :47 — 1135,2499 ────────────────────── */

.fl-notebook {
  left: calc(1135 * var(--k));
  top: calc(2499 * var(--k) + var(--r3));
  width: calc(282 * var(--k));
  height: calc(274 * var(--k));
}

.fl-notebook-tag {
  --rule-x: 119;
}

.fl-notebook-body {
  position: absolute;
  left: 0;
  top: calc(40 * var(--k));
  width: calc(251 * var(--k));
  font-size: calc(20 * var(--k));
  font-weight: 300;
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  color: #000;
  white-space: normal;
}

/* 33,246 = 1168,2745 of the canvas. That row was rewritten by the file: where
   `Indice de articulos` stood with its arrow BEHIND (1257 and 1397) there is
   now a quote — `4855dd9d-…37642a64`, "La innovacion que marca la diferencia
   no es la ultima herramienta" — with the arrow IN FRONT. `Indice de
   articulos` no longer exists anywhere in the file: sweeping the page's 240
   texts on 31-ago, the four matches for "indice" fall inside the `letter*`
   boards (the notebook page's filter), none in the corpus.

   On 27-ago the PLACEMENT was copied and the old copy kept because it was
   wired in "and it does not go without him saying so". Denis says it on
   31-ago: "seccion indice de articulos, ha cambiado, miralo, el q tiene la
   flecha al lado en penpot". So now the copy is the file's too, and the key
   goes from `notebook.index` to `notebook.featured`: it is no longer the
   index, it is the featured entry. The three non-Spanish languages have NO
   design source — the file only carries the quote in Spanish — and go
   translated: that is invention, and it is stated in the report.

   276 and `white-space: normal` because the file's quote is TWO lines:
   box 276x39, textBounds 270.18x38.4 = 2 x 19.2, and `.fl-text` is `nowrap`
   (`desk2026.css:146`). The break falls at "…la diferencia / no es la
   ultima…". */
.fl-notebook-link {
  position: absolute;
  left: calc(33 * var(--k));
  top: calc(246 * var(--k));
  width: calc(276 * var(--k));
  font-size: calc(16 * var(--k));
  font-weight: 300;
  font-style: italic;
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  color: #000;
  text-decoration: none;
  white-space: normal;
}

/* #2152:49 — an arrow, not a rule: the API reports its box as 20x0.01 because
   that is the shaft, and the head falls outside. The node's own SVG export is
   21x8 with the shaft at y 3.77, so the box is offset back by that much.
   Verified on 27-ago: `arrow-right.svg` and the node's export are the same
   path — what was wrong was not the drawing, it was the side.

   -32 and 6.23: the file puts the shaft at 1136,2755 and the text at
   1168,2745, i.e. the arrow 32 to the left of the text and its shaft 10 below
   the box's ceiling. The same 32 and 10 in both states (`2238:130` over
   `2238:131` at full screen), so they are not two loose numbers: they are the
   relation the file maintains. */
.fl-notebook-link::before {
  content: '';
  position: absolute;
  left: calc(-32 * var(--k));
  top: calc(6.23 * var(--k));
  width: calc(21 * var(--k));
  height: calc(8 * var(--k));
  background: url('/assets/images/2026/arrow-right.svg') center / contain no-repeat;
}

/* ── Caso Real #2077:114 — 0,2509 · 1115x941 ──────────────────────── */

/* Full bleed on ITS side (Denis, 17-ago-2026: "arreglar los blancos
   laterales"). The file draws this block flush against the canvas's left edge
   — x=0 — so above 1520 that side is carried to the window edge. The right
   keeps its margin, which the file also draws: it is Valerie's rule 3 as-is,
   and that is why it is not the same as filling the screen.

   The height does not change, as in the other full-bleed blocks: the photo
   grows in width and its `cover` crops, so the coordinate canvas does not
   move and this block does not push the next one. */
.fl-case {
  left: calc(-1 * var(--fl-bleed));
  top: calc(2509 * var(--k) + var(--r3));
  width: calc(1115 * var(--k) + var(--fl-bleed));
  height: calc(941 * var(--k));
}

/* Same window trick as ¿Qué somos?, with a much smaller move: the page crop is
   97.43% from 1.22%, the full one 99.70% from 0.15%, so at a constant 1141 the
   page window is 1115 wide starting 12.2px in. */
.fl-case-frame {
  position: absolute;
  left: 0;
  top: 0;
  height: calc(847 * var(--k));
  overflow: hidden;
}

.fl-case-img {
  position: absolute;
  top: 0;
  width: calc(1141 * var(--k) + var(--fl-bleed));
  height: calc(847 * var(--k));
  object-fit: cover;
}

/* THE SAME DOWNWARD CROP AS IN ¿QUE SOMOS? (Denis, 25-ago-2026: "en
   ultrawide la imagen hace un corte muy raro, deberia al menos alinearse
   hacia abajo"). Here the box widens with the bleed and the height stays
   pinned at 847, so above 1520 `cover` discards height: measured, 383px at
   2560 and 857px at 3840, half at the top and half at the bottom. Anchoring
   at the bottom loses sky and not both ends.
   In a media query and not a `calc` over `--fl-bleed` so it is stated beyond
   doubt: below 1521 this rule does NOT exist, and nobody touches the 1:1 with
   the frame at 1520. */
@media (min-width: 1521px) {
  .fl-case-img {
    object-position: 50% 100%;
  }
}

/* THE CAPTION FOLLOWS THE PHOTO, NOT THE CANVAS (Denis, 25-ago-2026: "casa
   dolmen el titulo debe estar alineado con el edge izquierdo igual que
   projects, para que no haga nada raro al escalar la vista"). The block goes
   full bleed on the left, so its photo starts at the WINDOW edge; giving the
   bleed back to the caption left it floating mid-image — 362 at 1920, 682 at
   2560 and 1122 at 3440, ever farther from what it belongs to.
   The file's 162 is now measured against the same edge as in the file: the
   photo's. Below 1520 nothing changes, which is where the canvas already
   fills the window. */
.fl-case-cap {
  position: absolute;
  left: calc(162 * var(--k));
  top: calc(897 * var(--k));
  color: #000;
}

.fl-case-cap span {
  display: block;
}

.fl-case-cap .fl-display {
  font-size: calc(24 * var(--k));
  line-height: 1.2;
}

.fl-case-cap .fl-text {
  font-size: calc(15 * var(--k));
  font-weight: 300;
  letter-spacing: var(--fl-track);
  line-height: 1.2;
}

/* ── projects #2074:59 — 0,3756 · 1520x1898 ───────────────────────── */

.fl-projects {
  /* To the window's real edge. The 40 of padding does not go here but on the
     rows, unscaled; the long why lives at `--kp`. */
  left: calc(-1 * var(--fl-bleed));
  /* 3756, not 3718: the file moved this group (-103, +38) on 26-ago-2026 and
     of the two coordinates only the x was absorbed. The missing 38 ate the
     top gap (file's 306 -> 268) and handed it to the bottom one (55 -> 93).
     Denis, 27-ago: "deja los tamaños de espacio como en figma" — meaning the
     gaps between views are NOT regularized, they are copied, and the file
     gives 175 / 267 / 258 / 306 / 55 / 147 / 106 / 221. With this line all
     eight match. Read live from `2074:59` on 27-ago at 10:50. */
  top: calc(3756 * var(--k) + var(--r4));
  /* `80 * --k + 1440 * --kp` and not `1520 * --kp`: the two 40 margins are
     fixed and only the content scales. With `1520 * --kp` the box measured
     3547 at 3440 and overflowed the window. */
  width: calc(80 * var(--k) + 1440 * var(--kp));
  height: calc(1898 * var(--kp));
}

.fl-projects-h {
  position: absolute;
  /* The heading does NOT scale with the grid: it lives on the canvas and is
     typography. But it does not get the bleed back either: same criterion as
     the Casa Dolmen caption (25-ago-2026), its 162 is measured against the
     real left edge, which is where the grid starts. Giving it back put it at
     1122 at 3440, while its first photo started at 93. */
  left: calc(162 * var(--k));
  top: 0;
  font-size: calc(24 * var(--k));
  color: #000;
}

/* #2093:98 — a solid white 1520x1035 rectangle sitting over the rows from y 863
   down. It is not artwork: it is how the design draws the collapsed gallery,
   so it is built as the clip it stands for rather than painted on top. Rows 3
   and 4 live below the cut and only `seguir viendo` brings them into view. */
.fl-rows {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(80 * var(--k) + 1440 * var(--kp));
  /* 863 closed, 1849 open — the 986 between them are derived at `--expand`,
     up in `.fl-corpus`. */
  height: calc(863 * var(--kp) + var(--expand));
  overflow: hidden;
}

/* A drawer, not a jump (Denis, 2026-08-05) — and since 31-ago-2026 a
   JS-driven one, not a transition: corpus.js walks `--sv` (same 0.65s, same
   curve) and holds the reader's viewpoint. The why lives at the `--sv`
   declaration in `.fl-corpus`. */

/* Each row is its own group in the design and the tiles inside it are staggered
   by hand — different heights, different y offsets, no grid. The offsets ride
   on custom properties so the HTML carries the four numbers Figma gives per
   tile and the CSS stays one rule. */
.fl-row {
  position: absolute;
  /* The rows are placement boxes, not surfaces: the previous row's tall tiles
     peek below the next row's box, and the — transparent — box ate their
     hover and click in that strip (Denis, 01-sep-2026: the APThotel tile dead
     from ~397px down). The pointer passes through the row and its tiles
     recover it. */
  pointer-events: none;
}

.fl-row > * {
  pointer-events: auto;
  position: absolute;
  left: calc(var(--x) * var(--kp));
  top: calc(var(--y) * var(--kp));
  width: calc(var(--w) * var(--kp));
  height: calc(var(--h) * var(--kp));
}

/* Each grid slot is now a button: entering a project is an action, and as a
   button it gets focus, the keyboard and — via its photo's `alt` — the
   accessible name, without inventing a role or a `tabindex`. The file's
   geometry stays where it was, on the node occupying the slot, which is now
   the button; the photo fills it. */
.fl-tile {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  overflow: hidden;
  cursor: pointer;
}

.fl-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── the caption when pointing at a tile ───────────────────────────
   IT NOW COMES FROM THE FILE, and before it did not. What stood here said the
   file drew no name over any of the fifteen photos: that was half true. The
   28-ago sweep looked INSIDE `projects` and the two nodes are parented in
   `formatos` — a grouping slip in the source, already noted in
   `figma-2026-map.md:419` — so they did not show up. Measured in the 26-ago
   re-read:

     · `Serena Materia` #2207:28 — Gloock 400 14/0.04em, white
     · `fluint studio`  #2207:31 — Lato  400 11/0.04em, white
     · over the Onix tile (frame 667,3858 · 345x390) and with the `_Mouse`
       cursor #2207:22 at its top-right corner: it is a HOVER, not a
       permanent caption.

   It is the same two-line box as `Caso Real` — `Casa Dolmen` Gloock 24 +
   `Lombardo arquitectura` Lato 15 — at a third of the size, and that is why
   it uses the same `.fl-display` / `.fl-text` as that one instead of fonts of
   its own.

   THE TWO POSITION NUMBERS: +21 from the tile's left edge and 102 above its
   bottom edge, which is where the FIRST line lands.

   THE 21 REPLACES A 124 THAT NO LONGER EXISTS. The 124 came from the 26-ago
   map, when the Onix tile was at 667 and the text at 791. In the 31-ago
   reimport the grid moved — Fila001 is now 41 / 405 / 770 / 1135 — and the
   text did NOT: it is still at 791, i.e. 21 from its tile. Measured in
   today's Penpot, not deduced. That closes the question the map left open
   (`figma-2026-map.md:1323`, inset or centred): at 21 from a 345 tile no
   centring holds, and it matches Denis's "bottom left".

   `--kp` and not `--k` in the position: the tile is a `--kp` object and above
   1520 the two scales part ways, so with `--k` the caption would detach from
   its photo. The font size does go in `--k`, like all the site's text.

   WHAT FALLS AWAY: the 45x0.5 rule the previous version carried. It came from
   the Serena tour (`.bath-option`), which is ONE line and centred; the file
   does not draw it under this box. Putting it back is this block again.

   AT REST THERE IS NOTHING, and it is not laziness: that way the resting
   frame remains the file's and the `projects` pixel diff measures the same as
   it did. `@media (hover: hover)`: on touch a `:hover` sticks after tapping.

   THE VEIL IS NOT MINE EITHER, and until 31-ago-2026 this said it was. The
   file DOES darken the photo on hover; what was wrong was the number, which
   came from a contrast calculation and not from a measurement.

   MEASURED on the `204:31` frame export at scale 1 (26-ago), comparing the
   hover tile against its two resting neighbours — same export, same photo,
   same size, so the neighbours are the control:

     tile        design    served    ratio
     apthotel    137.01    137.80    0.994   <- control, no veil
     fachada      94.78     94.27    1.005   <- control, no veil
     onix         96.24    118.44    0.8125  <- the hover one

   (mean of the sRGB channel without linearizing: the browser's alpha
   compositing happens in that space, so the ratio IS 1-alpha directly.)

   The two controls agree to 0.6%, and the pointed tile comes out 0.8125 ->
   alpha 0.1875, built as 0.19. And it is FLAT: by quarter-height bands it
   gives 0.823 / 0.798 / 0.804 / 0.820, i.e. neither gradient nor vignette.

   WHAT THIS COSTS, stated: 0.19 does NOT guarantee 4.5:1 for white over the
   photo. That needed 0.557, which is where the previous 0.56 came from. It is
   lowered because Denis asked for 1:1 with the file (31-ago-2026) and the
   file puts 0.19. It is fidelity beating an invention of mine, with the
   trade-off written down.

   PENDING: the 0.19 is measured on the 26-ago export, not read from the fill
   in today's Penpot — the plugin was down. When it connects, the fill gets
   read and the number corrected if it is not exact. */

.fl-tile::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(0, 0, 0, 0.19);
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

/* `text-align: left` IS NOT REDUNDANT. The tile is a `<button>` and the
   browser stylesheet gives it `text-align: center`, inherited here: the two
   `span`s are blocks the caption's width — the longest line's — and the
   shorter second line came out CENTRED within that block, 43 from the tile
   edge instead of 21. The file puts both at x 791, i.e. left-aligned (Denis,
   31-ago-2026: "esta alineado a la izq todo, no fluint studio centrado").
   And measuring the box did not catch it: the span's `getBoundingClientRect`
   gave 21 for both because the block DOES start at 21. What must be measured
   is the text. */
.fl-tile-cap {
  position: absolute;
  left: calc(21 * var(--kp));
  top: calc(100% - 102 * var(--kp));
  text-align: left;
  z-index: 2;
  color: #fff;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.fl-tile-cap span {
  display: block;
}

.fl-tile-cap .fl-display {
  font-size: calc(14 * var(--k));
}

/* `.fl-text` carries no tracking by default; the file asks for the house
   0.04em on the second line too, same as in `.fl-case-cap`. */
.fl-tile-cap .fl-text {
  font-size: calc(11 * var(--k));
  letter-spacing: var(--fl-track);
}

@media (hover: hover) {
  .fl-tile:hover::after,
  .fl-tile:hover .fl-tile-cap {
    opacity: 1;
  }
}

/* With the keyboard too. The tour does not do this — its options carry
   `.active` — but here the tile is the only entrance to the viewer and there
   cannot be a way of traversing it that does not say where it leads. */
.fl-tile:focus-visible::after,
.fl-tile:focus-visible .fl-tile-cap {
  opacity: 1;
}

/* ── the 360 badge ─────────────────────────────────────────────────
   White `noun-360` badge, 25x25, 11px from its tile's RIGHT and BOTTOM
   edges. The file puts it on exactly two photos — `APThotel_still_cmd12` and
   `4K_Roto_Onix_Bautismal_FRONT` — which are the only two projects with a
   tour and the two stills that open the split `Selector`.
   `figma-2026-map.md:569`.

   IT SHOWS AT REST, and that part the file does not say: it is signage — it
   says which of the fifteen tiles opens the selector instead of the ficha —
   and a notice that only appears on hover warns of nothing under a finger.

   WHERE THE FILE DRAWS IT is in `projects` `2097:317`, the full-screen
   frame, not in the corpus grid. Since both are the same gallery —
   `2097:317` is the open state of `seguir viendo` — it is built in the one
   that is served; stated because it MOVES THE PIXEL DIFF at rest against
   `ref-deskcorpus-0805.png`, which does not carry it.

   `mask` and not two SVGs: the glyph is black in the file and white is
   needed here. One asset, the colour in the CSS. The CSP's `img-src 'self'`
   covers a mask's `url()` the same as an image's.

   PENDING VERIFICATION: `ic_360.svg` comes from the old site's import
   (`b16cc32`), not from a design export. That it is the SAME glyph as the
   file's `noun-360` is unverified — the Penpot plugin is needed to export
   it. If it is not, the file gets swapped and nothing else. */
.fl-tile-360 {
  position: absolute;
  right: calc(11 * var(--kp));
  bottom: calc(11 * var(--kp));
  width: calc(25 * var(--kp));
  height: calc(25 * var(--kp));
  z-index: 2;
  background: #fff;
  -webkit-mask: url('/assets/images/icons/ic_360.svg') center / contain no-repeat;
  mask: url('/assets/images/icons/ic_360.svg') center / contain no-repeat;
  pointer-events: none;
}

/* GRID FROM `2097:318`, not from `2074:59` (Denis, 27-ago-2026: "DESKcorpus -
   PANTALLA COMPLETA este es el figma de la grid nueva, usa esta"). The two
   frames draw the same gallery and no longer match: `2074:59` and the folded
   frame `2097:206` carry the old grid, and the new one lives only in the
   open frame `2097:317` / group `2097:318`.

   What changes, and it is one single idea: the grid gets REGULARIZED. Four
   columns at a fixed 365 pitch — 40 / 405 / 770 / 1135 — and 345 wide in all
   of them, where before rows 1-2 went at 41 with pitch 364-365 and rows 3-4
   at 16 with pitch 373-377 and widths from 358 to 361. The vertical stagger
   stays: still by hand, row by row.

   The four rows' `y`s do NOT move: the new frame puts them at 73 / 463 /
   864 / 1243 and it has no heading, so the 29 that `projects` occupies in
   the corpus returns them to 102 / 492 / 893 / 1272, the same as always.
   Checked in all four. */
.fl-row1 { left: calc(40 * var(--k)); top: calc(102 * var(--kp)); width: calc(1440 * var(--kp)); height: calc(507 * var(--kp)); }
.fl-row2 { left: calc(40 * var(--k)); top: calc(492 * var(--kp)); width: calc(1440 * var(--kp)); height: calc(528 * var(--kp)); }
.fl-row3 { left: calc(40 * var(--k)); top: calc(893 * var(--kp)); width: calc(1440 * var(--kp)); height: calc(559 * var(--kp)); }
.fl-row4 { left: calc(40 * var(--k)); top: calc(1272 * var(--kp)); width: calc(1075 * var(--kp)); height: calc(577 * var(--kp)); }

/* `desplegable` #2097:263 at 709,916 — the row expander. It is a control in the
   design (the Guide describes the click), so it is a button here; the panel it
   opens is `Seguir viendo` #2093:98, a flattened SVG with no live nodes, so the
   expanded state is not built. */
.fl-more {
  position: absolute;
  /* Like the heading: typography in canvas units. The `top` hangs off the
     clip's edge wherever it is — 863 closed, 1849 open, `--expand` in
     between — and adds its usual gap (916 - 863 = 53) in text units, not
     grid units.
     In X it is NOT anchored to the left edge, because the file does not
     measure it against the edge: it puts it at 709 on a canvas whose centre
     is 760, i.e. 51 left of centre (Denis, 25-ago-2026). The grid now
     measures the whole window, so that centre is the window's. With
     `709 * --k` the control stayed pinned while the grid widened — measured,
     control centre against window centre: -6 at 1520, -206 at 1920, -526 at
     2560 and -966 at 3440. This way it returns exactly 709 at 1520 and keeps
     that -6 at all widths. */
  left: calc(50% - 51 * var(--k));
  top: calc(863 * var(--kp) + var(--expand) + 53 * var(--k));
  padding: 0;
  border: 0;
  background: none;
  font-size: calc(16 * var(--k));
  font-weight: 300;
  font-style: italic;
  letter-spacing: var(--fl-track);
  color: #000;
  cursor: pointer;
}

/* #2093:95 — a down arrow (same shaft-only box as the cuaderno one: 8x11 with
   the shaft at x 3.86). */
.fl-more::after {
  content: '';
  position: absolute;
  left: calc(97.1 * var(--k));
  top: calc(6 * var(--k));
  width: calc(8 * var(--k));
  height: calc(11 * var(--k));
  background: url('/assets/images/2026/arrow-down-10.svg') center / contain no-repeat;
  transition: transform 0.25s ease;
}

.fl-projects.is-open .fl-more::after {
  transform: rotate(180deg);
}

/* ── formatos #2090:65 — 40,4746 · 1197x1612 ──────────────────────── */

/* The group is 1612 tall because its second `let's talk` sits at the very
   bottom, so the box runs behind `360 Interactivo` and `vision`. It is empty
   space, not a surface: clicks pass through it. */
.fl-formats {
  left: calc(40 * var(--k));
  top: calc(4746 * var(--k) + var(--expand) + var(--grow) + var(--r4));
  width: calc(1197 * var(--k));
  height: calc(1612 * var(--k));
  pointer-events: none;
}

.fl-formats > * {
  position: absolute;
  pointer-events: auto;
  color: #000;
}

/* `fromato` carries no rule: the 45x3 one at 291,124 belongs to the promoted
   entry, which is what it is marking. */
.fl-formats-tag::after {
  content: none;
}

/* The three formats read alike at rest and one of them is promoted on hover
   (Denis, 2026-08-05). The design only ever draws that promoted state — it is
   why `Still render` appears at 58 Italic while its neighbours sit at 48
   Regular — so the resting face is the neighbours' and each entry keeps the y
   the source gives it. With the first one promoted the block reproduces the
   export exactly, which is how it is measured. */

.fl-formats-list {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: calc(320 * var(--k));
  margin: 0;
  padding: 0;
  list-style: none;
  pointer-events: none;
}

.fl-formats-item {
  position: absolute;
  pointer-events: auto;
}

.fl-formats-item button {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: calc(48 * var(--k));
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  color: #000;
  text-align: left;
  cursor: pointer;
  transition: font-size 0.25s ease, -webkit-text-stroke-width 0.25s ease;
}

/* The promoted entry, exactly as the source draws `Still render` (#2071:116):
   Italic 400 at 58/0.04em with a 0.22px stroke. */
.fl-formats-item.is-active button {
  font-style: italic;
  font-size: calc(58 * var(--k));
  -webkit-text-stroke: calc(0.22 * var(--k)) #000;
}

/* #2071:124 — the 45x3 rule belongs to the promoted entry, not to the label:
   the source puts it at 291,124, which is 10px past the end of `Still render`
   and 57 below its top. Hung off the entry so it follows whichever is active. */
.fl-formats-item.is-active::after {
  content: '';
  position: absolute;
  left: 100%;
  top: calc(57 * var(--k));
  width: calc(45 * var(--k));
  height: calc(3 * var(--k));
  margin-left: calc(10 * var(--k));
  background: #000;
}

/* At rest the three are the same size and the same distance apart: the second
   and third are 86 apart in the source, so the first sits 86 above the second
   at 89 and not at the 67 the file draws (Denis, 2026-08-05 — the gap read
   wrong). 67 is where the *promoted* one goes, and it goes back there the
   moment it is: that is the state the export draws, and this keeps both. */
.fl-formats-still { left: calc(1 * var(--k)); top: calc(89 * var(--k)); }
.fl-formats-still.is-active { top: calc(67 * var(--k)); }
.fl-formats-a { left: 0; top: calc(175 * var(--k)); }
.fl-formats-b { left: 0; top: calc(261 * var(--k)); }

/* IN FLOW, NOT ABSOLUTE, since 31-ago-2026: `hablemos` goes after this box
   and has to move down when the copy grows.
   The file draws ONE of the three copies — `Still render`'s — and gives it a
   345x116 box. The other two are longer and with the `height` pinned they
   overflowed it and slid under the label: at 1520 the
   `360 | experiencia interactiva` one ended at 213.5 with the link at 204,
   nine and a half px above (Denis, 31-ago-2026, with screenshot). It clipped
   one copy in Spanish and Catalan and two in English and French, because the
   length is set by the translation: any new pinned number breaks with the
   next one.
   Both numbers are the file's, measured in Penpot on 31-ago-2026 on the
   `formatos` group (`ca9094e0`, origin 705,2580, with the `formatos` label at
   its 841 marking the block's 0):
     · copy box           852, 920-841 = 79 · 345 x 116
     · `let's talk` group 852, 1045-841 = 204
   That is 125 lid to lid and 9 of gap. The 125 goes in as `min-height` and
   not as height, and the 9 fits inside because everything is `border-box`:
   the state the file draws leaves the label at its EXACT 204 — and the
   resting one, which promotes nothing, too — and only the copies that do not
   fit push it. The 109 that stood here is from the old Figma reading; Penpot,
   the source since 28-ago-2026, measures 116. */
.fl-formats-copy {
  position: static;
  margin: calc(79 * var(--k)) 0 0 calc(852 * var(--k));
  width: calc(345 * var(--k));
  min-height: calc(125 * var(--k));
  padding-bottom: calc(9 * var(--k));
  font-size: calc(16 * var(--k));
  font-style: italic;
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  white-space: normal;
}

/* The `4K` run only overrides size and style; the 0.04em tracking is inherited
   and Figma resolves it against the run's own 12px, so it is re-declared here
   rather than left to inherit the parent's already-computed 0.64px. */
.fl-formats-copy p {
  margin: 0;
}

.fl-formats-4k {
  font-size: calc(12 * var(--k));
  font-style: normal;
  letter-spacing: var(--fl-track);
}

/* `let's talk _` #2074:47 / #2152:33 — label plus a 21.45px hairline that
   crosses it at x 60.61. Sub-pixel numbers, kept as the source has them. */
.fl-talk {
  /* THE RULE FOLLOWS THE TEXT since 27-ago-2026, and that is why this is a
     flex. The file puts the rule at a fixed `left` — #2152:34 at x952.61,
     i.e. 60.61 from the 345 box's edge — because it drew it only once, in
     English. On translating "let's talk" (Denis: "siguen habiendo cosas en
     ingles [...] esto tienes que arreglarlo") that number stops holding:
     "hablemos" ends at 66.1 and the rule rode over it by 5.5px, "parlons-en"
     by 12.8. With the rule as the flex's second item, the relation the file
     draws — a short gap between the words and the stroke, like a dotted line
     to fill in — holds in all four languages. And English reproduces the
     file's number EXACTLY: 58.4 of text + 2.21 of gap = 60.61. */
  display: flex;
  align-items: flex-start;
  width: calc(345 * var(--k));
  font-size: calc(16 * var(--k));
  font-weight: 300;
  font-style: italic;
  letter-spacing: var(--fl-track);
  text-decoration: none;
}

.fl-talk::after {
  content: '';
  flex: 0 0 auto;
  /* 2.21 = the file's 60.61 minus the 58.4 "let's talk" measures here.
     The 16 and the 21.45 are still from node #2152:34. */
  margin-left: calc(2.21 * var(--k));
  margin-top: calc(16 * var(--k));
  width: calc(21.45 * var(--k));
  height: calc(0.4 * var(--k));
  background: #000;
}

/* No `top`: the copy's box places it, which is what it has to hang from
   (see `.fl-formats-copy`). The 852 is still the file's. */
.fl-talk-top {
  position: static;
  margin-left: calc(852 * var(--k));
}
/* 805/1596 and not 852/1593 since 27-ago-2026 15:36: `2152:33` goes from
   `892,6339` to `845,6342` in the frame, and this hangs off `.fl-formats`
   (40,4746), hence 845-40 and 6342-4746. The upper one (`2074:49`) did not
   move.
   THE ONLY NODE THAT CROSSES SCREENS: it hangs off `.fl-formats`, which goes
   with screen 4, but is drawn at the very bottom, in screen 6. Without this
   subtraction it would travel 115px with its column and slide into the faq's
   band. Measured: it is the only one — the other descendants that fall
   outside their block's box do so by one or two boundary px, not by a
   screen. */
.fl-talk-bottom {
  left: calc(805 * var(--k));
  /* `+ nav - fit` BECAUSE IT BELONGS TO THE FAQ, NOT TO FORMATOS. Its column
     carries neither the bar gap nor Serena's shrink and `.fl-faq` does, so in
     a short window the label ended up as much as 225px below the faq and ate
     FIN's band: at 1536x760 that band measured 75 instead of 190, and 23
     instead of 160 at 1280x600 — below the navmenu, which is exactly the
     brief's floor. It was misaligned since before today; what brought it to
     light was tightening that band 170px with the equalized rhythm, and
     `ritmo.js` catches it.
     At 1520x1117 both terms are 0, so parity does not move. */
  top: calc(1596 * var(--k) + var(--nav) - var(--fit) + var(--r6) - var(--r4));
}

.fl-faq-link {
  left: calc(1 * var(--k));
  top: calc(339 * var(--k));
  font-size: calc(16 * var(--k));
  font-weight: 300;
  font-style: italic;
  letter-spacing: var(--fl-track);
  text-decoration: none;
}

/* #2090:76 — a down arrow, 8x17 with the shaft at x 3.79. */
.fl-faq-link::after {
  content: '';
  position: absolute;
  left: calc(39.2 * var(--k));
  top: calc(3 * var(--k));
  width: calc(8 * var(--k));
  height: calc(17 * var(--k));
  background: url('/assets/images/2026/arrow-down-16.svg') center / contain no-repeat;
}

/* ── 360 Interactivo #2097:348 — 283,5251 · 1197x985 ──────────────── */

/* The other one touching the edge in the file, this one on the right:
   405 + 1115 is exactly the canvas's 1520. It grows toward there and its left
   does not move, so neither does the photo caption — which hangs off the
   block's left edge. */
.fl-inter360 {
  /* Anchored to the WINDOW's RIGHT edge and not to its file `left`. Valerie's
     rule 3 (6-ago-2026, docs/edge-to-edge-2026.md): what touches the edge in
     the frame touches the edge on the web, and what carries margin keeps it.
     This one touches it — 405 + 1115 is exactly the canvas's 1520 — so it
     goes full bleed; `.fl-solution-img` and the projects grid carry margin in
     the file and that is why they stay on the canvas. It is the same rule,
     not two criteria.
     Anchoring it on the right also means that, when the height forces it
     narrower, what is lost is lost on the left and it keeps touching its
     edge. */
  /* NOT AT THE WINDOW EDGE: AT 40 FROM IT. This line has gone back and forth
     three times today — canvas edge at 11:48, window edge at 12:13, stretched
     to the edge at 13:47 — and this time it is moved not by a request but by
     THE FILE: `2097:349` ends at 1480 and not 1520, i.e. 40 of padding, the
     same number as the `cómo será` image and the projects grid.
     `40 * --k - --fl-bleed` puts that edge 40 from the WINDOW at any width:
     at 1520 the bleed is 0 and the file's 40 comes out; at 3440 it gives
     -920, which is the window edge minus 40. */
  right: calc(40 * var(--k) - var(--fl-bleed));
  top: calc(5251 * var(--k) + var(--expand) + var(--grow) + var(--r5));
  /* IT STRETCHES, it does not shift (Denis, 26-ago-2026: "quiero que la img
     se comporte igual que la seccion de notebook, es decir que se estire
     igual"). That stretch was removed at 11:48 on 27-ago at the fixed-
     proportion request, and RETURNS at 13:47 the same day, with the reason
     stated by Denis: "serena materia es un problema, pq en pantallas
     apaisadas queda mucho espacio a la izquierda. como lo arreglamos? tal vez
     hacer que se extienda como hace la de casa dolmen? eso si, manteniendo el
     centro en la img".
     The gap was the price of moving the box instead of stretching it: at 3440
     there were 960px of white left of it. Stretching, there is no gap and the
     right anchor stays at the window edge, so both things he asked for before
     — flush right, and the text 16 from the photo — still stand. What is lost
     is the fixed proportion: the box becomes landscape above 1520, which is
     exactly what he asked.
     `--sm-w` is `--sm-h * 1197/920` since 15:36 — the file's new proportion,
     1.3011 — and it is what the box measures at 1520 and below. The LEFT edge
     does not move: it stays at the file's 283 at any width, and that is why
     `vision` keeps its 20 without discounting anything. */
  width: calc(var(--sm-w) + var(--fl-bleed));
  height: calc(var(--nav) + var(--sm-h) + 65 * var(--k));
  /* `box-sizing: border-box`, so the bar gap comes out of the height and is
     not added to it. The <img> is in flow, so the padding lowers it by
     itself; the panorama and the caption are absolute and carry it in their
     `top`. */
  padding-top: var(--nav);
}

.fl-inter360-img {
  width: 100%;
  height: var(--sm-h);
  object-fit: cover;
  /* THE CROP STAYS CENTRED, AND HERE THAT IS DENIS'S CONDITION, not a
     preference: "vital, que el marzipano mantenga su centro en la imagen"
     (26-ago-2026).
     `.fl-case-img` and `.fl-studio-img` anchor their crop at the bottom when
     they go full bleed — `object-position: 50% 100%` — because that way they
     lose sky and not both ends. Copying that HERE would break exactly what he
     asks: under this <img> there is a Marzipano panorama drawn centred in its
     box that knows nothing of `object-position`. Anchoring the photo at the
     bottom would shift it relative to the panorama, and as one fades over the
     other the room would jump.
     With `50% 50%` the crop is symmetric on both sides, so the photo's centre
     is the box's centre — which is exactly where the viewer puts its
     yaw/pitch. Both look at the same spot at any width.
     Written even though it is the default value: what is needed here is that
     the next person who comes to add full-bleed bands reads WHY this one does
     not carry it. */
  object-position: 50% 50%;
}

/* The rotator occupies the EXACT box of the <img> it replaces — same 920
   height from the file, same width — so the photo caption and everything
   below stay where they were. It starts transparent and `rotator.js` reveals it
   with its first decoded frame, so while it loads what shows is the render.
   `pointer-events: none` because it is pure decoration: below there is
   nothing to cover but the poster, and the linked caption lives on top with
   its z-index. */
.fl-inter360-rotator {
  position: absolute;
  left: 0;
  width: 100%;
  top: var(--nav);
  height: var(--sm-h);
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms ease;
}

.fl-inter360-rotator.is-ready {
  opacity: 1;
}

/* Each frame fills the box with the SAME centred cover as the file's <img>:
   the ten files share camera and size, so sharing box and crop is what makes
   the framing identical for all of them (Denis, 02-sep-2026: "el encuadre es
   el mismo para todas las img"). The fade is the 1200ms the materials cycle
   already carried; `is-instant` is the checks' gate to skip without waiting
   for the fade. */
.fl-inter360-rotator img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  opacity: 0;
  /* 600 = rotator.js FADE: the one that removes `is-on` waits FADE + 100, so
     the two numbers must agree or both frames sit half-transparent over
     white (final audit, 3-sep-2026: this said 1200 after FADE went to 600). */
  transition: opacity 600ms ease;
}

.fl-inter360-rotator img.is-on {
  opacity: 1;
}

.fl-inter360-rotator img.is-instant {
  transition: none;
}

.fl-inter360-cap {
  position: absolute;
  left: 0;
  top: calc(var(--nav) + var(--sm-h) + 21 * var(--k));
  /* 403 and not 318 since 27-ago-2026. The right-hand label went from
     "360º Interactivo" to "360º experiencia interactiva" (#2090:70) and the
     file widened the box with it: #2090:72 measures 403x44 today. With the
     old 318 the text stuck out 90px on the right, measured in all four
     languages. */
  width: calc(403 * var(--k));
  height: calc(44 * var(--k));
}

.fl-inter360-cap a {
  display: block;
  height: 100%;
  color: #000;
  text-decoration: none;
}

.fl-inter360-cap .fl-display {
  position: absolute;
  left: 0;
  top: 0;
  font-size: calc(24 * var(--k));
}

.fl-inter360-cap .fl-text {
  position: absolute;
  left: calc(202 * var(--k));
  top: calc(9 * var(--k));
  font-size: calc(15 * var(--k));
  letter-spacing: var(--fl-track);
}

/* #2090:71 — the rule under the two labels. 403 since 27-ago-2026, with the
   box: the LINE node measures 405,6225 · 403x0, i.e. exactly the `link`'s
   width. */
.fl-inter360-cap a::after {
  content: '';
  position: absolute;
  left: 0;
  top: calc(33 * var(--k));
  width: calc(403 * var(--k));
  height: calc(0.35 * var(--k));
  background: #000;
}

/* ── vision #2093:152 — 162,6051 · 227x120 ────────────────────────
   REDESIGNED ON 26-ago-2026 and read live via `/nodes` at 18:07, not from
   the map: the file has been saved four times since I read it at 15:40
   (14:06, 14:53, 15:29 and 16:01 UTC) and this block changed along the way.
   What was there before: `40,5335 · 345x208`, with a Gloock 48 label and a
   Lato 22 body.

   Three changes, all the file's:
     · the `vision` label (#2093:153) LEAVES the canvas — it still exists but
       at x −279, outside the 1520 frame, and the frame render does not draw
       it.
     · the body (#2071:76) goes to 227 wide, Lato Light 20/24, ls 0.8
       (=0.04em) and `textAlignHorizontal: RIGHT`, with new copy.
     · and it moves down: from 5413 to 6051. */

.fl-vision {
  /* IT FOLLOWS THE PHOTO, AND NOW ON BOTH AXES.
     In X it already did (Denis, 25-ago-2026): the file puts it flush against
     the Serena piece's left edge, and since that piece is ruled by the
     window's HEIGHT, a file `left` left gaps of up to 540px. Now it is 16 and
     not 20 because the body has narrowed: 162 + 227 = 389 against the photo's
     405.
     In Y it is new, and it comes from the file itself: the body runs from
     6051 to 6171 and the photo ends at 6171 — the same pixel. So the design
     aligns it at the BOTTOM against the photo, not against the canvas.
     Pinning it at 6051 would detach it as soon as `--nav` or `--sm-h` moved,
     which is exactly what already happened once with the top edge.
     When the design fits whole, the file's two numbers come back out by
     arithmetic and not by exception: 1520 − (1115 + 16) − 227 = 162, and
     5251 + 920 − 120 = 6051.
     On 27-ago at 12:29 this subtracted `--fl-bleed`, because the photo had
     gone to the window edge keeping its width. At 13:47 it stretches again
     and the discount is redundant. And at 15:36 THE FILE redoes both nodes:
     `2093:152` goes from `162,6051 1075x307` to `36,6038 1154x323` and the
     body `2071:76` from `162,6051` to `36,6038`, without touching its text or
     its 227x120 box.
     The two new numbers, read and not chosen:
       · the gap to the photo goes from 16 to 20 — 283 (photo) minus 263
         (36+227). Since the photo ends 40 from the edge, that gives
         `--sm-w + 40 + 20`.
       · and it stops being BOTTOM-aligned with the photo: its base falls at
         6158 and the photo ends at 6171, i.e. 13 above. Hence the -133
         (920 - 120 - 13) where -120 went before. */
  left: auto;
  right: calc(var(--sm-w) + 60 * var(--k));
  top: calc(5251 * var(--k) + var(--nav) + var(--sm-h) - 133 * var(--k)
            + var(--expand) + var(--grow) + var(--r5));
  width: calc(227 * var(--k));
  height: calc(120 * var(--k));
}

.fl-vision-body {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(227 * var(--k));
  font-size: calc(20 * var(--k));
  font-weight: 300;
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  /* `textAlignHorizontal: RIGHT` on the node. It is what makes the paragraph
     lean against the photo instead of against the air on the left. */
  text-align: right;
  color: #000;
  white-space: normal;
}

/* The italic runs are Lato-Italic 400 over a Light 300 body — a step up, not
   a bold. Before it was a single run at the end; since 26-ago there are
   three, spread through the sentence.

   `line-height: 0` IS NOT A HACK, it is what returns the box to its 120.
   Lato-Italic has more ascender/descender than Lato-Light, so its inline box
   overflows the paragraph's and stretches the whole line: measured, 24.8
   instead of 24, and with italics on all five lines the paragraph came out at
   124 inside a 120 box that the file pins at exactly 120 (5 x 24). With the
   italics at 0 their box cannot stretch the line — the glyphs still paint the
   same, an inline box's overflow moves nothing — and the paragraph lands at
   an exact 120, the same the control without italics gives.
   It went unnoticed before because there was only one run and it fell on the
   last line, with a box of 132 of slack. Now there are three and the box is
   tight. */
.fl-vision-body em {
  font-weight: 400;
  font-style: italic;
  line-height: 0;
}

/* ── faq #2093:78 — 283,6342 · 502x270 ────────────────────────────── */

.fl-faq {
  left: calc(283 * var(--k));
  /* `--nav` TOO, not just `--fit` (Denis, 25-ago-2026). The Serena group
     measures `--nav + --sm-h + 65`, that is `985 + --nav - --fit`: it grows
     with the gap reserved for the bar and shrinks with what the photo loses.
     Subtracting only `--fit` returned the shrink and ignored the growth, so
     everything below rose 90*k as soon as the window dropped below the 1075
     the group needs. Measured at 1440x820: the file's 106 gap between
     `.fl-inter360` and this block was left at 15. */
  top: calc(6342 * var(--k) + var(--nav) + var(--expand) - var(--fit) + var(--grow) + var(--r6));
  width: calc(502 * var(--k));
  height: calc(270 * var(--k));
}

/* At rest only the questions, stacked at the source's own 27 pitch; the answer
   opens under the one the pointer is on and pushes the rest down (Denis,
   2026-08-05). The file draws exactly that state with the fourth open — its
   own coordinates are q4 at 81, the answer at 108 and q5 at 251, so the block
   opens by 143 and the answer lands 27 under whichever question asked for it.
   Buttons, not paragraphs: the keyboard has to reach them, and the reset
   leaves them reading as the plain text the file draws. */
.fl-q {
  position: absolute;
  left: 0;
  top: calc((var(--y) + var(--shift, 0)) * var(--k));
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  /* No `font` shorthand and no `font-family` here: the button *is* the
     `.fl-text` node, this sheet loads after the one that class lives in, and
     either declaration would beat it and hand the element the section's
     family — the UA's serif. What a button brings of its own is reset above;
     the family stays where it was. */
  font-size: calc(16 * var(--k));
  font-style: italic;
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  color: #000;
  text-align: left;
  cursor: pointer;
  transition: top 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* It opens and closes rather than appearing: height from 0 to its own, and on
   the way from one question to another the one showing closes first and the
   new one opens after it (Denis, 2026-08-05) — `corpus.js` sequences that, the
   two states are here. The height itself is measured and set inline: the wrap
   depends on the language. Five of these now, one per question (the real
   copy, board FAQ_textos, 02-sep-2026); `pre-line` draws the answers' own
   line breaks, which travel inside the i18n strings. */
.fl-faq-a {
  position: absolute;
  left: 0;
  top: calc(var(--at, 108) * var(--k));
  width: calc(502 * var(--k));
  height: 0;
  overflow: hidden;
  opacity: 0;
  transition:
    height 0.3s cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 0.3s ease;
  font-size: calc(16 * var(--k));
  font-weight: 300;
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  color: #000;
  white-space: pre-line;
}

.fl-faq-a.is-open {
  opacity: 1;
}

/* ── FIN #2123:31 left on 28-Aug-2026: `contact` is its substitute (Denis,
   "es el sustituto de la ultima pagina"); its rules went in the final audit
   of 3-sep-2026. ──────────────────────────────────────────────────── */

/* FIN'S ORPHANS. They lived in the footer and `contact` replaces it; they
   stay because a four-language site cannot be left without a selector. They
   go to `contact`'s left column — the same x 73 as the email and the social
   links — with the bottom-edge distances they had in the footer, and in its
   same #FFFEFA because they still land on photography. INVENTION: the file
   does not draw them in this frame. */
.fl-credit,
.fl-langs {
  position: absolute;
  left: var(--fl-col-photo);
  font-size: calc(16 * var(--k));
  letter-spacing: var(--fl-track);
  color: #fffefa;
}

.fl-credit {
  bottom: calc(56 * var(--k));
}

.fl-credit-by {
  display: inline-block;
  width: calc(96 * var(--k));
}

.fl-credit-name {
  font-family: 'Prompt', Arial, sans-serif;
  font-weight: 700;
}

.fl-langs {
  bottom: calc(84 * var(--k));
}

.fl-lang {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.fl-lang[aria-pressed='true'] {
  font-weight: 700;
}

/* ── contact #2207:34 — 0,5508 of the canvas · 1520x920 ───────────
   THE LAST VIEW (Denis, 28-ago-2026). It goes AFTER FIN: the file places it
   in the same slot as the footer — 665,5508 is DESKcorpus's bottom edge,
   which is where FIN is drawn — and the file does not break the tie.
   It stacks instead of replacing because the footer carries the language
   selector, the credit and the nav, and nobody has asked to remove them.

   No rhythm band between FIN and this one: the file draws them at the SAME
   y, so the separation it gives is zero. Both are full-bleed photography and
   they touch, which is what the file shows.

   The frame carries no bar: its `nav` (#2207:50) and its logo (#2207:45) are
   `visible: false`. Nothing to do here — the traveller hangs off FIN's nav
   and leaves with it when passing by. */

.fl-contact {
  /* The column over the photo (email, social links, languages, credit). The
     file's 73 is distance TO THE PHOTO, and the photo touches the WINDOW
     edge (it goes full bleed): `- --fl-bleed` keeps that 73 from the real
     edge at any width, like Serena's `40 * --k - --fl-bleed`. At 1520 the
     bleed is 0 and the file's 73 comes out; without it, at 1920 they
     measured 273 of window (Denis, 31-ago-2026: "no esta bien colocado el
     padding start"). */
  --fl-col-photo: calc(73 * var(--k) - var(--fl-bleed));
  left: 0;
  top: calc(6833 * var(--k) + var(--nav) + var(--expand) - var(--fit) + var(--grow) + var(--r7));
  width: calc(1520 * var(--k));
  height: calc(920 * var(--k));
  /* The frame's fill (#FFFFFF), and it is not decoration: the FIN photo's
     `filter: blur(2px)` paints OUTSIDE its box — a filter does not clip
     itself — and left a 4px pink strip crossing this screen's top edge,
     visible in the right half, where there is no photography underneath.
     Contact comes later in the DOM, so its background covers it. */
  background: #fff;
}

.fl-contact > *,
.fl-contact-form > * {
  position: absolute;
  margin: 0;
}

.fl-contact-form {
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

/* #2077:166 — 0,0 · 628x921 (the extra pixel is clipped by the frame, and
   here the box is already an exact 920). It touches the canvas's LEFT edge,
   so it eats the air on that side like the rest of the full-bleed photos;
   its right edge stays at the file's 628, which is what the form column
   hangs from. Since 3-sep-2026 the asset is the full landscape render the
   viewer serves (`visor/bano.webp`, 1442x1228, the 240515 render — Denis,
   18:48: "la img del contact debe de ser este tanto mobile como desktop");
   one file for tile, viewer and contact. The Penpot node carries the older
   240517 fill, drawn cover/centre — the framing is kept, the render is not:
   the shower is cut at the left edge at 1520, exactly like the board's.
   The photo's LEFT edge is what is always seen (Denis, 3-sep-2026, 19:26:
   "se debe de ir comiendo el lado derecho y el izquierdo es el que se debe
   de ver siempre"): `object-position: 0` pins the image's left edge to the
   box's — the window's edge at any width, since the box starts at `-bleed`
   — and `cover` crops on the right. Departure from the board, named: the
   node draws the fill centred, 227 columns further in at 1520 (the shower
   cut at the edge), and until 19:26 that was reproduced with
   `min(0px, bleed - 226.9k)`, which slid the crop with the bleed above
   1520. Parity 9a's photo zone re-bases with the instruction; 9c asserts
   the window-edge strip is the same at every width. */
.fl-contact-img {
  left: calc(-1 * var(--fl-bleed));
  top: 0;
  width: calc(628 * var(--k) + var(--fl-bleed));
  height: 100%;
  object-fit: cover;
  object-position: 0 center;
}

/* #2213:32 and #2213:33 — 73,666 and 73,725, both over the photograph.
   `--fl-col-photo` is explained at `.fl-contact`. */
.fl-contact-details,
.fl-contact-social {
  left: var(--fl-col-photo);
  width: calc(345 * var(--k));
  font-size: calc(16 * var(--k));
  letter-spacing: var(--fl-track);
  color: #fffefa;
  white-space: normal;
}

.fl-contact-details {
  top: calc(666 * var(--k));
  font-style: italic;
}

.fl-contact-social {
  top: calc(725 * var(--k));
  font-weight: 300;
}

.fl-contact-details a,
.fl-contact-social a {
  color: inherit;
  text-decoration: none;
}

.fl-contact-details a:hover,
.fl-contact-social a:hover,
.fl-contact-details a:focus-visible,
.fl-contact-social a:focus-visible {
  text-decoration: underline;
}

/* #2238:156 — 892,177 */
.fl-contact-h {
  left: calc(892 * var(--k));
  top: calc(177 * var(--k));
  font-size: calc(24 * var(--k));
  letter-spacing: var(--fl-track);
  color: #000;
}

/* The two rules for name and email; the message's three are painted by the
   textarea itself (see `.fl-contact-msg`). 0.5 of stroke, which is what the
   nodes say: below one pixel the browser paints it grey, and that is how it
   comes out in the exported frame. */
.fl-contact-rule {
  left: calc(892 * var(--k));
  top: calc(var(--y) * var(--k));
  width: calc(345 * var(--k));
  height: calc(0.5 * var(--k));
  background: #000;
}

/* The fields. They go WITHOUT a border: the line beneath is a separate node.
   The two one-line ones are 32 tall — from their text to their rule — with
   the bottom 8 as padding, so the text sits at the file's 24 and the click
   area still reaches down to the rule. */
.fl-contact-field {
  left: calc(892 * var(--k));
  width: calc(345 * var(--k));
  padding: 0;
  border: 0;
  background: none;
  color: #000;
  font-size: calc(20 * var(--k));
  letter-spacing: var(--fl-track);
  line-height: 1.2;
  appearance: none;
}

.fl-contact-field::placeholder {
  color: rgba(0, 0, 0, 0.25);
  opacity: 1;
}

.fl-contact-field:focus-visible {
  outline: calc(1 * var(--k)) solid rgba(0, 0, 0, 0.35);
  outline-offset: calc(2 * var(--k));
}

.fl-contact-name {
  top: calc(244 * var(--k));
  height: calc(32 * var(--k));
  padding-bottom: calc(8 * var(--k));
}

.fl-contact-email {
  top: calc(314 * var(--k));
  height: calc(32 * var(--k));
  padding-bottom: calc(8 * var(--k));
}

/* #2207:73 at 389, and the file's three rules at 420, 464 and 510 — i.e. at
   31, 75 and 121 from the field. A NOTEBOOK since 31-ago-2026 (Denis: "el
   texto debe de escribirse encima de las lineas... como si fuese un
   cuaderno"): the rules are painted by the textarea itself and travel with
   the text on scroll (`background-attachment: local`), which is what fixed
   rules cannot do. Line height 121/3: three exact line boxes in the field's
   height, each with its rule AT THE BOTTOM, which is the same anatomy as
   name and email (text + air + rule; Denis, 31-ago-2026: "debe de haber un
   poco de elevacion sobre la linea, como esta en nombre y email"). The
   uniform pitch is invention — the file's rules go at 31/75/121, unequal —
   and the last rule lands on the file's 121. */
.fl-contact-msg {
  /* The rule is NOT `0.5 * --k` like the spans: a subpixel stroke inside a
     repeated pattern of fractional period rasterizes differently on each
     pass and at k<1 some rules vanish (measured at 1000 wide: the second one
     did not paint). 1px at 50% is the same grey the browser paints the
     file's 0.5 with, but identical on every rule. */
  --fl-notebook: calc(121 * var(--k) / 3);
  --fl-rule: calc(var(--fl-notebook) - 1px);
  /* The rule's ink as a variable: mobile paints it white over its photo by
     redefining ONLY the colour, not the pattern's geometry. */
  --fl-rule-ink: rgba(0, 0, 0, 0.5);
  top: calc(389 * var(--k));
  height: calc(121 * var(--k));
  resize: none;
  overflow: auto;
  line-height: var(--fl-notebook);
  background-image: repeating-linear-gradient(to bottom,
    transparent 0 var(--fl-rule),
    var(--fl-rule-ink) var(--fl-rule) var(--fl-notebook));
  background-attachment: local;
}

/* #2238:158 — the file puts it at 1202,522 with `0/500`, and that 1202 is
   really "flush with the end of the rules": its right edge dies at 1237, the
   column's. With the label at `0/2000` (deliberate deviation, Denis
   1-sep-2026) that edge is anchored and the text grows inward; with the
   file's left it would stick 7px out of the column. 283 = 1520 - 1237. */
.fl-contact-count {
  right: calc(283 * var(--k));
  top: calc(522 * var(--k));
  font-size: calc(12 * var(--k));
  letter-spacing: var(--fl-track);
  color: rgba(0, 0, 0, 0.25);
}

/* #2238:162 (the box) + #2238:160 (the label). The label IS centred, and
   this time it is measured in the source: its node (910,567 · 48x20) has its
   centre at 933,577 and the box (892,561 · 82x36) at 933,579 — the earlier
   "18 from the left" was only symmetric for the file's text width, and with
   our Lato the label landed off-centre (Denis, 31-ago-2026). Truly centring
   is the faithful thing: `text-align` for the x axis and the <button>'s own
   centring for the y axis. */
.fl-contact-send {
  left: calc(892 * var(--k));
  top: calc(561 * var(--k));
  width: calc(82 * var(--k));
  height: calc(36 * var(--k));
  padding: 0;
  border: 0;
  border-radius: calc(15 * var(--k));
  background: #444;
  color: #fffefa;
  font-size: calc(16 * var(--k));
  letter-spacing: var(--fl-track);
  text-align: center;
  cursor: pointer;
}

/* The form's honeypot field: out of sight but NOT with `display: none`,
   which is precisely the one bots skip.

   `.fl-corpus` in front on purpose, and it is not decoration: this file's
   mobile sweep (`.fl-corpus * { position: static }`, further down) scores
   (0,1,0) and would pull the trap into the flow, where it would take its
   pixel in the middle of the form. With the canvas class it is (0,2,0) and
   this one wins. */
.fl-corpus .fl-contact-honeypot {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  clip-path: inset(50%);
  pointer-events: none;
}

/* The send acknowledgement. INVENTED: the file draws neither the sent state
   nor the error state. It goes in the gap the design itself leaves between
   the button (892,561 plus 36 of height, so ending at 597) and the WhatsApp
   line (685), with the body size and ink that already exist so as not to
   introduce a home-grown value. Empty at rest, which is what leaves the
   pixel diff against the frame intact. */
.fl-contact-status {
  left: calc(892 * var(--k));
  top: calc(609 * var(--k));
  width: calc(345 * var(--k));
  font-size: calc(16 * var(--k));
  letter-spacing: var(--fl-track);
  color: #000;
}

/* #2238:165 — 892,685. Light 300 with `WhatsApp` in Regular 400 and
   underlined: the underline is a loose node (#2238:167, 1053,709 x 98 with 1
   of stroke). */
.fl-contact-wa {
  left: calc(892 * var(--k));
  top: calc(685 * var(--k));
  font-weight: 300;
  font-size: calc(20 * var(--k));
  letter-spacing: var(--fl-track);
  color: #000;
}

/* The two nodes only the MOBILE contact board draws (8392bb36): the one-line
   WhatsApp pill and the form's label. On the big canvas they do not exist. */
.fl-contact-wa-mobile,
.fl-contact-intro {
  display: none;
}

.fl-contact-wa a {
  font-weight: 400;
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: calc(1 * var(--k));
  text-underline-offset: calc(5 * var(--k));
}

/* ── PANTALLA COMPLETA — the scroll states ─────────────────────────
   A section named `... - PANTALLA COMPLETA` holds what a block looks like
   once the screen is filled by it (Denis, 2026-08-04). Each delta below is
   the stand-alone frame's coordinates minus the in-page ones, read from the
   raw node API — never estimated:

     ¿Qué somos?  #2073:131  the image alone goes full bleed: 954 -> 1520 wide,
                             x 283 -> 0. Both texts stay exactly where they
                             are — their frame x (568 and 719) is identical in
                             the two states once the image's own shift is
                             accounted for, so nothing moves but the photo.
     Solución     #2073:145  `this will be` +32 in x · headline +289 in x and
                             32 -> 42px (its box 76 -> 100, which is the same
                             1.2 line height) · the image holds its x and drops
                             both its blur and its 0.55 fill opacity · the body
                             copy does not move, exactly as the Guide says.
     Caso Real    #2073:147  image 1115 -> 1141 wide at the same x · caption
                             -39 in x · the `cuaderno` column +38 in x and its
                             body box 120 -> 144 tall.

   The stand-alone frames also re-compose vertically (the Solución body sits
   24px lower in its 920 frame, the Caso Real caption 21px higher). Those are
   NOT animated: the author's note says the body copy does not move, and a
   block that is 7950px into a page cannot honour a y that only exists inside
   a 920-tall art board. Named here so the omission is visible.

   `projects` needs nothing: its two PANTALLA COMPLETA frames (#2097:207 at
   920 with the white panel, #2097:317 at 1853 without it) are the collapsed
   and expanded states of the `seguir viendo` control, already built — and
   they confirm its geometry, 1707px of rows in both. */

/* ── PANTALLA COMPLETA, driven by the scroll ───────────────────────
   The design gives these blocks a second frame, "what it looks like when the
   screen is full of it". It is not a state that snaps on: it runs *with* the
   scroll (Denis, 2026-08-05) — nothing at the moment the block starts coming
   up, finished the moment the block is entirely on the screen. So every delta
   below is a mix of its two ends against `--full`, which `corpus.js` sets from
   0 to 1 as that stretch of scroll goes by. No transition either: a 0.7s ease
   on top of a scrub is just the page lagging a wheel.

   `--full` is set on the block and inherited by its parts. `cuaderno` belongs
   to the same stand-alone frame as `Caso Real` but sits before it in the page,
   where no combinator reaches it, so it reads `--case` off the root instead. */

/* The window opens from 954 to 1520 with the scroll — and from there, if the
   screen gives more than 1520, it keeps opening until it fills it. It is
   multiplied by `--full` because only the full-screen state is the one the
   file draws at full bleed: at rest this block has its margin and keeps it.
   The height does not change: the photo fills in width and its `cover`
   crops. That way this block does not push against `Solución` either, which
   starts 267px lower. */
.fl-studio-frame {
  left: calc(-283 * var(--full, 0) * var(--k) - var(--fl-bleed) * var(--full, 0));
  width: calc((954 + 566 * var(--full, 0)) * var(--k) + 2 * var(--fl-bleed) * var(--full, 0));
}

.fl-studio-img {
  left: calc(-263 * (1 - var(--full, 0)) * var(--k));
  width: calc(1520 * var(--k) + 2 * var(--fl-bleed) * var(--full, 0));
  /* THE CROP MOVES DOWNWARD with the state (Denis, 25-ago-2026: "en
     ultrawide la imagen hace un corte muy raro, deberia al menos alinearse
     hacia abajo"). At rest the box has the photo's proportion and there is
     nothing to crop, so the 50% paints nothing; at full bleed on an
     ultrawide it does: the source is 3040x1840 (1.652) and the box reaches
     3.74 at 3440, so `cover` enlarges it 2.26x and discards 1162px of
     height, half at the top and half at the bottom. Anchoring at the bottom,
     what is lost is lost at the top — sky — and not at both ends at once. */
  object-position: 50% calc(50% + 50% * var(--full, 0));
}

/* `this will be` DOES NOT MOVE. Neither with the width nor with the state.
   Denis, 26-ago-2026 20:25: "this will be no se debe de mover, por favor,
   dejalo quieto". That undoes the two things tried this afternoon: the
   `-1 * --fl-bleed` of 17:52 and the travel to the column of 20:01.
   It stays at its 40 of canvas, which is what #2044:6 and #2071:78 draw. */
.fl-solution .fl-tag {
  /* THE RULE DOES SHIFT, and it is the only part of the tag that moves
     (Denis, 26-ago-2026 20:40: "anima en el this will be _ la _ que se
     desplace, pero solo eso junto al desplazamiento del texto"). It rides
     the same `--full` that drives the headline, so the two things move
     together.
     Both ends are the file's and neither is mine: 138 in page (#2044:2 at
     705 with its rule #2054:25 at 843) and in the 1520 full, and 165 in the
     1849 frame (#2220:46, text at 40 and rule at 205). Until now it went a
     flat 165, i.e. 27 too much at rest. */
  --rule-x: calc(138 + 27 * var(--full, 0));
}

/* THE HEADLINE IS WHAT ANIMATES, and it stops at the end of the body's
   column. Denis, same hour: "esto debe solo hacer animacion sin mover el
   will be_; 'se decide...' hasta el final de la columna de 'hacemos
   imagenes' SIN ponerse encima de la imagen".

   `100%` inside a `translateX` is the element's OWN width, so with the box
   hugging the ink (`max-content`) this is literally "slide until your right
   edge touches the end of the column", without measuring the text or writing
   a number per language:

     588 = 162 + 466 - 40   the end of `.fl-about` (#2132:19) seen from
                            where the headline starts
     min(289k, ...)         never farther than the file itself takes it
                            (#2088:30 puts it at 329, i.e. +289)
     max(0px, ...)          and never backwards: if a language has a line
                            longer than the column, it stays where it is
                            instead of sliding out the margin's left

   Measured, in ink px at 32: es 497 · en 565 · ca 556 · fr 594. The first
   three slide 91, 23 and 32; French stays still because its long line
   already measures 6 more than the column.

   AND HERE THE FILE'S 32 -> 42 GROWTH FALLS AWAY, the only thing in this
   rule that cannot be had together with what was asked. At 42 the long line
   measures 649 (es), 740 (en), 727 (ca) and 779 (fr); the photo's left edge
   never drops below 652 of canvas — that is the cap `--solution-h` gives it so it
   does not eat the body — so at 42 the headline GOES INTO THE PHOTO in all
   four languages, slide or no slide. At 32 it does in none: 537 / 605 / 596
   / 634 against 652. So "sin ponerse encima de la imagen" and "42" are
   incompatible, and the instruction rules.
   It is a real loss against the file and it is stated: #2088:30 and #2220:53
   draw it at 42. Getting it back is one line, with the overlap back too. */
.fl-solution-h {
  /* The file's 32, now without the `+ 10 * --full`. And explicit: it was the
     only line giving the headline a size, and without it it falls to the
     inherited 24. */
  font-size: calc(32 * var(--k));
  width: max-content;
  transform: translateX(calc(var(--full, 0) * max(0px, min(289 * var(--k), 588 * var(--k) - 100%))));
}

.fl-solution-img {
  filter: blur(calc(5.8 * (1 - var(--full, 0)) * var(--k)));
  opacity: calc(0.55 + 0.45 * var(--full, 0));
}

.fl-case-frame {
  width: calc((1115 + 26 * var(--full, 0)) * var(--k) + var(--fl-bleed));
}

.fl-case-img {
  left: calc(-12.2 * (1 - var(--full, 0)) * var(--k));
}

.fl-case-cap {
  transform: translateX(calc(-39 * var(--case, 0) * var(--k)));
}

.fl-notebook {
  transform: translateX(calc(38 * var(--case, 0) * var(--k)));
}

/* Serena rides like Casa Dolmen, mirrored (Denis, 3-sep-2026: "aplicar el
   mismo efecto al texto y img que hay en la seccion de casa dolmen — leve
   desplazamiento al scrolear 1:1 pero desde derecha a izq, para que tenga
   sentido"). Same `--full` the engine writes on the block, same numbers,
   opposite side: the picture slides LEFT inside its box as the block enters
   (Dolmen's slides right), the caption travels +39 (Dolmen's -39). The
   picture moves through `object-position`, not through the box: the renders
   are 2280x1701 (1.34) in a 1197:920 box (1.30), so `cover` leaves ~15 of
   slack a side at k = 1 and the 12.2 fits inside it. Poster and frames take
   the same rule, so the rotator keeps matching the photo under it pixel for
   pixel (`pano.js`), and no box changes size. Desktop only: the phone block
   positions the caption with `--full` on its own terms. */
@media (min-width: 800px) {
  .fl-inter360-img,
  .fl-inter360-rotator img {
    object-position: calc(50% + 12.2 * (1 - var(--full, 0)) * var(--k)) 50%;
  }

  .fl-inter360-cap {
    transform: translateX(calc(39 * var(--full, 0) * var(--k)));
  }
}

/* 144 IN BOTH STATES since 27-ago-2026, and the 24 travel is withdrawn.
   It went 120 at rest and 144 open because that is what the file's two nodes
   measured. On changing the cuaderno copy — #2152:46 becomes "Investigacion
   y opinion sobre los temas que atraviesan este oficio..." — the file also
   widened the box: 251x144 in page (#2152:46) AND 251x144 at full screen
   (#2152:25). Same height, so there is no growth left to animate.
   It is not a fit to the text: with the old 120 the body overflowed 24px,
   but the 144 comes from the two nodes, not from measuring the paragraph. */
.fl-notebook-body {
  height: calc(144 * var(--k));
}

/* The column travels +38 and this row travels +52, so the link adds the
   missing 14. And it drops 7. Both numbers come from comparing the same
   nodes in the two frames, with the shift the label and the body fix
   (frame x - 67, frame y + 2220, also checked with rule `2132:23`):

     arrow  page 1136,2755  ->  `2238:130` 1255,542  = canvas 1188,2762
     text   page 1168,2745  ->  `2238:131` 1287,532  = canvas 1220,2752

   It used to say -9, which was the travel of `Indice de articulos`, the node
   the file no longer draws. */
.fl-notebook-link {
  transform: translate(
    calc(14 * var(--case, 0) * var(--k)),
    calc(7 * var(--case, 0) * var(--k)));
}

/* ── project viewer ────────────────────────────────────────────────
   Two Penpot screens that do not live in the corpus: the `Selector` (board
   `Selector - APThotel_still vs 360`, 1047137d…) and the ficha from the
   `Projects VISOR` board (2f4f762c…), both 1520x920. They open over the page
   from a grid tile.

   THE CANVAS HERE IS NOT THE PINNED 1520x920: the boards represent the whole
   window, so what is preserved of their geometry is the RELATIONS — each
   block below says which axis it anchors against and by which factor it
   scales. It is the same thing `.fl-frame` does with the cover. */

.fl-viewer {
  /* `<dialog>` brings a border, padding, `fit-content` width and auto margin. */
  position: fixed;
  inset: 0;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  /* `contain` so the wheel does not chain to the page beneath. Not enough on
     its own — see `viewer.js`, which also restores the scroll on close — but
     it is the cheap half of the problem. */
  overflow: hidden;
  overscroll-behavior: contain;
  background: #fff;
  color: #000;
}

.fl-viewer::backdrop {
  background: #fff;
}

/* Opening and closing CROSS-FADE instead of cutting (Denis, 01-sep: "muy
   abrupta"). Declared invention — the board draws no screen transitions; a
   short fade with no displacement is the minimum that softens without
   inventing choreography. The close is orchestrated by `viewer.js`
   (`is-closing` and then `close()`); under `prefers-reduced-motion` the
   class is not even added. */
.fl-viewer[open] {
  animation: fl-viewer-in 0.25s ease;
}

.fl-viewer.is-closing {
  animation: fl-viewer-out 0.2s ease forwards;
}

@keyframes fl-viewer-in {
  from { opacity: 0; }
}

@keyframes fl-viewer-out {
  to { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .fl-viewer[open],
  .fl-menu.is-open::before,
  .fl-menu.is-open a[data-nav] {
    animation: none;
  }
}

.fl-selector,
.fl-sheet {
  position: absolute;
  inset: 0;
}

.fl-selector[hidden],
.fl-sheet[hidden] {
  display: none;
}

/* ── Selector — board `Selector - APThotel_still vs 360` (1047137d…) ──
   Redrawn on 01-sep-2026 (Denis): no longer two full-bleed halves with the
   white bar, but two 559x677 CARDS on white — still at 94,121 and 360 at
   845,121 (board asymmetry: 94 of left margin, 116 of right) — with the
   Gloock 48 label centred over each and a 20% black veil as the pointed
   state (the board draws it over still, with its _Mouse annotation on top).
   The frame is the whole window; here the design pixel is the classic
   min(W/1520, H/920), because what has to fit is the pair of cards, which
   takes nearly the frame's full width. Positions hang off the centre with
   the board's offsets. */

.fl-selector {
  --u: min(calc(100vw / 1520), calc(100svh / 920));
}

.fl-sel-half {
  position: absolute;
  top: calc(50% - 339 * var(--u));
  width: calc(559 * var(--u));
  height: calc(677 * var(--u));
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: none;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.fl-sel-still { left: calc(50% - 666 * var(--u)); }
.fl-sel-tour { left: calc(50% + 85 * var(--u)); }

/* The assets come cropped from the old design at 761x920 (proportion .827);
   the new card measures 559x677 (.826) — the same to within 0.1%, so `cover`
   barely decides anything. If the pixel diff said otherwise, they get
   re-baked. */
.fl-sel-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* The 20% veil on hover is declared INVENTION: the new board draws no hover
   state (its black@0.2 sits UNDER the image — a backing, invisible in the
   export — and the only mark is the _Mouse annotation over still, which says
   "click here", not how). Some notice that the card responds is needed; a
   soft veil is the closest thing to the rest's language (the tiles' veil). */
.fl-sel-half::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.2);
  opacity: 0;
  transition: opacity 0.25s ease;
}

.fl-sel-half:hover::after,
.fl-sel-half:focus-visible::after {
  opacity: 1;
}

/* Gloock 48 / 1.92 / #FFFEFA, centred over its card: on the board the tour's
   label nails its card's centre (1125 vs 1124.5) and still's is off by 8.5 —
   it reads as centred and the rule wins. */
.fl-sel-label {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: calc(48 * var(--k));
  letter-spacing: calc(1.92 * var(--k));
  color: #fffefa;
  white-space: nowrap;
  pointer-events: none;
}

/* ── project ficha — board `Projects VISOR` (Penpot 2f4f762c…) ───────
   Since 01-sep-2026 the template is the new board's, measured over MCP in
   that session: frame `Projects - NordicHommeoffice` 1520x920 representing
   the whole window. The FITTING RULE does not change: the image fits inside
   955x614 keeping proportion and is centred on both axes — 283 =
   (1520-955)/2 and 153 = (920-613)/2 on the board, same as in the old QUEUE.
   `--vw` and `--vh` are that box, which depends on the photo and is set by
   `viewer.js`.

   What hangs off the photo is only the caption, and in a new place: FLUSH
   with its left edge (the board draws it at x 283, the photo's same x) and
   +28 below the bottom one (794 - 766). The rest hangs off the WINDOW, not
   the photo: `seguir viendo` centred at the bottom, the arrows at 40 from
   each edge and the `X` top right — board coordinates, annotated rule by
   rule. */

/* THE WINDOW IS THE FRAME (Denis, 01-sep): the 1520x920 board represents the
   whole browser, so the ficha cannot scale on width alone — in a window
   shorter than its proportion, the photo did not fit the height and the
   caption and `seguir viendo` drifted from where the board puts them.

   `--u` is the DESIGN PIXEL and THE PHOTO is drawn with it — only the photo
   and its anchoring: the labels, the caption and the glyphs go with `--k`,
   the rest of the site's scale (Denis, 01-sep: the font size is not
   touched). HEIGHT RULES (100svh/920): that way the photo takes of the
   window the proportion it takes on the board, which is what one sees. It
   only yields when the width cannot take it: the cap is that the viewer's
   widest box (955) fits leaving 80 per side for the arrows' lane (40 of
   margin + 16 of glyph + air) — that 80 is declared invention, the board
   draws no narrow window. With plain `min(W/1520, H/920)`, a window squarer
   than the frame (1200x1024, Denis 01-sep) shrank the photo to the width and
   left `seguir viendo` dangling half a screen from it.
   `--uv` is the pure VERTICAL ANCHOR: the ficha's heights are proportions of
   the window (32 up top the X, 479 the arrows' stroke, 80 at the bottom the
   `seguir viendo`), whatever happens with the width — the same criterion the
   notebook reader's arrows already mount. The HORIZONTAL edge margins (40 on
   each side) stay with `--k`, against the window, like the rest of the site.
   At 1520x920 the three factors are 1 and every number is the board's as-is. */
.fl-viewer {
  --uv: calc(100svh / 920);
}

.fl-sheet {
  --u: min(calc(100svh / 920), calc((100vw - 160px) / 955));
}

/* The container receives focus on open — see `viewer.js` — and a container
   draws no ring: what is announced is the screen, not a control. */
.fl-sheet:focus {
  outline: none;
}

.fl-sheet-fig {
  position: absolute;
  inset: 0;
  margin: 0;
}

/* The views' canvas. `overflow: hidden` because during the slide two images
   coexist, the one leaving at the top and the one entering from below. */
.fl-sheet-views {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.fl-sheet-img {
  position: absolute;
  left: calc(50% - var(--vw) * var(--u) / 2);
  top: calc(50% - var(--vh) * var(--u) / 2);
  width: calc(var(--vw) * var(--u));
  height: calc(var(--vh) * var(--u));
  object-fit: cover;
  /* `transform` only: a project change touches `--vw`/`--vh`/`src` and must
     not animate; a view step moves `transform` and does. */
  transition: transform 0.6s ease;
}

/* A single text node in the file (#2077:119): `NordicHomeOffice fluint
   studio`, with Gloock 24 as base and a run in Lato Light 15 from `fluint`
   on. The 0.96 tracking is paragraph-level and thus falls on both runs —
   0.04em on the name and 0.064em on the caption — so it goes here in canvas
   units and not as `em`. It is split into two explicit lines: in the file
   the 227 box does it, and leaving it to the width would mean a long name
   breaking where it should not. */
.fl-sheet-caption {
  position: absolute;
  left: calc(50% - var(--vw) * var(--u) / 2);
  top: calc(50% + var(--vh) * var(--u) / 2 + 28 * var(--k));
  width: calc(227 * var(--k));
}

/* A project's views have different boxes and the caption hangs off the box:
   when the VIEW changes, it travels to its new place in step with the slide
   instead of jumping. Only then — `is-viewed` is set by `cambiarVista` and
   removed by `pintarFicha`: on a PROJECT change the photo enters dry and the
   caption has to enter with it, not glide in 0.6s later (caught in the
   01-sep review). Invention — the board draws a single screen per project
   and says nothing of this. */
.fl-sheet.is-viewed .fl-sheet-caption {
  transition: left 0.6s ease, top 0.6s ease;
}

.fl-sheet-name {
  display: block;
  font-size: calc(24 * var(--k));
  letter-spacing: calc(0.96 * var(--k));
}

/* On the new board each run carries its own 0.04em tracking — 0.96 for the
   name in Gloock 24 and 0.6 for the caption in Lato 15 — no longer the old
   QUEUE's paragraph 0.96 falling on both. */
.fl-sheet-studio {
  display: block;
  font-weight: 300;
  font-size: calc(15 * var(--k));
  letter-spacing: calc(0.6 * var(--k));
}

/* `seguir viendo` — board group: Lato Light Italic 16 text with 0.64 of
   tracking at 709,820 (92 wide) and `Arrow 1`, a vertical arrow of 10 with
   stroke 1 whose tip falls at x 810. The group (709..810) sits centred on
   the canvas — its centre is 759.5 on a canvas whose centre is 760 — so in x
   it hangs off the centre: 760 - 709 = 51 to the left. In y it hangs off the
   window's BOTTOM EDGE, not the centre: the board is the whole window and
   the text ends at 840, i.e. 80 from the edge (920 - 840). */
.fl-sheet-continue {
  position: absolute;
  left: calc(50% - 51 * var(--k));
  bottom: calc(80 * var(--uv));
  padding: 0;
  border: 0;
  background: none;
  font-size: calc(16 * var(--k));
  font-weight: 300;
  font-style: italic;
  letter-spacing: calc(0.64 * var(--k));
  color: #000;
  text-align: left;
  cursor: pointer;
}

/* Pressed in a chain, like the old `next project`: the area grows without
   moving anything drawn. Added, not on the board. */
.fl-sheet-continue::before {
  content: '';
  position: absolute;
  inset: calc(-14 * var(--k)) calc(-16 * var(--k));
}

/* `Arrow 1`. The glyph is `arrow-down-10.svg` (8x11), which already is this
   arrow: a line of 10 with a tip, stroke 1. Its vertical axis sits at the
   board's x 810, i.e. 101 from the text's start: 101 - 4 centres the svg's
   8. The stroke's `y` is 826..836, +6 from the text's top edge. */
.fl-sheet-continue::after {
  content: '';
  position: absolute;
  left: calc(97 * var(--k));
  top: calc(6 * var(--k));
  width: calc(8 * var(--k));
  height: calc(11 * var(--k));
  background: url('/assets/images/2026/arrow-down-10.svg') center / contain no-repeat;
}

/* The project arrows — `Arrow 2` (forward, right) and `Arrow 3` (back,
   left): a line of 16 with stroke 1.5, round tail and tip, painted 40 from
   their window edge and with the stroke at y 479. The same number the
   notebook reader's arrows already mount, which copied this board: the
   height is kept as a PROPORTION of the window (479/920), because the board
   is the whole window. The glyph is `arrow-viewer.svg`, built from the
   board's numbers (Penpot's SVG export comes back empty) and the head with
   `arrow-down-10.svg`'s proportions, which is the same language. */
.fl-sheet-arrow {
  position: absolute;
  top: calc(479 * var(--uv) - 5.5 * var(--k));
  width: calc(16 * var(--k));
  height: calc(11 * var(--k));
  padding: 0;
  border: 0;
  background: url('/assets/images/2026/arrow-viewer.svg') center / contain no-repeat;
  cursor: pointer;
}

.fl-sheet-next {
  right: calc(40 * var(--k));
}

.fl-sheet-prev {
  left: calc(40 * var(--k));
  transform: scaleX(-1);
}

/* THE SIDE COLUMN IS ARROW (Denis, 01-sep): the pressable area grows from
   the glyph to the white on its side — from the window edge to the photo's
   in width, and in height everything but a 100 strip at the top (where the X
   lives) and another at the bottom (the caption and `seguir viendo`). "Not
   the whole side", said by him.
   The column is the glyph's ::before, which does not move from its 40/479;
   on `atras` the button's scaleX(-1) mirrors it to the left edge by itself.
   As the ::before's anchor, the button sits at right 40k / top 479uv-5.5k. */
.fl-sheet-arrow::before {
  content: '';
  position: absolute;
  right: calc(-40 * var(--k));
  width: calc(50vw - var(--vw) * var(--u) / 2);
  top: calc(5.5 * var(--k) - 379 * var(--uv));
  height: calc(100svh - 200 * var(--uv));
}

.fl-sheet-arrow:focus-visible,
.fl-sheet-x:focus-visible,
.fl-sel-x:focus-visible,
.fl-sheet-continue:focus-visible {
  opacity: 0.6;
}

/* The `X` — the close this board DOES draw: the `X` character in Lato Light
   16, 10x20 box at 1470,32, i.e. 40 from the right edge (1520 - 1480). */
.fl-sheet-x,
.fl-sel-x {
  position: absolute;
  right: calc(40 * var(--k));
  top: calc(32 * var(--uv));
  padding: 0;
  border: 0;
  background: none;
  font-family: 'Lato', Arial, sans-serif;
  font-size: calc(16 * var(--k));
  font-weight: 300;
  line-height: 1.2;
  letter-spacing: 0;
  color: #000;
  cursor: pointer;
}

.fl-sheet-x::before,
.fl-sel-x::before {
  content: '';
  position: absolute;
  inset: calc(-16 * var(--k));
}

/* INVENTION, and marked: the file draws no close. It is hidden just like
   `.fl-a11y` while nobody looks for it, and appears the moment it receives
   keyboard focus — the only case where Esc is not enough, because it must be
   announceable. */
.fl-viewer-close {
  position: absolute;
  left: calc(40 * var(--k));
  top: calc(40 * var(--k));
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
  background: none;
  font-family: 'Lato', Arial, sans-serif;
  font-size: calc(16 * var(--k));
  white-space: nowrap;
  cursor: pointer;
}

.fl-viewer-close:focus-visible {
  width: auto;
  height: auto;
  padding: calc(8 * var(--k)) calc(16 * var(--k));
  overflow: visible;
  clip-path: none;
  background: #000;
  color: #fff;
}

/* Phone chrome that lives in the shared markup: on desktop it does not
   exist. Its real rules are in the block below. */
.fl-burger,
.fl-talk-solution {
  display: none;
}

/* ── phone — the MBL_* boards of the Penpot file ───────────────────
   Measured screens since 2026-09-02: fifteen 375x812 boards (see
   docs/mobile-penpot-spec.md for the node-by-node dump). Same technique as
   the canvas above — every number is the board's own, times `--k` — but the
   layout is stacked flow, not one absolute canvas: each full-screen board
   becomes a `812 * --k` section and the in-between ones stack.
   What the boards do NOT draw is named in the spec's "inventions" section
   and marked NOT IN THE DESIGN below. Nothing here may leak upward: the
   whole block is inside the 799 query, and the desktop battery is the gate. */

@media (max-width: 799px) {
  .fl-corpus {
    /* `--k` is NOT redeclared: it is the phone's :root one (desk2026.css),
       inherited — effects.js removes its inline value below 800. */
    height: auto;
    /* No side padding: half the boards are full-bleed photo. The text sets
       its own 20 from the design, block by block. */
    padding: 0;
    overflow: visible;
  }

  /* One sweep out of absolute placement — every node, not just the top-level
     blocks, or the ones left behind escape their block and overlap it. What
     the boards place is re-declared below, after this rule. */
  .fl-corpus {
    --kp: var(--k);
    --grow: 0px;
    --fit: 0px;
    --nav: 0px;
  }

  .fl-corpus *,
  .fl-corpus > * {
    position: static;
    left: auto;
    top: auto;
    right: auto;
    bottom: auto;
    width: auto;
    height: auto;
  }

  /* The sweep returns the reader-only headings to the flow, where they take
     up their line again — the vision came out 27px low because of the unseen
     h2 (same finding as the 01-sep redo: three dead gaps). */
  .fl-corpus .fl-a11y {
    position: absolute;
    width: 1px;
    height: 1px;
  }

  /* ── the bar: logo + burger (MBL boards' shared chrome) ──────────
     One fixed bar, no background: logo at 20,16 (32px lockup, 94x39) and
     the burger at the right. Ink is black over white sections and white
     over the photographic ones — `mobile.js` reads which section is under
     the bar; no luminance sampling (it cannot resolve ink over ink). */
  .fl-menu {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 4;
    display: block;
    width: 100%;
    height: calc(55 * var(--k));
    margin: 0;
    padding: 0;
    background: none;
    pointer-events: none;
  }

  .fl-menu.fl-menu-fixed {
    left: 0;
    width: 100%;
  }

  /* The links only exist in the drop-down. */
  .fl-menu a[data-nav] {
    display: none;
    position: static;
    left: auto;
    top: auto;
  }

  .fl-menu .fl-logo {
    position: absolute;
    left: calc(20 * var(--k));
    top: calc(16 * var(--k));
    width: calc(94 * var(--k));
    height: calc(39 * var(--k));
    margin: 0;
    transform: none;
    pointer-events: auto;
    color: var(--fl-ink, #000);
  }

  /* `.fl-warp` alongside, for the same reason `desk2026.css` writes it into
     the desktop lockup: the lens copy lives in `.fl-lens-layer`, outside
     `.fl-menu`, so a rule that needs the bar as an ancestor never reaches it.
     Without these two the copy kept the DESKTOP pair here — 48px glyphs with
     `luint` at 24 — inside the phone's 94-wide box, and the bubble drew a
     shredded `fluint` over the real one (Denis, 3-sep-2026: "en mobile al
     pasar el raton falla"). One declaration for the node and its double;
     `tools/lens-pairs.js` now sweeps this width. */
  .fl-menu .fl-logo span,
  .fl-warp .fl-logo-f,
  .fl-warp .fl-logo-rest {
    position: absolute;
    font-size: calc(32 * var(--k));
  }

  /* The lockup at 32: `luint` starts at 17 of the group (board's 37-20). */
  .fl-menu .fl-logo .fl-logo-rest,
  .fl-warp .fl-logo-rest {
    left: calc(17 * var(--k));
  }

  /* boton_menu: two 20-wide lines, stroke 2, 7 apart (board y 24/31). The
     44px hit area is not drawn anywhere — it is the touch floor. */
  .fl-burger {
    display: block;
    position: absolute;
    right: calc(8 * var(--k));
    top: calc(6 * var(--k));
    width: 44px;
    height: 44px;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    pointer-events: auto;
  }

  .fl-burger span {
    position: absolute;
    left: calc(50% - 10 * var(--k));
    width: calc(20 * var(--k));
    height: 2px;
    background: var(--fl-ink, #000);
    transition: transform 0.25s ease, background 0.3s ease;
  }

  .fl-burger span:first-child { top: calc(50% - 4.5 * var(--k)); }
  .fl-burger span:last-child { top: calc(50% + 2.5 * var(--k)); }

  /* The bar's ink over the photo screens (written by mobile.js). */
  .fl-menu.is-light-ink { --fl-ink: #fff; }


  /* ── MBL_menu: the drop-down ─────────────────────────────────────
     White full screen, the links centred in Gloock 20 with a 54 rhythm, the
     close where the burger was. The board draws three links; the site has
     five destinations and keeps them — NOT IN THE DESIGN, said here. */
  /* The nav IS the overlay: a centred column. The logo and the burger are
     absolute, so they do not take part. */
  .fl-menu.is-open {
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: calc(30 * var(--k));
    pointer-events: auto;
    --fl-ink: #000;
  }

  /* Open and close FADE (Denis, 3-sep-2026: "mete un efecto a abrir cerrar
     nav menu de mobile de fade o lo que sea coherente con el proyecto"):
     the viewer's own keyframes and timing, 0.25 in / 0.2 out, on the white
     and the links only — the logo and the burger stay put. The white is a
     pseudo so that it can fade without the bar; `is-closing` is orchestrated
     by mobile.js like the viewer's close, and `is-open` stays until the fade
     ends. Declared invention: the MBL_menu board draws no transition. */
  .fl-menu.is-open::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: #fff;
    animation: fl-viewer-in 0.25s ease;
  }

  .fl-menu.is-open a[data-nav] {
    animation: fl-viewer-in 0.25s ease;
  }

  .fl-menu.is-closing::before,
  .fl-menu.is-closing a[data-nav] {
    animation: fl-viewer-out 0.2s ease forwards;
  }

  .fl-menu.is-open a[data-nav] {
    display: block;
    font-family: 'Gloock', Georgia, serif;
    font-size: calc(20 * var(--k));
    font-weight: 400;
    letter-spacing: var(--fl-track);
    color: #000;
    text-align: center;
  }

  /* Mobile ships without `estudio` and `cuaderno` for now (Denis,
     03-sep-2026: "en mobile saldremos sin eso") — the links stay in the
     markup for desktop; this width just does not draw them. */
  .fl-menu.is-open a[data-nav='studio'],
  .fl-menu.is-open a[data-nav='cuaderno'] {
    display: none;
  }

  .fl-menu.is-open .fl-burger span {
    background: #000;
  }

  .fl-menu.is-open .fl-burger span:first-child {
    transform: translateY(calc(3.5 * var(--k))) rotate(45deg);
  }

  .fl-menu.is-open .fl-burger span:last-child {
    transform: translateY(calc(-3.5 * var(--k))) rotate(-45deg);
  }

  /* The scroll lock lives on the overlay, not on <html>: `overflow: hidden`
     there made the document unscrollable and, on a phone, brought the
     browser's toolbar back — `innerHeight` changed and the screens
     re-laid out under the fade (Denis, 3-sep-2026: "se mueve la vista
     abajo arriba"). The overlay covers the screen, so every touch starts
     on it; mobile.js stops wheel and scroll keys the same way. */
  .fl-menu.is-open {
    touch-action: none;
  }

  /* ── MBL_¿qué somos? → .fl-studio ─────────────────────────────────
     The photo is one full-bleed cover behind a clip window: fitted at
     20,206 335x400 in the flow state, open to the whole 375x812 in the
     FULL SCREEN board. The photo never scales — the window opens, which is
     the desktop's own corner-to-corner move told in clip-path. */
  .fl-studio {
    position: relative;
    width: 100%;
    height: calc(812 * var(--k));
  }

  /* `--full` is the block's progress, 0 in flow (the board's fitted window:
     335x400 with 20 of margin) to 1 head-on (full bleed, FULL SCREEN
     board). corpus.js writes it at this width too, with its easing — the
     same number that on desktop clears the `cómo será` photo. We tried
     `animation-timeline: view()` and this Chromium reported the animation
     finished with the section still entering (finished/progress 1). */
  .fl-studio-frame {
    position: absolute;
    inset: 0;
    overflow: hidden;
    clip-path: inset(
      calc(206 * var(--k) * (1 - var(--full, 0)))
      calc(20 * var(--k) * (1 - var(--full, 0)))
    );
  }

  .fl-studio-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* The titles sit over the photo in both states (board 93.6,348 and
     146.6,392, identical in the FULL). */
  .fl-studio-h {
    position: absolute;
    left: calc(93.6 * var(--k));
    top: calc(348 * var(--k));
    width: auto;
    font-size: calc(24 * var(--k));
    color: #fff;
  }

  .fl-studio-sub {
    position: absolute;
    left: calc(146.6 * var(--k));
    top: calc(392 * var(--k));
    font-size: calc(14 * var(--k));
    font-style: italic;
    letter-spacing: var(--fl-track);
    color: #fff;
  }

  /* ── MBL_about → .fl-solution + .fl-about, one screen ────────────
     The circles photo sits blurred behind both: board -286,55 731x702,
     blur 12 (Penpot radius, so 6 in CSS — same halving the desktop reads
     from the raw API). The `cómo será` tag is not drawn on mobile. */
  .fl-solution {
    position: relative;
    width: 100%;
    height: calc(812 * var(--k));
    overflow: hidden;
  }

  /* Board geometry IN PROPORTION TO THE REAL WIDTH, not to the canvas
     capped at 480: with `--k` pinned, from 480 to 799 the photo fell short
     on the right (Denis, 02-sep: "en según qué anchos no queda a ras").
     Horizontally -286/375 and 731/375 of the section width always cover;
     vertically it travels with `--full` between the two boards: 55,702 in
     flow and 0,812 (full bleed) in the FULL SCREEN, while the blur clears
     with the desktop rule, untouched here. */
  .fl-solution-img {
    position: absolute;
    left: calc(-286 * var(--k));
    top: calc((55 - 55 * var(--full, 0)) * var(--k));
    right: auto;
    width: calc(731 * var(--k));
    height: calc((702 + 110 * var(--full, 0)) * var(--k));
    object-fit: cover;
    transform: none;
  }

  .fl-solution .fl-tag {
    display: none;
  }

  .fl-solution-h {
    position: absolute;
    left: calc(20 * var(--k));
    top: calc(215 * var(--k));
    width: calc(321 * var(--k));
    font-size: calc(20 * var(--k));
    transform: none;
  }

  /* let's talk — board 20,698, Lato 11 Light italic with its 21.4 rule.
     The element only exists for this screen; desktop hides it. */
  /* The `top` (board's 698, with its touch area) is set by the touch block. */
  .fl-talk-solution {
    display: block;
    position: absolute;
    left: calc(20 * var(--k));
    font-size: calc(11 * var(--k));
    font-weight: 300;
    font-style: italic;
    color: #000;
    text-decoration: none;
  }

  .fl-talk-solution::after {
    content: '';
    display: inline-block;
    /* At the box's foot, like the board (rule at 711 of a 698+13 node):
       resting on the baseline it floated and read as a dash, not an
       underline. */
    vertical-align: calc(-2.5 * var(--k));
    width: calc(21.4 * var(--k));
    height: 1px;
    margin-left: calc(9 * var(--k));
    background: #000;
  }

  /* The about body rides over the same screen: board 20,301, 246 wide,
     Lato 12 on 1.2. Negative margin pulls it up onto `.fl-solution`,
     which is 812 tall no matter what, so the offset is exact. */
  /* The exact hole, like the vision over Serena: a box the height of the
     gap (812-301 = 511) and an equal negative margin, so the next section
     starts where `.fl-solution` ends whatever the text measures in each
     language. `pointer-events: none` because the box rides over the
     `let's talk` and ate its tap (02-sep review, confirmed:
     elementFromPoint returned this box). */
  .fl-about {
    position: relative;
    z-index: 1;
    width: calc(246 * var(--k));
    height: calc(511 * var(--k));
    margin: calc(-511 * var(--k)) 0 0 calc(20 * var(--k));
    font-size: calc(12 * var(--k));
    pointer-events: none;
    overflow: hidden;
  }

  .fl-about p {
    margin: 0 0 calc(14.4 * var(--k));
    white-space: normal;
  }

  /* ── cuaderno: DOES NOT EXIST ON THE BOARDS ──────────────────────
     No MBL draws it and it is not invented here (Denis, 02-sep-2026). The
     reader remains reachable through its menu link. */
  .fl-notebook {
    display: none;
  }

  /* The formatos label, without the canvas's rule: the board writes plain
     `formatos` — the only rule on that screen is the promoted format's. */
  .fl-tag {
    position: static;
    transform: none;
    font-size: calc(24 * var(--k));
  }

  .fl-tag::after {
    display: none;
  }

  /* The canvas's `4K` is not on the board either. */
  .fl-formats-4k {
    display: none;
  }

  /* ── MBL_caso real → .fl-case ────────────────────────────────────
     Full-bleed screen; caption at 20,40 (the in-flow board), Gloock 24 over
     Lato 15 Light, white. */
  .fl-case {
    position: relative;
    width: 100%;
    height: calc(812 * var(--k));
  }

  .fl-case-frame {
    position: absolute;
    inset: 0;
    overflow: hidden;
  }

  .fl-case-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transform: none;
  }

  /* The caption SLIDES between the two points the file draws: 40 on the
     in-flow board (entering) and 726 on the FULL SCREEN (head-on) —
     Denis, 02-sep: "cuando entras el texto está arriba y a medida que
     deslizas va bajando". It travels with `--full`, which corpus.js writes
     on `.fl-case` at this width too, with its easing. */
  .fl-case-cap {
    position: absolute;
    left: calc(20 * var(--k));
    top: calc((40 + 686 * var(--full, 0)) * var(--k));
    width: auto;
    transform: none;
  }

  /* The file breaks the title by its box: «Casa» / «Dolmen» on two lines
     (Denis's screenshot of the Penpot render, 02-sep). `min-content`
     breaks per word, so it comes out the same whatever our fonts measure —
     without touching the text the desktop paints on one line. */
  .fl-case-cap .fl-display {
    display: block;
    width: min-content;
    white-space: normal;
    font-size: calc(24 * var(--k));
    color: #fff;
  }

  /* No gap between lines: the file is ONE two-line node at 1.2 leading,
     not two separate blocks (Denis, 02-sep). */
  .fl-case-cap .fl-text {
    display: block;
    margin-top: 0;
    font-size: calc(15 * var(--k));
    font-weight: 300;
    color: #fff;
  }

  /* ── MBL_caso_2 → .fl-projects: the edge-to-edge column ──────────
     Full-width photos at the canvas heights (aspect 375/--h), 44 apart,
     names overlaid Gloock 24 near each photo's lower edge, 360 badge at
     30x30. The heading is not drawn; it stays for readers only. */
  /* The column's board (MBL_caso_2) opens with 73 of white — the logo's
     chrome — before the first photo: that is the separation from the Casa
     Dolmen screen (Denis, 02-sep: "no hay separación"). */
  /* 122 = the board's 73 of chrome + 29 of heading + 20 of air: the
     heading is not on the board (Denis asks for it, 02-sep: "integrado
     como el resto de títulos") and room is made for it BELOW the logo's
     slot — the #projects anchor pins the section to the top, and at the
     formatos pattern's 24 it would live permanently under the bar's logo. */
  .fl-projects {
    position: relative;
    width: 100%;
    padding-top: calc(122 * var(--k));
  }

  .fl-projects-h {
    position: absolute;
    left: calc(20 * var(--k));
    top: calc(73 * var(--k));
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
    font-size: calc(24 * var(--k));
  }

  /* `seguir viendo` opens and closes LIKE ON DESKTOP: the gallery is a
     clipping WINDOW whose height grows with `--sv` (the number corpus.js
     flies along its 650ms curve and whose closing pins the button) and
     the photos are REVEALED at their size, never shrunk (Denis, 02-sep:
     "es como que las img se encojen; quiero el mismo que desktop"). Both
     numbers come from the column itself: row 1 measures
     361+507+380+483 + 3 gaps of 44 = 1863; rows 2-4 add
     44+1552+132 + 44+1535+132 + 44+1128+88 = 4699. Change one --h in the
     markup and these two change with it — same rule as the canvas's 986. */
  .fl-rows {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    height: calc((1863 + 4699 * var(--sv, 0)) * var(--k));
  }

  .fl-row {
    display: contents;
  }

  /* The board's gap (44) as the tile's margin; the tile NEVER changes its
     box — what moves is the clip above. */
  .fl-tile {
    position: relative;
    width: 100%;
    aspect-ratio: 375 / var(--h);
    flex-shrink: 0;
    margin-top: calc(44 * var(--k));
    padding: 0;
    border: 0;
  }

  .fl-row1 .fl-tile:first-child {
    margin-top: 0;
  }

  /* Folded, what remains under the clip leaves the tab order and the
     reader; the delay covers the return flight. */
  .fl-projects:not(.is-open) :is(.fl-row2, .fl-row3, .fl-row4) .fl-tile {
    visibility: hidden;
    transition: visibility 0s 0.7s;
  }

  .fl-tile-cap {
    display: block;
    position: absolute;
    left: calc(20 * var(--k));
    bottom: calc(44 * var(--k));
    opacity: 1;
    transform: none;
  }

  .fl-tile-cap .fl-display {
    position: static;
    display: block;
    font-size: calc(24 * var(--k));
    color: #fff;
  }

  /* The board only labels the two tours; the name on all of them is the
     same thing desktop does on hover. The studio does not fit in the mobile
     label. */
  .fl-tile-cap .fl-text {
    display: none;
  }

  .fl-corpus .fl-tile-360 {
    position: absolute;
    right: calc(20 * var(--k));
    bottom: calc(32 * var(--k));
    width: calc(30 * var(--k));
    height: calc(30 * var(--k));
  }

  /* The vertical margin is set by the touch block (26 from the board's strip). */
  .fl-more {
    position: static;
    display: block;
    width: fit-content;
    margin: 0 auto;
    font-size: calc(16 * var(--k));
    font-weight: 300;
    font-style: italic;
  }

  /* The canvas's arrow is absolute and with the button gone `static` it
     anchored to `.fl-corpus`: it painted loose at the hero's seam (Denis,
     02-sep-2026, screenshot). In flow, behind the text. */
  .fl-more::after {
    position: static;
    display: inline-block;
    vertical-align: baseline;
    margin-left: calc(10 * var(--k));
    width: calc(8 * var(--k));
    height: calc(11 * var(--k));
  }

  /* ── MBL_formatos → .fl-formats ─────────────────────────────────
     One 375x812 screen. The copy TRAVELS: it sits under whichever format
     is promoted — the desktop swaps it at the promoted entry's side, the
     phone slides it under the tapped one (Denis, 02-sep: "aparecen debajo
     al pasar el raton o darle click"). Flex order moves it without moving
     the DOM: `:has()` reads which item carries `is-active`. The board's
     own gaps ride each box as margins, so the resting state lands on the
     board's ys — 24/237/308/442/503 — and the moved states keep its
     rhythm. Rest size 34, promoted 40 italic with its 45x3 rule; the
     `hablemos` links are not drawn. */
  /* CONTENT height, not the board's 812: that one draws the promoted
     state, and with the rest state promoting nothing a fixed screen left
     the FAQ hanging at half distance and a void down to Serena (Denis,
     02-sep). The board's two airs are kept as margins: 184 from the list
     to the FAQ and 23 from the FAQ to the foot — constant in any state. */
  .fl-formats {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    height: auto;
    padding: calc(24 * var(--k)) calc(20 * var(--k)) calc(23 * var(--k));
    pointer-events: auto;
  }

  .fl-formats-tag {
    order: 0;
    margin: 0;
  }

  .fl-formats-list {
    display: contents;
  }

  /* `top`/`left` explicitly zeroed: the canvas's jumps (`.is-active` moves
     the promoted one to another y) displace a `relative`. */
  .fl-formats-item,
  .fl-formats-item.is-active {
    position: relative;
    top: auto;
    left: auto;
    width: max-content;
    /* The board breaks «experiencia interactiva» into two lines within the
       column; without a cap, English ran off the screen on one. */
    max-width: calc(335 * var(--k));
  }

  /* The gaps: 184 from the tag to the first (board), and between formats
     THE SAME — the 20 the file draws between the two tours. The board's 50
     is the gap after the unfolded copy, not the list's resting rhythm
     (Denis, 02-sep: "la separación es la misma"). The copy brings its 23
     when opening and the next one keeps its 20 below. */
  .fl-formats-still { order: 1; margin-top: calc(184 * var(--k)); }
  .fl-formats-a { order: 3; margin-top: calc(20 * var(--k)); }
  .fl-formats-b { order: 5; margin-top: calc(20 * var(--k)); }

  .fl-formats-copy {
    order: 2;
    width: calc(335 * var(--k));
    min-height: 0;
    height: auto;
    margin: calc(23 * var(--k)) 0 0;
    padding-bottom: 0;
    font-size: calc(14 * var(--k));
    font-style: italic;
  }

  /* At rest nothing is promoted (as on desktop) and the copy does not
     exist: without this, its margin left a ghost gap in the list. */
  .fl-formats-copy:not(:has(p:not([hidden]))) {
    margin: 0;
  }

  /* The copy travels with the promoted one; without `:has` it stays under
     Still, which is the state the board draws. */
  .fl-formats:has(.fl-formats-a.is-active) .fl-formats-copy { order: 4; }
  .fl-formats:has(.fl-formats-b.is-active) .fl-formats-copy { order: 6; }

  .fl-formats-item button {
    font-size: calc(34 * var(--k));
    white-space: normal;
  }

  .fl-formats-item.is-active button {
    font-size: calc(40 * var(--k));
    -webkit-text-stroke: calc(0.22 * var(--k)) #000;
  }

  /* The promoted one's rule goes at the FOOT of the text, like an
     underline — the board draws it 39 from the item's top (Line 2: 276
     with Still at 237). At mid-height it read as a dash (Denis, 02-sep). */
  .fl-formats-item.is-active::after {
    top: calc(39 * var(--k));
    width: calc(45 * var(--k));
    height: calc(3 * var(--k));
  }

  .fl-formats-copy p {
    white-space: normal;
  }

  .fl-talk {
    display: none;
  }

  /* The screen's foot, in flow: the 184 the board leaves after the list,
     centred. Lato 16 Light italic, like the node. */
  /* The vertical margins (board's 184, with its touch area) are set by the
     touch block; here only the centring. */
  .fl-faq-link {
    order: 7;
    display: block;
    width: max-content;
    margin: 0 auto;
    font-size: calc(16 * var(--k));
    font-weight: 300;
    font-style: italic;
  }

  /* The desktop lens makes the span a block and the arrow fell to a
     second line; the board draws it BESIDE the text. */
  .fl-faq-link span {
    display: inline;
  }

  /* Geometry only: the painting (background with the glyph, already
     black) is that of this same ::after's desktop rule. */
  .fl-faq-link::after {
    content: '';
    position: static;
    display: inline-block;
    vertical-align: middle;
    width: calc(16 * var(--k));
    height: calc(16 * var(--k));
    margin-left: calc(12 * var(--k));
  }

  /* ── MBL_Serena Materia → .fl-inter360 ───────────────────────────
     Full-bleed screen showing the lower crop of the onix photo (board
     draws the 812-tall image shifted -131). Title block top-left at 28,40;
     the 360 badge 30x30 near the right edge. */
  .fl-inter360 {
    position: relative;
    width: 100%;
    height: calc(812 * var(--k));
    overflow: hidden;
  }

  /* THE BOARD'S CROP, NOT THE FULL-BLEED ONE: both boards draw the 812
     photo shifted -131 — the ceiling gets cut at the top, the bathtub
     falls to the bottom, and the photo's foot lands at 681, with air (the
     page background) down to the screen's 812. With `inset: 0` the photo
     (1.34) fit whole in height — ceiling band included — and the block's
     caption "floated" mid-photo (Denis, 02-sep: "en penpot el texto baja
     mucho mas"). The box measures the node's 812 and moves up the 131: the
     section (overflow hidden) clips what pokes out. */
  .fl-inter360-img {
    position: absolute;
    top: calc(-131 * var(--k));
    left: 0;
    width: 100%;
    height: calc(812 * var(--k));
    object-fit: cover;
    object-position: center;
  }

  .fl-inter360-rotator {
    position: absolute;
    inset: 0;
    top: calc(-131 * var(--k));
    height: calc(812 * var(--k));
  }

  /* The caption SLIDES between the two points the boards draw, like Casa
     Dolmen's (Denis, 02-sep: "al igual que casa dolmen, el texto se
     desplaza de arriba a abajo"): it enters at 28,40 (in-flow board) and
     drops to 20,587 (FULL SCREEN) — 547 of travel and 8 to the left.
     It travels with `--full`, which corpus.js writes on `.fl-inter360` at
     this width too, with its easing. */
  .fl-inter360-cap {
    position: absolute;
    left: calc((28 - 8 * var(--full, 0)) * var(--k));
    top: calc((40 + 547 * var(--full, 0)) * var(--k));
    z-index: 2;
  }

  .fl-inter360-cap a {
    position: relative;
    display: block;
  }

  .fl-inter360-cap .fl-display {
    position: static;
    display: block;
    font-size: calc(24 * var(--k));
    color: #fff;
  }

  .fl-inter360-cap .fl-text {
    position: static;
    display: block;
    height: auto;
    margin-top: calc(6 * var(--k));
    font-size: calc(15 * var(--k));
    color: #fff;
  }

  .fl-inter360-cap a::after {
    display: none;
  }

  /* The badge accompanies the caption: 325,52 on the in-flow board (20
     from the right edge) to 317,599 on the FULL — the same travel of 547
     down and 8 left as the title. */
  .fl-inter360::after {
    content: '';
    position: absolute;
    right: calc((20 + 8 * var(--full, 0)) * var(--k));
    top: calc((52 + 547 * var(--full, 0)) * var(--k));
    z-index: 2;
    width: calc(30 * var(--k));
    height: calc(30 * var(--k));
    background: #fff;
    -webkit-mask: url('/assets/images/icons/ic_360.svg') center / contain no-repeat;
    mask: url('/assets/images/icons/ic_360.svg') center / contain no-repeat;
  }

  /* ── vision — rides the Serena screen (MBL FULL board) ───────────
     Right-aligned over the photo's top-right, Lato 20, white. The box is
     exactly the hole it climbs into, so the flow after it lands back at
     the section's end whatever the text measures. */
  /* 72 and not the board's 40: the FULL board draws no bar on this screen
     and the page always carries it — at 40 the sentence died under the
     logo. It drops by what the bar measures; deviation from the file,
     stated. */
  /* 772 = 812 - 40: the body starts at the FULL SCREEN board's 40 (node at
     128,40 — the 128 falls out on its own from the 227 width + the 20 of
     padding). `width: 100%` is not decoration: without it, the container
     inherits the desktop's 227, the body is born flush LEFT and the
     `margin-left: auto` has no room to distribute — the paragraph came out
     on the opposite side to the board's (Denis, 02-sep: "el texto que esta
     arriba esta alineado distinto"). */
  .fl-vision {
    position: relative;
    z-index: 2;
    width: 100%;
    height: calc(772 * var(--k));
    margin-top: calc(-772 * var(--k));
    padding: 0 calc(20 * var(--k));
    pointer-events: none;
  }

  /* IT APPEARS while the caption drops (Denis, 02-sep: "cuando va bajando
     va apareciendo este texto"): the in-flow board does not draw it and
     the FULL SCREEN does — the opacity is the travel's own progress.
     `--serena` and not `--full` because the animating block is a previous
     sibling, not an ancestor; corpus.js publishes it on the root.
     TWO CLASSES, not one: the `.fl-corpus :is(h1, h2, p, ...)` reset
     weighs (0,1,1) because of the `:is()` and its `margin: 0` crushed the
     `margin-left: auto` — the paragraph came out flush LEFT with its 227
     box (Denis, 02-sep: "alineado distinto"). The auto is what plants the
     body at the board's 128 without writing the number. */
  .fl-vision .fl-vision-body {
    position: static;
    width: calc(227 * var(--k));
    margin-left: auto;
    font-size: calc(20 * var(--k));
    text-align: right;
    color: #fff;
    white-space: normal;
    opacity: var(--serena, 0);
  }

  /* ── MBL_faq → .fl-faq ───────────────────────────────────────────
     Questions Lato 14 italic on a 27 rhythm, the answer Lato 14 Light. */
  .fl-faq {
    padding: calc(29 * var(--k)) calc(20 * var(--k)) calc(40 * var(--k));
  }

  /* The heading desktop hides IS DRAWN here: the Serena board (97638392)
     puts "FAQ _" in the white band under the photo — black Lato 20 Light
     italic at 21,763, with its rule at the foot 6 from the text
     (65,783 · 21.4), the same mark the promoted format carries. The h2
     rises 49 into the previous screen's air (763 = 812 - 49; -78 = -49 -
     the padding's 29) and its bottom margin returns the questions to the
     usual 29 — nothing else moves. */
  .fl-faq h2 {
    position: static;
    width: auto;
    height: auto;
    margin: calc(-78 * var(--k)) 0 calc(54 * var(--k)) calc(1 * var(--k));
    overflow: visible;
    clip-path: none;
    font-size: calc(20 * var(--k));
    font-weight: 300;
    font-style: italic;
    letter-spacing: calc(0.8 * var(--k));
    color: #000;
  }

  .fl-faq h2::after {
    content: '';
    display: inline-block;
    width: calc(21.4 * var(--k));
    margin-left: calc(6 * var(--k));
    border-bottom: 1px solid #000;
  }

  /* The 27 rhythm (margin included) is set by the touch block. */
  .fl-q {
    display: block;
    font-size: calc(14 * var(--k));
    font-style: italic;
  }

  /* On the phone the list is normal flow and each answer lives in the DOM
     right after its question, so it opens BELOW the tapped one — the
     formatos grammar (Denis, 02-sep) — without repositioning anything. The
     margin is the open one's air; closed it must be 0 or each
     question/answer pair would leave a 40 ghost gap in the 27 rhythm. */
  .fl-faq-a {
    width: auto;
    margin: calc(20 * var(--k)) 0;
    font-size: calc(14 * var(--k));
    font-weight: 300;
    transition:
      height 0.3s cubic-bezier(0.22, 0.61, 0.36, 1),
      opacity 0.3s ease,
      margin 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  .fl-faq-a:not(.is-open) {
    margin: 0;
  }

  /* ── MBL_contact → .fl-contact ───────────────────────────────────
     The last screen: photo under a 20% black veil, the WhatsApp button
     first, then the mail form at x 61, then the footer column in #FFFEFA.
     The board's field type is 10px; inputs hold 16px real — an iPhone
     zooms into anything smaller and the page jumps. Deviation, named. */
  .fl-contact {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
    min-height: calc(812 * var(--k));
    padding: calc(234 * var(--k)) calc(20 * var(--k)) calc(24 * var(--k));
    overflow: hidden;
  }

  /* The board's photo node is NOT the screen: 628x812 at -109,340, the
     same 3840x3264 fill centred inside it, and the 375 board cropping it
     from 109 to 484. A centred `cover` of the screen itself lands ~27 source
     columns to the right of that (Denis, 3-sep-2026: "el encuadre de la
     foto de penpot no es el mismo [...], igualala"), so the box is the
     node's, and the section clips it like the board does.
     The section is taller than the board's 812 — the 16px inputs and their
     touch areas, the deviation named above — so the node is scaled WITH the
     section: `aspect-ratio` keeps its 628:812 at whatever height.
     Since 19:26 the board's -109 is gone: the left edge is what is always
     seen (Denis, 3-sep-2026: "el izquierdo es el que se debe de ver
     siempre"), so the node's box starts at the screen's left edge and the
     section clips its right. Departure from the board, named;
     `tools/mobile-penpot.js` asserts it. */
  .fl-contact-img {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: auto;
    aspect-ratio: 628 / 812;
    object-fit: cover;
    object-position: center;
  }

  /* The board's veil: black at 20% over the whole photo. */
  .fl-contact::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.2);
  }

  .fl-contact > :not(.fl-contact-img) {
    position: relative;
  }

  /* The desktop sentence does not exist on the mobile board: here the pill
     is its own node (below). */
  .fl-contact-wa {
    display: none;
  }

  /* Board 20,574 335x59, radius 30, white 6% fill, 1px white stroke: ONE
     Lato 16 line at 41 from the edge with the send glyph on the right (the
     board's `Vector`, 38 from the right edge), and the whole pill is the
     link. The drawn label ("Cuentanos más por Whatssap") is the file's
     typo; the real copy goes in. */
  .fl-contact-wa-mobile {
    order: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    align-self: stretch;
    padding: 0 calc(38 * var(--k)) 0 calc(41 * var(--k));
    height: calc(59 * var(--k));
    border: 1px solid #fff;
    border-radius: calc(30 * var(--k));
    background: rgba(255, 255, 255, 0.06);
    font-size: calc(16 * var(--k));
    color: #fff;
    text-decoration: none;
    white-space: normal;
  }

  .fl-contact-wa-mobile::after {
    content: '';
    width: calc(16 * var(--k));
    height: calc(16 * var(--k));
    background: url('/assets/images/icons/ic_enviar.svg') center / contain no-repeat;
  }

  .fl-contact-form {
    order: 2;
    width: calc(249 * var(--k));
    margin: calc(52 * var(--k)) 0 0 calc(41 * var(--k));
  }

  /* The desktop title is not drawn on the mobile board; it stays as the
     section's accessible name (the `.fl-a11y` clip). Its place is taken by
     the board's label, "o si lo prefieres envianos un email" — Lato 16
     Light, with `nombre` at the 17 the file draws (intro 0..19, label at
     36). */
  .fl-corpus .fl-contact-h {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .fl-corpus .fl-contact-intro {
    display: block;
    position: static;
    margin: 0 0 calc(17 * var(--k));
    font-size: calc(16 * var(--k));
    font-weight: 300;
    color: #fff;
    white-space: normal;
  }

  .fl-contact-rule {
    display: none;
  }

  .fl-contact-field {
    display: block;
    width: calc(206 * var(--k));
    padding: 0 0 calc(6 * var(--k));
    border: 0;
    border-bottom: 0.5px solid #fff;
    margin-bottom: calc(22 * var(--k));
    font-size: max(16px, calc(10 * var(--k)));
    color: #fff;
  }
  /* No `background: none` here, on purpose. The canvas rule already clears it
     for all three fields, and repeating it at this width was the shorthand
     that RESET `background-image` — same specificity as `.fl-contact-msg` and
     further down the file, so the message field lost its notebook rules and
     its `background-attachment: local` on the phone and nowhere else (Denis,
     3-sep-2026: "el contact, hola, no tiene las underlines"). */

  .fl-contact-field::placeholder {
    font-style: italic;
    color: rgba(255, 255, 255, 0.85);
  }

  .fl-contact-msg {
    /* The notebook continues: rules of 32 in the field's 96, white here —
       the canvas's are black at 50% and over this photo they do not show.
       The last one coincides with the box's foot, so the border is
       redundant. */
    --fl-notebook: calc(32 * var(--k));
    --fl-rule-ink: rgba(255, 255, 255, 0.7);
    height: calc(96 * var(--k));
    border-bottom: 0;
  }

  .fl-corpus .fl-contact-count {
    width: calc(206 * var(--k));
    margin: calc(-16 * var(--k)) 0 calc(12 * var(--k));
    text-align: right;
    font-size: calc(11 * var(--k));
    color: #fff;
  }

  .fl-contact-send {
    padding: calc(4 * var(--k)) calc(14 * var(--k));
    border: 0.5px solid #fff;
    border-radius: calc(30 * var(--k));
    background: rgba(255, 255, 255, 0.06);
    font-size: calc(12 * var(--k));
    font-style: italic;
    font-weight: 300;
    color: #fff;
  }

  /* The footer over the photo, the 61 column and in the board's #FFFEFA —
     Lato 10 (italic for the contact data, Light for the social links) with
     the 35 of air between blocks the file draws (data 714..738, links at
     773). The 96 down to the data compensates for the real form being
     taller than the board's (the inputs keep a real 16px against the drawn
     10). */
  .fl-corpus .fl-contact-details {
    order: 3;
    margin: calc(96 * var(--k)) 0 0 calc(41 * var(--k));
    font-size: calc(10 * var(--k));
    font-style: italic;
    color: #fffefa;
  }

  .fl-corpus .fl-contact-social {
    order: 4;
    margin: calc(35 * var(--k)) 0 0 calc(41 * var(--k));
    font-size: calc(10 * var(--k));
    font-weight: 300;
    color: #fffefa;
  }

  .fl-contact-details a,
  .fl-contact-social a {
    color: #fffefa;
  }

  /* NOT IN THE DESIGN: the language switcher and the credit are not drawn
     on any MBL board, and a four-language site keeps its switcher. Same
     column, same ink. */
  .fl-corpus .fl-langs {
    order: 5;
    margin: calc(32 * var(--k)) 0 0 calc(41 * var(--k));
    color: #fffefa;
  }

  .fl-langs .fl-lang {
    color: #fffefa;
    font-size: calc(12 * var(--k));
  }

  .fl-corpus .fl-credit {
    order: 6;
    margin: calc(12 * var(--k)) 0 0 calc(41 * var(--k));
    font-size: calc(10 * var(--k));
    color: #fffefa;
  }

  /* `width: auto` because the base rule's 96 is the DESKTOP alignment column,
     measured for its 16: at 10 it left a ~50px hole between `powered by` and
     the name, and the 6 of air this block already declares was never reaching
     anything (Denis, 3-sep-2026: "el powered by mylky halo mas pequeño apra q
     cuadre con el resto de fuentes"). */
  .fl-corpus .fl-credit-by {
    width: auto;
    margin-right: calc(6 * var(--k));
  }

  /* ── the viewer on mobile (MBL_visor) ────────────────────────────
     The board draws the 335x254 card with its caption centred — Gloock 20
     over Lato 13 Light. The stacked mechanics (one view, arrows at the
     edges, fixed close) come from the previous block, audited on 01-sep. */
  /* The viewer lives outside `.fl-corpus` but inherits the phone :root's
     `--k` all the same; it only collapses its own factors. */
  .fl-viewer {
    --uv: var(--k);
  }

  .fl-sheet,
  .fl-selector {
    --u: var(--k);
  }

  .fl-selector {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(24 * var(--k));
  }

  .fl-sel-half {
    position: relative;
    top: auto;
    left: auto;
    width: calc(220 * var(--k));
    height: calc(266 * var(--k));
  }

  .fl-sel-label {
    font-size: calc(32 * var(--k));
  }

  .fl-sel-x {
    position: fixed;
    top: calc(16 * var(--k));
    right: calc(16 * var(--k));
  }

  /* The ficha is the board's COLUMN: all the project's views stacked in
     335x254 cards (81/395/708 → pitch 313.5, gap 59.5) traversed by
     scroll; the project's caption stays FIXED at the screen's foot
     (board: 730..770 of 812), and at the bottom goes `siguiente proyecto`
     with the next one's cover — the between-projects navigation, in the
     style of Denis's reference (brickvisual). The views are stacked by
     viewer.js (.fl-extra-view); the canvas's slide does not run here:
     seguir and arrows are not drawn. */
  .fl-sheet {
    display: block;
    padding: calc(81 * var(--k)) calc(20 * var(--k)) calc(96 * var(--k));
    overflow-y: auto;
    /* The column slides sideways between projects (below): a transform
       past the edge must not open a horizontal scroll. */
    overflow-x: hidden;
  }

  .fl-sheet-fig {
    position: static;
    width: auto;
  }

  .fl-sheet-views {
    position: static;
    width: 100%;
    aspect-ratio: auto;
    display: flex;
    flex-direction: column;
    gap: calc(59 * var(--k));
    /* The project change slides the whole column, the canvas's 0.6s
       (viewer.js `changeProject`, Denis 3-sep-2026). */
    transition: transform 0.6s ease;
  }

  /* The OUTGOING column of a project change (viewer.js `changeProject`,
     19:26: "debe de ser p1-p2", no blank between): a throwaway clone pinned
     where the real one was seen, clipped to the screen so it adds no scroll,
     and out of the finger's way. */
  .fl-sheet-views.is-ghost {
    position: absolute;
    left: calc(20 * var(--k));
    right: calc(20 * var(--k));
    width: auto;
    overflow: hidden;
    pointer-events: none;
  }

  /* Each view at ITS proportion and WHOLE — the vertical one comes out
     tall and the landscape one complete (Denis, 02-sep: "si son
     alargadas, alargadas"). The canvas box (--vw/--vh) is each photo's
     real proportion; the board draws uniform 254 cards, but the work
     rules. */
  .fl-sheet-img {
    position: static;
    width: 100%;
    height: auto;
    aspect-ratio: var(--vw) / var(--vh);
    object-fit: cover;
    transform: none !important;
    transition: none !important;
  }

  /* BLACK ink, not the board's white: the site's viewer is white and the
     fixed caption crosses gaps and light backgrounds where the white
     vanished (Denis, 02-sep: "es blanca la fuente y blanco el fondo"). */
  .fl-sheet-caption {
    position: fixed;
    /* 56 per side: the project arrows' gap (brickvisual). */
    left: calc(56 * var(--k));
    right: calc(56 * var(--k));
    /* Explicit `top: auto`: the canvas hangs the caption from a top that
       here beat the bottom and threw it below the screen (measured at
       768). */
    top: auto;
    bottom: calc(42 * var(--k));
    width: auto;
    margin: 0;
    text-align: center;
    color: #000;
    pointer-events: none;
    z-index: 2;
  }

  .fl-sheet-caption .fl-display {
    position: static;
    display: block;
    font-size: calc(20 * var(--k));
    color: #000;
  }

  .fl-sheet-caption .fl-text {
    position: static;
    display: block;
    font-size: calc(13 * var(--k));
    font-weight: 300;
    color: #000;
  }

  .fl-sheet-continue {
    display: none;
  }

  /* The arrows navigate PROJECTS, flanking the fixed caption — the
     pattern of Denis's reference (brickvisual, 02-sep): ← name → on the
     bottom edge, and scroll for the project's photos. Like the caption —
     black, on a white viewer — with the canvas glyph as mask. */
  /* The glyph goes as background, NOT as mask: masking the button also
     clips its ::before's hit-test in Blink and the enlarged touch zone
     received no taps (measured with touchscreen.tap, 02-sep). The
     canvas's SVG is already black, which is this viewer's ink. */
  .fl-sheet-arrow {
    position: fixed;
    top: auto;
    bottom: calc(60 * var(--k));
    width: calc(18 * var(--k));
    height: calc(13 * var(--k));
    background: url('/assets/images/2026/arrow-viewer.svg') center / contain no-repeat;
    z-index: 3;
  }

  .fl-sheet-next {
    right: calc(20 * var(--k));
  }

  .fl-sheet-prev {
    left: calc(20 * var(--k));
  }

  /* The finger margin around the glyph, not the canvas's column.
     Genuinely roomy: ~66x73px of zone per arrow (Denis, 02-sep:
     "cuesta mucho acertar"). The caption in between steals no taps — it
     carries pointer-events: none. */
  .fl-sheet-arrow::before {
    right: calc(-24 * var(--k));
    left: calc(-24 * var(--k));
    top: calc(-30 * var(--k));
    bottom: calc(-30 * var(--k));
    width: auto;
    height: auto;
  }


  .fl-sheet-x {
    position: fixed;
    top: calc(16 * var(--k));
    right: calc(16 * var(--k));
  }

  /* ── touch area (WCAG 2.5.8: 24px floor) ─────────────────────────
     The boards draw smaller chrome — the send pill measures 17 — so the
     area grows via padding and the negative margin returns each box to its
     place: the drawing does not move, the finger has somewhere to land. */
  .fl-q,
  .fl-more {
    padding-top: 6px;
    padding-bottom: 6px;
    margin-top: -6px;
    margin-bottom: -6px;
  }

  /* The FAQ carries its touch compensation inside its own flow margin:
     the group's -6 trod on the board's 184 (a later rule). */
  .fl-faq-link {
    padding-top: 6px;
    padding-bottom: 6px;
    margin-top: calc(184 * var(--k) - 6px);
    margin-bottom: -6px;
  }

  .fl-q {
    /* The board's 27 rhythm came from its margin of 10; with the touch
       padding inside, the visible margin trims down to the same. */
    margin-bottom: calc(10 * var(--k) - 12px);
  }

  /* The board's strip measures 72 with the text (20) centred: 26 per side. */
  .fl-more {
    margin: calc(26 * var(--k) - 6px) auto;
  }

  /* 7 and not 6 since the footer carries the board's Lato 10 (02-sep): at
     320 the line measures 10.2 and with 6+6 the area stayed at 22, below
     the 24 floor (WCAG 2.5.8). */
  .fl-contact-details a,
  .fl-contact-social a {
    display: inline-block;
    padding-top: 7px;
    padding-bottom: 7px;
    margin-top: -7px;
    margin-bottom: -7px;
  }

  .fl-langs .fl-lang {
    display: inline-block;
    padding-top: 6px;
    padding-bottom: 6px;
    margin-top: -6px;
    margin-bottom: -6px;
  }

  /* The hero CTA is ruled here in full: the board anchors it at 151 and
     the touch area is these 11 per side (desk2026 only leaves left and
     typography). */
  .fl-hero-cta {
    display: inline-block;
    height: auto;
    padding-top: 11px;
    padding-bottom: 11px;
    bottom: calc(151 * var(--k) - 11px);
    margin: 0;
  }

  .fl-hero-cta span {
    position: static;
  }

  .fl-talk-solution {
    top: calc(698 * var(--k) - 11px);
    padding-top: 11px;
    padding-bottom: 11px;
  }

  .fl-contact-send {
    min-height: 24px;
  }

  /* (The WhatsApp link's touch area is no longer needed here: at this
     width the desktop sentence is not drawn and the whole pill — 59
     tall — is the link.) */
  .fl-langs .fl-lang {
    padding-left: 8px;
    padding-right: 8px;
    margin-left: -8px;
    margin-right: -8px;
  }

  .fl-formats-item button {
    padding-top: 4px;
    padding-bottom: 4px;
    margin-top: -4px;
    margin-bottom: -4px;
  }
}


/* The full-screen states still apply; they just stop travelling. The viewer
   lives outside `.fl-corpus`, so its two travellers are named here —
   `viewer.js` already swaps vistas without sliding under this same query. */
@media (prefers-reduced-motion: reduce) {
  .fl-corpus *,
  .fl-menu a,
  .fl-more::after,
  .fl-sheet-img,
  .fl-sheet-caption {
    transition: none !important;
  }
}
