/*
 * COMFORTEX — the landing page's own stylesheet.
 *
 * ONLY THE LANDING PAGE LOADS THIS. It is not part of app.css, because none of
 * what is below exists anywhere inside the signed-in tool, and app.css is
 * fetched on every screen an agent opens all day. Two files, each cached on its
 * own, beats one file where half of it is dead weight on every page.
 *
 * AND IT IS NOT INLINE IN THE BLADE VIEW EITHER, which is where the page's CSS
 * used to live. At ~290 lines that was a reasonable trade; with four components
 * added it is 50KB that would be re-sent on every visit and could never be
 * cached. The <style> block is gone and this file replaces it.
 *
 * ORGANISATION
 *   0  the page: bar, hero copy, bands, ledger, access, foot   (was inline)
 *   1  LineWaves  — the hero's WebGL shutter field
 *   2  CardSwap   — the three-card deck in "Cum funcționează"
 *   4  LogoLoop   — the catalogue nameplate rail
 *
 * Sections 1-4 are ports of React Bits components. There is no React and no
 * build step here — see the header of assets/landing.js for why, and what that
 * cost. Every one of them has a still, complete resting state under
 * prefers-reduced-motion and with scripting off; none of them is load-bearing
 * for any content on the page.
 */

/* ==========================================================================
   0. THE PAGE
   ========================================================================== */
/* Page-specific. The design system lives in public/assets/app.css. */

/*
 * overflow-x: clip, and it is load-bearing rather than defensive.
 *
 * The nameplate rail is a full-bleed strip whose track is width:max-content —
 * far wider than the viewport by design, because that is what there is to
 * scroll. Without this the document grows to the track's width and the whole
 * page slides sideways on a phone.
 *
 * `clip` and not `hidden`: `hidden` on <body> makes the element a scroll
 * container, which silently kills `position: sticky` anywhere inside it and
 * makes anchor jumps scroll the body instead of the page. `clip` cuts the
 * overflow without any of that. Only the x axis — clipping y would cut the
 * badge's shadow and, worse, trap the page's own scrolling.
 */
.lp { --lp-w: 1160px; background: #fff; overflow-x: clip; }
.lp-in { max-width: var(--lp-w); margin: 0 auto; padding-inline: var(--s-5); }
@media (max-width: 720px) { .lp-in { padding-inline: var(--s-4); } }

/* ======================================================================= bar */

.lp-bar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 3;
  height: 76px; display: flex; align-items: center;
}
.lp-bar .lp-in { display: flex; align-items: center; gap: var(--s-4); width: 100%; }
.lp-bar img { height: 27px; display: block; }
.lp-bar__spacer { flex: 1; }
.lp-bar .topbar__out { border-color: rgba(255,255,255,.28); color: rgba(255,255,255,.88); }
.lp-bar .topbar__out:hover { background: rgba(255,255,255,.12); border-color: #fff; color: #fff; }
/* Two buttons plus the wordmark do not fit at 380px. The sign-up link folds
   away: the hero immediately below carries it as its primary button, while
   Autentificare has no second home above the fold. */
@media (max-width: 520px) { .lp-bar__join { display: none; } }

/* ================================================================ hero type */
/*
 * The words in the hero. They used to belong to `.lp-stage`, which was a dark
 * gradient with a WebGL field of light over it and the mark fanning open beside
 * the copy. All of that is gone: the hero is the shutter now (section 5), and a
 * page gets one big move, not two competing ones.
 *
 * What survives is exactly the type, because the type was never the problem —
 * the eyebrow's 22×2px red tick, the display face at a size that scales with
 * the viewport, one phrase of the headline in red, and the honest line under
 * the buttons. All of it is set on the dark room the shutter stands in, so the
 * colours are unchanged from the day they were measured.
 */

.lp-eyebrow {
  display: inline-flex; align-items: center; gap: 9px;
  font: 500 11.5px/1 var(--font); letter-spacing: .13em; text-transform: uppercase;
  color: rgba(255,255,255,.62); margin-bottom: var(--s-4);
}
.lp-eyebrow::before {
  content: ""; width: 22px; height: 2px; border-radius: 2px; background: var(--brand-red);
}

.lp-title {
  font-family: var(--font-display); font-weight: 700; color: #fff;
  font-size: clamp(36px, 5.4vw, 68px); line-height: .98; letter-spacing: -.035em;
  margin: 0 0 var(--s-4); text-wrap: balance;
}
/* One phrase in red, never a whole line: a full red heading reads as a warning,
   and §1.1's colour rule is the one rule this app cannot break. On --ink at
   this size #FF4148 clears AA comfortably where #ED1C24 would not. */
.lp-title em { font-style: normal; color: #FF4148; }

.lp-lede {
  color: rgba(255,255,255,.78); margin: 0;
  font-size: clamp(15px, 1.5vw, 17.5px); line-height: 1.58;
}

.lp-cta { display: flex; gap: var(--s-3); margin-top: var(--s-5); flex-wrap: wrap; }
.lp-cta .btn { min-height: 50px; padding-inline: 26px; font-size: 15.5px; }
.lp-cta .btn--secondary {
  background: transparent; color: #fff; border-color: rgba(255,255,255,.36);
}
.lp-cta .btn--secondary:not([disabled]):hover {
  background: rgba(255,255,255,.11); border-color: #fff; box-shadow: none;
}

/*
 * The limit, said in the hero rather than three screens down.
 *
 * The page has always admitted that one product family of ten can actually be
 * priced — but it admitted it in the "Ce acoperim" band, below a hero making
 * three confident claims. Somebody who reads the hero and presses "Cere un
 * cont" never sees it, and finds out after they have an account.
 *
 * It carries the same 22px red rail as .lp-eyebrow, so the copy column is
 * bracketed top and bottom by the same mark, and it is deliberately quiet:
 * --on-dark-mute at --fs-xs is 6.78:1 on --ink, legible without competing with
 * the buttons above it. Nothing is hidden and nothing is shouted.
 */
.lp-honest {
  display: flex; align-items: center; gap: 10px;
  margin: var(--s-4) 0 0;
  color: var(--on-dark-mute); font-size: var(--fs-xs); line-height: 1.5;
}
.lp-honest::before {
  content: ""; width: 22px; height: 2px; border-radius: 2px; flex: none;
  background: var(--brand-red);
}
@media (max-width: 520px) { .lp-honest::before { display: none; } }

/* The copy assembles once, on arrival, and then holds still — everything else
   in the hero is driven by the reader's own scrolling. */
@media (prefers-reduced-motion: no-preference) {
  .sh-copy .lp-eyebrow { animation: rise 700ms var(--ease-out)  60ms both; }
  .sh-copy .lp-title   { animation: rise 800ms var(--ease-out) 140ms both; }
  .sh-copy .lp-lede    { animation: rise 800ms var(--ease-out) 240ms both; }
  .sh-copy .lp-cta     { animation: rise 800ms var(--ease-out) 340ms both; }
  .sh-copy .lp-honest  { animation: rise 800ms var(--ease-out) 440ms both; }
  /* `backwards` AND NOT `both`, and it is the difference between a cue that
     goes away and one that does not. A forwards fill keeps the animation's own
     opacity applied after it ends, and an animation's value beats an inline
     style — so the script's fade would be written and ignored, and the page
     would still be saying "derulează" to somebody three screens into scrolling.
     With `backwards` the fill applies only before the animation starts and the
     inline style takes over the moment it is done. */
  .sh-copy .sh-cue     { animation: rise 800ms var(--ease-out) 620ms backwards; }
}
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* ===================================================================== bands */

.lp-sec { padding-block: clamp(56px, 8vw, 96px); }
.lp-sec--tint { background: var(--grey-50); border-block: 1px solid var(--edge); }

.lp-h2 { font-size: clamp(25px, 3.4vw, 36px); letter-spacing: -.026em; margin-bottom: var(--s-3); }
.lp-sub { color: var(--grey-600); margin: 0 0 var(--s-6); max-width: 56ch; font-size: var(--fs-body); }

/* --- what we cover: TYPE, not drawings ------------------------------------
   The eight categories used to be eight 118px schematics. They are names now.
   A name says "we make pergolas" as well as a line drawing does, and it does
   not suggest the drawing is the product you would be buying. */

.lp-cats { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 0 var(--s-6); }
.lp-cat {
  display: flex; align-items: baseline; gap: var(--s-3);
  padding: var(--s-4) 0; border-bottom: 1px solid var(--edge);
}
.lp-cat__name { font-family: var(--font-display); font-weight: 600; font-size: 17px; letter-spacing: -.012em; }
.lp-cat__state { margin-left: auto; flex: none; font-size: var(--fs-xs); color: var(--grey-500); white-space: nowrap; }
.lp-cat--soon .lp-cat__name { color: var(--grey-600); font-weight: 500; }

/*
 * THE ONE PRODUCT THAT CAN ACTUALLY BE PRICED, PROMOTED OUT OF THE LIST.
 *
 * It used to be the first of ten identical rows, distinguished by its state
 * word being red. Two things were wrong with that. The page's single
 * unambiguously positive statement was the quietest thing in the section — and
 * `.lp-cat--live .lp-cat__state` was 12px of --brand-fill TEXT, which §1.1
 * forbids outright ("brand red is NEVER small text") and which measures 4.52:1,
 * legible but no more, at a size where it needs to be better than that.
 *
 * As a solid fill with a white label the colour becomes a FILL, which the rule
 * sanctions, the contrast question moves to white-on-red where it is 4.52:1 for
 * a 12px semibold label, and the loudest element in the section is now the one
 * true thing the page has to say. The other nine rows are untouched: the
 * contrast between one loud row and nine quiet ones IS the argument.
 */
.lp-lead {
  display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap;
  padding: var(--s-4) 0 var(--s-4) var(--s-4);
  margin-bottom: var(--s-4);
  border-left: 3px solid var(--brand-red);
}
.lp-lead__name {
  font-family: var(--font-display); font-weight: 600; font-size: 21px;
  letter-spacing: -.016em; color: var(--ink);
}
.lp-lead__state {
  margin-left: auto; flex: none;
  background: var(--brand-fill); color: #fff;
  font: 600 var(--fs-xs)/1 var(--font);
  padding: 7px 12px; border-radius: 999px; white-space: nowrap;
}
@media (max-width: 520px) {
  .lp-lead { padding-left: var(--s-3); gap: var(--s-3); }
  .lp-lead__name { font-size: 19px; }
  .lp-lead__state { margin-left: 0; }
}

/* --- access -------------------------------------------------------------- */

.lp-access { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 340px); gap: var(--s-7); align-items: start; }
@media (max-width: 860px) { .lp-access { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); } }
.lp-access p { color: var(--grey-600); }
.lp-access .card { box-shadow: var(--shadow-3); border-radius: var(--r-lg); }

.lp-foot { border-top: 1px solid var(--edge); background: #fff; padding-block: var(--s-5); }
.lp-foot .lp-in { display: flex; gap: var(--s-4); flex-wrap: wrap; align-items: center;
                  font-size: var(--fs-xs); color: var(--grey-600); }
.lp-foot img { height: 26px; }
/* ==========================================================================
   2. CARDSWAP — the deck
   ========================================================================== */
/* =============================================================== CUM FUNCȚIONEAZĂ
 *
 * The three-card deck, ported from React Bits' <CardSwap />.
 *
 * WHAT THE ORIGINAL WAS AND WHY ALMOST NONE OF IT SURVIVED
 * It was a gsap timeline writing x/y/z/zIndex onto absolutely-positioned divs
 * with an elastic.out(0.6,0.9) ease, five cards deep, parked in the corner of a
 * hero and scaled to 55% on a phone. Three things about that are wrong for this
 * page and all three are fixed here rather than carried over:
 *
 *   1  gsap is not available and is not coming. But the swap does not need a
 *      tween engine: every value the timeline animates is a transform, and a
 *      transform between two known states is what a CSS transition is FOR. The
 *      deck therefore runs entirely on the compositor at zero JavaScript per
 *      frame — no rAF, no easing solver, no interpolation in script. It never
 *      joins the page's shared tick loop, which is the whole point on a phone.
 *
 *   2  elastic.out(0.6,0.9) oscillates four or five times before it settles.
 *      On a page whose argument is millimetre precision that reads as a toy.
 *      The character is kept — a drop, then a snap that overshoots — but as ONE
 *      ~8% overshoot: cubic-bezier(.2, 1.32, .36, 1). It still lands with a
 *      bounce; it does not wobble.
 *
 *   3  `transform: scale(.55) translate(25%, 25%)` at 480px renders a 20px
 *      heading at 11px and deliberately hangs the stack off the edge of the
 *      viewport. Nothing here is scaled. Below 720px the deck changes FORM: it
 *      becomes a native scroll-snap track, which cannot fight the page scroll,
 *      cannot overflow, and works with scripting off.
 *
 * THE DECK DOES INFORMATION WORK. It sits beside the three steps and the three
 * step rows are its control — each card is the illustration of the paragraph
 * you are reading. That is also what closes the accessibility hole in the
 * original: an auto-rotating stack with no way to steer it.
 *
 * ALL FOUR STATES ARE COMPOSED, none is a blank box:
 *   full          the fan, auto-advancing every 5s, steerable
 *   reduced       the same fan at rest, still steerable, instantly (§10 forces
 *                 every transition to 1ms, and !important beats the inline
 *                 durations the script writes, so this needs no JS branch)
 *   no-JS         three full-width cards in document flow under three readable
 *                 paragraphs — the `html.js` gate below is what guarantees it
 *   <=720px       a scroll-snap track, with or without script
 */

/* ============================================================ the section box */

/*
 * overflow:clip both ways, and the vertical axis is not an accident.
 *
 * The dropping card travels 410px straight down out of a 420px well that has
 * only ~160px of slack beneath it before the next band starts. Without this it
 * sails across the section seam twice a minute at 40% opacity. Clipped, it
 * disappears exactly at the line where the tinted band begins, which reads as
 * the card sliding underneath — occlusion, not a cut.
 *
 * `hidden` first as the fallback for Safari <16: it clips identically, it is
 * only worse in that it could in principle be scrolled programmatically.
 */
.lp-how { overflow: hidden; overflow: clip; }

.lp-how__grid {
  display: grid;
  grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr);
  gap: var(--s-7);
  align-items: center;
}
@media (max-width: 720px) {
  .lp-how__grid { grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
  /* The stack becomes the illustration FOR the text rather than beside it, so
     it goes first: h2, lede, deck, then the three rows it controls. Order, not
     a second markup order: the steps stay first in the document, which is the
     order they are read in with scripting off and by a screen reader. */
  .lp-deck { order: -1; }
}

/* ================================================================= the steps */
/*
 * These replace .lp-steps / .lp-step / .lp-step__n in landing.blade.php's own
 * <style> block. Delete the old rules; the class names are deliberately the
 * same so the rest of the page reads unchanged.
 *
 * The three fading top rules (ink -> grey-400 -> grey-200) are gone. A left
 * rail carries both jobs now — the sequence AND which step is showing — because
 * two separate marks for two states on the same row is one mark too many.
 */

.lp-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-5); }

.lp-step {
  padding-left: var(--s-5);
  border-left: 2px solid var(--grey-200);
  transition: border-color var(--t-base) var(--ease-out);
}
.lp-step.is-active { border-left-color: var(--brand-red); }

/*
 * THE §1.1 FIX, and it is a correction not a flourish. .lp-step__n was
 * `600 12px` in --brand-red: small red text, which the rule forbids outright,
 * and #ED1C24 on white is 4.38:1 — under the 4.5:1 floor for small text. At
 * 44px/700 it is large text, where 4.38:1 clears the 3:1 requirement, and it
 * becomes "a big number", which §1.1 names as a sanctioned use of the colour.
 * Same colour, legal at last.
 *
 * Longhands, not the `font` shorthand: the shorthand resets font-variant, which
 * would silently drop the tabular figures that keep 01/02/03 the same width.
 */
.lp-step__n {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 44px;
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: var(--brand-red);
  margin-bottom: var(--s-2);
}

.lp-step__h { margin: 0; }

/*
 * The control is the number + title, not the whole row.
 *
 * The row was specced as one big <button> wrapping the paragraph too. It is not
 * built that way for two reasons: a <button> may only contain phrasing content,
 * so <h3> and <p> would both be invalid inside it and the step titles would
 * stop being headings; and a button whose accessible name is a 40-word
 * paragraph is announced in full, every time, by every screen reader. The
 * paragraph stays a real <p> outside the control — selectable, copyable, and
 * still the first thing a reader meets.
 */
.lp-step__btn {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; cursor: pointer;
  border-radius: var(--r-ctrl);
}
.lp-step__t {
  display: block;
  font-family: var(--font-display); font-weight: 600; font-size: 18px;
  letter-spacing: -.014em; color: var(--grey-600);
  transition: color var(--t-base) var(--ease-out);
}
.lp-step.is-active .lp-step__t { color: var(--ink); }
.lp-step__btn:hover .lp-step__t { color: var(--ink); }
.lp-step__btn:focus-visible {
  outline: 2px solid var(--brand-red); outline-offset: 4px;
}

.lp-step__p {
  margin: var(--s-2) 0 0;
  color: var(--grey-600); font-size: var(--fs-sm); line-height: 1.62;
  max-width: 46ch;
}

/* With scripting off the button is inert. It must not advertise otherwise. */
html:not(.js) .lp-step__btn { cursor: default; }
html:not(.js) .lp-step__t   { color: var(--ink); }

/* =================================================================== the well */
/*
 * BASE = THE HONEST STATE: three cards in document flow, full width, readable.
 * Everything below either re-forms this into a fan (script + wide) or into a
 * scroll track (narrow). Nothing here depends on script to become visible.
 */

.lp-deck { position: relative; min-width: 0; }

.lp-deck__card {
  position: relative;
  width: 100%;
  margin-bottom: var(--s-4);
  padding: var(--s-5);
  border-radius: var(--r-card);
  background: #fff;
  border: 1px solid var(--edge);
  box-shadow: var(--shadow-2);
  overflow: hidden;   /* clips the ray watermark to the card's own radius */
}
.lp-deck__card:last-child { margin-bottom: 0; }

/* ------------------------------------------------------------------ the fan */
/*
 * Gated on BOTH `html.js` and a width, because the fan is the one layout that
 * cannot degrade: absolutely-positioned cards with no script to place them are
 * a pile in the corner.
 *
 * The slot geometry is makeSlot() from the original, verbatim in intent:
 * x = i * 46, y = -i * 52, z = -i * 70, zIndex = total - i. It lives in CSS
 * rather than in script so the resting fan is correct on the first paint AND
 * survives landing.js failing to load — the nth-child rules below are the whole
 * placement; the script only ever overrides --i and z-index inline.
 */
@media (min-width: 721px) {
  html.js .lp-deck {
    height: 420px;
    perspective: 1100px;

    --deck-x: 46px;
    --deck-y: 52px;
    --deck-z: 70px;
    --deck-skew: -4deg;
    --deck-drop: 410px;
    /* ONE overshoot of about 8%, in place of elastic.out(0.6,0.9)'s four. */
    --deck-snap: cubic-bezier(.2, 1.32, .36, 1);
  }

  html.js .lp-deck__card {
    --i: 0;     /* slot index, 0 = front. Written inline by the script. */
    --drop: 0;  /* 0 or 1; 1 is the dropping card, mid-swap */

    position: absolute; top: 50%; left: 50%;
    /*
     * The subtraction is the fix for a real clipping bug, not a nicety.
     *
     * At `min(420px, 100%)` the card fills its column and the fan then pushes
     * the back card a further (total - 1) * --deck-x to the right — 92px — so
     * between about 721px and 1100px of viewport the rearmost card ran past
     * .lp-in's 24px gutter and was guillotined by this section's own
     * overflow:clip. Reserving the fan's own horizontal travel inside the
     * width makes the deck self-contained at EVERY width: the back card's
     * right edge lands exactly on the column edge.
     *
     * 420px still wins wherever the column is wide enough, so nothing about
     * the full-width look changes. Narrower cards only make .lp-fig shorter
     * (its height is driven by its width), so the interior height arithmetic
     * gains slack rather than losing it.
     */
    width: min(420px, calc(100% - var(--deck-x) * 2));
    height: 330px;
    margin: 0;
    box-shadow: var(--shadow-4);
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    /* transform-style stays FLAT, against the original's preserve-3d: nothing
       inside a card is transformed in 3D, and preserve-3d would make the
       overflow:hidden above stop clipping the watermark. */
    transform-style: flat;
    will-change: transform;

    transform:
      translate3d(
        calc(-50% + var(--deck-x) * var(--i)),
        calc(-50% - var(--deck-y) * var(--i) + var(--deck-drop) * var(--drop)),
        calc(var(--deck-z) * var(--i) * -1)
      )
      skewY(var(--deck-skew));

    /* The two-value order here is load-bearing: the script writes
       transition-duration and transition-delay as two-value lists that assume
       transform first, opacity second. */
    transition: transform 700ms var(--deck-snap), opacity 260ms linear;
  }

  html.js .lp-deck__card:nth-child(1) { --i: 0; z-index: 3; }
  html.js .lp-deck__card:nth-child(2) { --i: 1; z-index: 2; }
  html.js .lp-deck__card:nth-child(3) { --i: 2; z-index: 1; }

  /* The drop. --ease-out, not the snap curve: a card falling out of a stack
     does not bounce on the way down. The fade is delayed 360ms so the eye reads
     a fall and not a dissolve. */
  html.js .lp-deck__card.is-dropping {
    --drop: 1;
    opacity: 0;
    transition: transform 600ms var(--ease-out), opacity 240ms linear 360ms;
  }

  /* The teleport to the back slot. Applied for exactly one forced reflow while
     the card is invisible and behind the other two, then removed — which is the
     only way to move an element without transitioning it. */
  html.js .lp-deck__card.is-cut { transition: none; opacity: 0; }

  /* Only once the script is actually driving it. A pointer cursor on a card
     that does nothing is a lie the no-JS state must not tell. */
  html.js .lp-deck.is-live .lp-deck__card { cursor: pointer; }
}

/* Between 721 and 900 the right column is narrow enough that the full offsets
   push the back card past the section's inline padding. Re-tuned, not scaled. */
@media (min-width: 721px) and (max-width: 900px) {
  html.js .lp-deck { height: 380px; --deck-x: 34px; --deck-y: 40px; --deck-z: 54px; }
  html.js .lp-deck__card { height: 300px; }
  html.js .lp-fig { max-width: 300px; }   /* 219px tall, inside the shorter card */
}

/* --------------------------------------------------------- the mobile track */
/*
 * NOT gated on html.js. Native scroll-snap is the whole mechanism: it works
 * with scripting off, it cannot scroll-jack, it needs no pointer maths and no
 * touch-action override, so a swipe that starts on a card can never trap the
 * page. The script only adds manners — auto-advance, and keeping the active
 * step row honest after a manual swipe.
 */
@media (max-width: 720px) {
  .lp-deck {
    display: flex;
    align-items: stretch;
    gap: var(--s-3);
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--s-4);
    /* Bleed to the viewport edges inside .lp-in's 16px gutter so the next card
       peeks — that peek IS the affordance, which is why the scrollbar below can
       be hidden. Safe only because the page is overflow-x:clip; a 100vw here
       would include the scrollbar gutter and open a sideways scroll at 360px. */
    margin-inline: calc(var(--s-4) * -1);
    padding-inline: var(--s-4);
    /* 12px / 24px, not 8px both ways. overflow-y is hidden here (it has to be:
       overflow-x:auto forces the other axis to a non-visible value) and that
       clips to the PADDING box — so the padding is the only room the card's
       --shadow-2 has. That shadow reaches 12px above the card and 24px below
       (0 6px 18px), and at 8px it was being cut off square on both edges,
       which reads as a seam across the track rather than a lifted card. */
    padding-block: var(--s-3) var(--s-5);
    scrollbar-width: none;
  }
  .lp-deck::-webkit-scrollbar { display: none; }

  .lp-deck__card {
    flex: 0 0 84%;   /* 275px at 360px wide, which the card interiors fit */
    margin: 0;
    scroll-snap-align: center;
    transform: none;
  }
}

/* ============================================================ card interiors */

.lp-deck__name {
  margin: 0 0 var(--s-3);
  font-family: var(--font); font-weight: 500; font-size: 11px;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--grey-500);
}

/* The mark's own geometry at 8%, bottom right — brand, and specifically NOT a
   product photograph. <use> against the symbol inlined once at the top of
   <body>; if that symbol is missing the <svg> renders nothing at all, which is
   the correct failure. */
.lp-deck__wm {
  position: absolute; right: -14px; bottom: -16px;
  width: 132px; height: auto; opacity: .08; pointer-events: none;
  fill: var(--ink);
}

/* ---- A: the configuration, the deck's one dark card ---------------------- */

.lp-deck__card--cfg {
  background: var(--ink);
  border-color: rgba(255, 255, 255, .14);
  color: var(--on-dark);
}
.lp-deck__card--cfg .lp-deck__name { color: var(--on-dark-mute); }
.lp-deck__card--cfg .lp-deck__wm { fill: #fff; }

.lp-cardhd { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
.lp-cardhd__t {
  margin: 0; min-width: 0;
  font-family: var(--font-display); font-weight: 600; font-size: 20px;
  letter-spacing: -.018em; color: #fff;
}
.lp-chip {
  margin-left: auto; flex: none;
  font-size: 11.5px; color: var(--on-dark-mute);
  background: rgba(255, 255, 255, .08);
  padding: 4px 9px; border-radius: 999px; white-space: nowrap;
}

.lp-spec { margin: 0; }
.lp-spec__row {
  display: flex; align-items: baseline; gap: var(--s-3);
  padding: 9px 10px; margin-inline: -10px;
  border-radius: var(--r-ctrl);
  border-bottom: 1px solid rgba(255, 255, 255, .09);
  font-size: 13.5px;
}
.lp-spec__row:last-child { border-bottom: 0; }
.lp-spec__row dt { color: var(--on-dark-mute); min-width: 0; }
.lp-spec__row dd {
  margin: 0 0 0 auto; flex: none;
  color: #fff; font-variant-numeric: tabular-nums;
}
/* Not chosen yet. An em dash rather than an invented lamella code — the page's
   whole argument is that it does not make values up.

   --on-dark-mute (60% white), NOT the 38% it was: 38% over --ink measures
   3.49:1, under the 4.5:1 floor for 13.5px text, and this em dash is content —
   it is the row saying "nothing here yet". At 60% it is 6.55:1 and still
   plainly dimmer than the #fff of a row that has an answer. */
.lp-spec__row dd.is-empty { color: var(--on-dark-mute); }

/* Which field is live. Red as a RING, which §1.1 sanctions by name. */
.lp-spec__row.is-focus {
  background: rgba(237, 28, 36, .10);
  box-shadow: inset 0 0 0 1px var(--red-ring);
  border-bottom-color: transparent;
}

/* ---- B: the drawing ------------------------------------------------------ */

/* max-width, not width: in the fan the card is a fixed 330px box and the figure
   is the only child whose height is driven by its own width. At 340 it renders
   249px tall and clears the 282px of content box; wider and overflow:hidden
   would quietly crop the bottom dimension line. */
.lp-fig { display: block; width: 100%; max-width: 340px; height: auto; margin-inline: auto; }
/* --grey-500, not --grey-200. The outline IS the drawing — the shape the whole
   card exists to show — and #D6D6D3 on white is 1.6:1, under the 3:1 that
   1.4.11 asks of a graphic carrying meaning, and faint enough to half-vanish on
   a laptop in daylight. --grey-500 is 4.56:1. The extension lines below stay at
   --grey-200 because those genuinely are hairlines. Hierarchy still holds: the
   outline is stroke-width 2 against the dimension lines' 1. */
.lp-fig__op  { fill: none; stroke: var(--grey-500); stroke-width: 2; }
.lp-fig__ext { fill: none; stroke: var(--grey-200); stroke-width: 1; }
.lp-fig__dim { fill: none; stroke: var(--grey-500); stroke-width: 1; }
.lp-fig__tip { fill: var(--grey-500); }
.lp-fig__num {
  fill: var(--grey-600); font-family: var(--font); font-size: 11px;
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
/* The operation-side arrow. §1.1 lists exactly this as a sanctioned red shape,
   so the drawing is quoting the design system's own example back at it. */
.lp-fig__act { fill: none; stroke: var(--brand-red); stroke-width: 2; }
.lp-fig__acthead { fill: var(--brand-red); }
.lp-fig__cap { fill: var(--grey-600); font-family: var(--font); font-size: 11px; }

/* ---- C: the offer -------------------------------------------------------- */

.lp-sheet {
  position: relative;
  background: #fff; border-radius: var(--r-ctrl);
  box-shadow: var(--shadow-2);
  padding: var(--s-4) var(--s-4) var(--s-3);
  transform: rotate(-1.5deg);
  overflow: hidden;
}
/* The same 3px brand rule §16 puts across the top of the sign-in card. */
.lp-sheet::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 3px; background: var(--brand-fill);
}
.lp-sheet__logo { display: block; height: 18px; width: auto; margin: var(--s-2) 0 var(--s-4); }

.lp-sheet__rows { list-style: none; margin: 0; padding: 0; }
.lp-sheet__row {
  display: flex; align-items: center; gap: var(--s-3);
  padding-block: 7px;
}
.lp-mini { flex: none; width: 24px; height: 20px; }
.lp-mini rect { fill: none; stroke: var(--grey-200); stroke-width: 1.5; }
.lp-mini path { fill: none; stroke: var(--grey-200); stroke-width: 1.5; }
.lp-sheet__lines { flex: 1; min-width: 0; display: grid; gap: 5px; }
.lp-sheet__lines i { display: block; height: 5px; border-radius: 3px; background: var(--grey-200); }
.lp-sheet__lines i:last-child { width: 58%; }

.lp-sheet__hair { display: block; height: 1px; background: var(--grey-200); margin-top: var(--s-3); }
.lp-sheet__foot {
  display: block; margin-top: 7px;
  font-size: 10px; color: var(--grey-500); letter-spacing: .04em;
}

/* ------------------------------------------------------------------ phones */

@media (max-width: 720px) {
  .lp-deck__card { padding: var(--s-4); }
  .lp-cardhd__t { font-size: 18px; }
  .lp-deck__wm { width: 104px; }
}
@media (max-width: 400px) {
  /* The chip and a 18px title stop fitting on one line before the words break. */
  .lp-cardhd { flex-wrap: wrap; gap: var(--s-2); }
  .lp-chip { margin-left: 0; }
}

/* ==========================================================================
   4. LOGOLOOP — the nameplate rail
   ========================================================================== */
/* =========================================================== ȘINA (the rail)
 *
 * Section 3 of the brief: a full-bleed strip fused to the bottom of the hero,
 * carrying the ten catalogue names past the reader.
 *
 * This is React Bits' <LogoLoop /> with the React removed. What survives the
 * port is the mechanism — a track translated by a rAF loop, a velocity eased
 * toward its target, the offset wrapped modulo one measured sequence, and a
 * copy count derived from that measurement. What does not survive is its
 * subject: there are no partner logos to loop, no certifications and no client
 * names, and none may be invented, so what loops is OUR OWN CATALOGUE.
 *
 * THE RESTING STATE IS A NAMEPLATE, NOT A PAUSED MARQUEE.
 * Everything below is written so that the DEFAULT — no script, or reduced
 * motion — is a complete, centred, wrapped list of all ten names with nothing
 * clipped at either end. The scrolling layout is the enhancement, gated behind
 * `html.js` and behind `prefers-reduced-motion: no-preference`, and it is
 * additionally revocable at runtime through `.is-static` (see logoloop.js:
 * the OS setting can flip mid-session and the strip has to follow it).
 *
 * Gating the marquee in the MEDIA QUERY as well as in JS is what stops the
 * layout shift: if the scrolling form only arrived when script added a class,
 * every load would paint a two-row nameplate and then snap to a 72px strip.
 */

.lp-rail {
  /* Tuned here, read by nothing else — the JS never touches these. */
  --rail-gap: 34px;      /* name to mark, and mark to name */
  --rail-mark: 14px;
  --rail-fade: 88px;
  --rail-h: 72px;

  /* #1B1718 is the hero gradient's own bottom stop, so the hero and this strip
     read as ONE dark object rather than as a band stuck underneath one. The
     seam only disappears if this value tracks .lp-stage's last colour stop. */
  --rail-bg: #1B1718;
  /* THE SAME COLOUR AT ZERO ALPHA, and it must be moved with --rail-bg or the
     fade masks below stop matching the strip they sit on. It cannot be
     `transparent`: that is rgba(0,0,0,0), and an engine interpolating it
     un-premultiplied fades the edges through grey instead of into the dark. */
  --rail-bg-0: rgba(27, 23, 24, 0);

  position: relative;
  /* NEVER 100vw. On a document with a scrollbar, 100vw is wider than the
     content box by the gutter width, and that difference is the single
     commonest cause of a sideways scroll at 360px. */
  width: 100%;
  /* Structural, not hopeful: with a max-content track inside an overflow-hidden
     root, no measurement error and no font swap can push the page sideways. */
  overflow: hidden;

  background-color: var(--rail-bg);
  /* The ruler-tick motif along the top edge — 1px tall, drawn as a background
     rather than a pseudo-element because ::before and ::after are both already
     spent on the fade masks below. */
  background-image: repeating-linear-gradient(to right,
      rgba(255, 255, 255, .10) 0 1px, transparent 1px 8px);
  background-repeat: no-repeat;
  background-size: 100% 1px;
  background-position: 0 0;

  /* MOVED OFF .lp-stage. The dark block — hero plus rail — keeps exactly one
     red edge, and it belongs at the block's true bottom, which is here.
     Delete `border-bottom` from .lp-stage when this lands, or there will be a
     red rule through the middle of the dark object. */
  border-bottom: 3px solid var(--brand-fill);
}

/* --- the track -----------------------------------------------------------
   Default: the wrapped nameplate. This is what a visitor with scripting off,
   or with reduced motion on, actually gets, so it is the state that is written
   first and styled properly. */
.lp-rail__track {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  padding: var(--s-4);
  /* A drag across a moving strip that selects half a category name looks
     broken; there is nothing here anyone would want to copy. */
  user-select: none;
  -webkit-user-select: none;
}

/* The second and third copies exist only to make the loop seamless. In the
   nameplate they would be a duplicated list, so they are hidden by default and
   only shown by the marquee rule — which means a clone left in the DOM after a
   runtime motion flip costs nothing and needs no DOM churn to undo. */
.lp-rail__seq ~ .lp-rail__seq { display: none; }

/* flex: 1 1 100% and not `auto`: the sequence has to be told to take the whole
   width of the track, or it stays one max-content flex item ~2300px wide and
   wraps nowhere. Taking the full row is what lets its OWN wrap do the work and
   keeps the centring honest. */
.lp-rail__seq {
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  column-gap: var(--rail-gap);
  row-gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.lp-rail__item {
  flex: 0 0 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
}

/* --- the names -----------------------------------------------------------
   text-transform does the uppercasing so the strings in the Blade @foreach
   stay exactly the strings the rest of the page prints. A rail that shouted
   RULOURI APLICATE in the source would drift from the ledger below the moment
   somebody edited one and not the other. */
.lp-rail__name {
  font: 600 13px/1 var(--font-display);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-dark-mute);
  white-space: nowrap;
  transform-origin: center center;
}

/* THE ONE PIECE OF INFORMATION THIS STRIP CARRIES.
   "Rulouri aplicate" is the only product that can actually be priced today, so
   it is the only name at full white and the only one with a red dot. The rail
   states no availability in words — it cannot mislead, and the honest ledger
   two sections down does the stating properly. */
.lp-rail__item--live .lp-rail__name { color: #fff; }
.lp-rail__item--live .lp-rail__name::before {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-right: 9px;
  border-radius: 50%;
  background: var(--brand-fill);
  vertical-align: middle;
}

/* --- the separator -------------------------------------------------------
   The ten-ray mark between every pair. It is a <use> of an inlined <symbol>,
   NOT <img src="logo-symbol.svg">: that file is solid #ED1C24 and an <img>
   cannot be re-coloured, so it would turn the strip into ten red glyphs of
   decoration — which §1.1 spends its whole comment arguing against.

   The separator <li> also carries .lp-rail__item, so it picks up the marquee's
   trailing margin like any other item. It is hidden by DEFAULT because the
   nameplate is a centred list of names, and a chain of marks landing at the
   start of a wrapped row reads as a bullet nobody asked for. */
.lp-rail__sep { display: none; }

.lp-rail__sep svg {
  display: block;
  width: var(--rail-mark);
  height: var(--rail-mark);
  fill: rgba(255, 255, 255, .30);
}

/* ======================================================= the marquee itself
 * Two gates, and both have to pass:
 *   html.js               — the strip was enhanced, so clones exist
 *   no-preference         — the reader has not asked for stillness
 * plus :not(.is-static), which logoloop.js sets when the OS setting flips
 * while the page is open.
 */
@media (prefers-reduced-motion: no-preference) {

  html.js .lp-rail:not(.is-static) .lp-rail__track {
    flex-wrap: nowrap;
    justify-content: flex-start;
    /* max-content, so the track is exactly as wide as its copies and the JS's
       measurement of one sequence is the true wrap distance. */
    width: max-content;
    column-gap: 0;
    padding: 0;
    height: var(--rail-h);
    will-change: transform;
  }

  html.js .lp-rail:not(.is-static) .lp-rail__seq {
    flex: 0 0 auto;
    flex-wrap: nowrap;
    column-gap: 0;
  }

  html.js .lp-rail:not(.is-static) .lp-rail__seq ~ .lp-rail__seq { display: flex; }

  html.js .lp-rail:not(.is-static) .lp-rail__sep { display: inline-flex; }

  /* MARGIN, NOT GAP, AND ON THE LAST CHILD TOO.
     `gap` puts space BETWEEN items and nothing after the last one, so copy 2
     would start hard against the end of copy 1 and the seam would be visible
     once per lap as a missing space. A trailing margin makes every sequence
     self-contained, which is the whole reason the original component sets
     margin-right on :last-child as well. */
  html.js .lp-rail:not(.is-static) .lp-rail__item { margin-right: var(--rail-gap); }

  /* --- fadeOut ----------------------------------------------------------
     Names must not appear and vanish at a hard edge; they have to arrive out
     of the dark and leave into it. Both stops are variables, and if the hero's
     bottom stop ever changes BOTH have to follow it — --rail-bg and its
     zero-alpha twin --rail-bg-0, declared together at the top of this file. */
  html.js .lp-rail:not(.is-static)::before,
  html.js .lp-rail:not(.is-static)::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: var(--rail-fade);
    z-index: 2;
    pointer-events: none;
  }
  html.js .lp-rail:not(.is-static)::before {
    left: 0;
    background: linear-gradient(to right, var(--rail-bg) 0%, var(--rail-bg-0) 100%);
  }
  html.js .lp-rail:not(.is-static)::after {
    right: 0;
    background: linear-gradient(to left, var(--rail-bg) 0%, var(--rail-bg-0) 100%);
  }
}

/* --- scaleOnHover --------------------------------------------------------
 * The original scales a logo to 1.2 on hover. At 13px with .16em tracking that
 * would be a lurch, so the port re-tunes rather than rescales: 1.12 plus a lift
 * to full white, which is the same gesture at this type size.
 *
 * Gated on (hover: hover) and (pointer: fine) — on a touch screen :hover latches
 * after a tap and the name would simply stay big and white until something else
 * was tapped, which reads as a stuck state, not as feedback.
 *
 * §10's blanket reduced-motion rule flattens the transition to 1ms, so under
 * reduced motion this becomes an instant colour change with no travel. That is
 * correct: the resting state of a transition is always the visible one.
 */
@media (hover: hover) and (pointer: fine) {
  .lp-rail--scale-hover .lp-rail__name {
    transition: transform var(--t-base) var(--ease-out),
                color var(--t-base) var(--ease-out);
  }
  .lp-rail--scale-hover .lp-rail__item:hover .lp-rail__name {
    transform: scale(1.12);
    color: #fff;
  }
  /* The marks do not react. They are punctuation; punctuation that lights up
     when the cursor passes is noise. */
}

/* --- phones --------------------------------------------------------------
 * Nothing is scaled down; the strip is re-tuned. Smaller type and a tighter gap
 * shorten one sequence, which means fewer clones and less to composite, and the
 * 48px masks stop the fade from eating a third of a 360px viewport.
 */
@media (max-width: 720px) {
  .lp-rail {
    --rail-gap: 26px;
    --rail-mark: 12px;
    --rail-fade: 48px;
    --rail-h: 60px;
  }
  .lp-rail__name { font-size: 11.5px; }
  .lp-rail__track { padding: var(--s-4) var(--s-4); }
}

/* ==========================================================================
   3. THE SKETCH LANGUAGE, AND THE SHUTTER REVEAL
   ========================================================================== */
/*
 * COMFORTEX landing — THE SKETCH LANGUAGE.
 *
 * Paste at the TOP of public/assets/landing.css, above section 0: the tokens
 * below are read by the reveal's figure, by the deck's redrawn card B, by the
 * ledger's index column, by the hero grid and by the footer rule — five places
 * in five sections, so they cannot live inside any one of them.
 *
 * IT IS NOT A BLUEPRINT AESTHETIC. It is the hand app/Drawing/RaDrawing.php
 * already draws in, enlarged to the page: four stroke weights in a fixed
 * hierarchy, perpendicular end ticks, `%d mm` on every figure, dashed grey-400
 * for what is not yet known, Inter throughout. The landing page and the sheet
 * the customer receives are meant to be one document, and that only holds if
 * this file quotes the generator rather than inventing a drafting style.
 *
 * WHERE IT MAY APPEAR, and the list is closed: the reveal (#desen), the hero
 * grid, .lp-proof's marks, .lp-h2's rule, deck cards B and C, the ledger, the
 * #acces notes, the footer rule. WHERE IT MUST NOT: .lp-bar, any .btn, the
 * request card's interior, any form control, behind .lp-how. Those surfaces are
 * the tool's own chrome and they stay plain. The moment this becomes a
 * background texture it stops being a drawing and becomes wallpaper, and then
 * the drawing in the reveal means nothing.
 *
 * ─────────────────────────────────────────────────────────────────────────
 * READ THIS BEFORE MERGING. Sections 1 and 2 below are also present, in the
 * same form, in the reveal component's own stylesheet. Ship ONE copy. Sections
 * 3, 4 and 5 exist only here and the drawing is wrong without them.
 * ─────────────────────────────────────────────────────────────────────────
 */

/* ==========================================================================
   SK 1. TOKENS                                    (shared — merge, do not dup)
   ========================================================================== */

:root {
  /* Every value is drawn from the app.css ramp. There is no new colour here and
   * no hue outside brand red and the greys.
   *
   * --sk-ink is --grey-500, 4.56:1 on white and ~4.4:1 on the grey-50 sheet:
   * past the 3:1 that WCAG 1.4.11 asks of a graphic carrying meaning.
   * --grey-200 was the instinct and it measures 1.6:1 — the outline IS the
   * drawing, and a drawing you cannot see on a laptop in daylight has not been
   * drawn. Only genuine hairlines go that light.
   *
   * --sk-unknown is --grey-400, the ramp's own "placeholder, not text" value.
   * Everything the product cannot yet compute is drawn in it: the casetă, the
   * axes, the em dash. Frontend spec §4.2 is explicit that this is a neutral
   * placeholder and NOT an error state, so it is never alert red and — §1.1 —
   * never brand red either. */
  --sk-paper:   var(--grey-50);
  --sk-ink:     var(--grey-500);   /* object and dimension lines */
  --sk-figure:  var(--grey-600);   /* the numbers — 6.72:1, they are read */
  --sk-note:    var(--grey-500);   /* leader labels */
  --sk-unknown: var(--grey-400);   /* what is not yet known */
  --sk-hair:    var(--grey-200);   /* extension lines, hatch, row rules */

  /* FOUR WEIGHTS AND NO FIFTH.
   *
   * The generator's ratio is 1.3 : 0.6 : 0.4 (RaDrawing lines 258, 266,
   * 605-607). Scaled to a screen that would be 1.75 : 0.81 : 0.54, and 0.54px
   * does not render on a 1× display — it renders as a grey smear, which is
   * worse than not drawing the line at all. The ratio is DELIBERATELY loosened
   * to 1.75 : 1 : 0.75: the same hierarchy, at sizes a screen can draw. Nobody
   * should "correct" this back to the generator's numbers. */
  --sk-w-object:  1.75px;   /* the part itself */
  --sk-w-hidden:  1.25px;   /* dashed 5 3 — exists, but is not yet dimensioned */
  --sk-w-dim:     1px;      /* dimension lines, extension overshoots, leaders */
  --sk-w-texture: .75px;    /* hatch, spiral, the module grid */
  --sk-w-axis:    1px;      /* chain-dashed 12 3 2 3 */

  /* The module. Minor rule every 16px, major every 5 modules. One number, so
   * the hero's dark sheet and the reveal's light one are provably the same
   * paper rather than two grids that happen to look alike. */
  --sk-mod: 16px;

  /* Documentation, not a value anything reads: dimension end ticks are 9 user
   * units, centred on the line, in every figure on this page. It is baked into
   * the path data because the path data lives in a fixed viewBox. */
  --sk-tick: 9;
}

/* ==========================================================================
   SK 2. THE FOUR WEIGHTS                          (shared — merge, do not dup)
   ========================================================================== */
/*
 * Every stroked node in the markup ALSO carries vector-effect="non-scaling-
 * stroke" as an attribute. That is the decision that lets one figure work from
 * 328px to 520px wide: stroke widths resolve in CSS pixels whatever the viewBox
 * scale, so --sk-w-object is 1.75px on a phone and 1.75px on a desk. Written as
 * an attribute and not only as the rule below, because Safari resolved
 * `vector-effect` as a CSS property late, and a figure whose hatch and
 * extension lines have vanished is not a degraded figure, it is a smear.
 */
.sk-obj    { fill: none; stroke: var(--sk-ink);     stroke-width: var(--sk-w-object); }
.sk-hidden { fill: none; stroke: var(--sk-unknown); stroke-width: var(--sk-w-hidden); stroke-dasharray: 5 3; }
.sk-dim    { fill: none; stroke: var(--sk-ink);     stroke-width: var(--sk-w-dim); }
.sk-ext    { fill: none; stroke: var(--sk-hair);    stroke-width: var(--sk-w-texture); }
.sk-axis   { fill: none; stroke: var(--sk-unknown); stroke-width: var(--sk-w-axis); stroke-dasharray: 12 3 2 3; }
.sk-tex    { fill: none; stroke: var(--sk-hair);    stroke-width: var(--sk-w-texture); }
.sk-lead   { fill: none; stroke: var(--sk-note);    stroke-width: var(--sk-w-dim); }
.sk-pick   { fill: var(--sk-note); }

/* RED IS A FILL HERE, which §1.1 sanctions by name, and there is exactly one of
   these per drawing. --brand-red and not --brand-fill: §1.1 keeps the logo's
   true red wherever red is a shape rather than a label's ground. */
.sk-act { fill: var(--brand-red); }

/* The backstop for the attribute above. Every stroked node in the drawing
   already carries vector-effect as an attribute, so this rule only matters where
   the attribute has been forgotten — which is exactly the edit it is here to
   survive. */
.sk-dwg path,
.sk-dwg rect,
.sk-dwg circle,
.sk-dwg polyline { vector-effect: non-scaling-stroke; }

/* ==========================================================================
   SK 3. WHAT THE DRAWING NEEDS AND NOTHING ELSE DEFINES        (must be added)
   ========================================================================== */

/* The opening interior. White on a grey-50 sheet is the only value on this page
   that reads as LIT, and the opening is where light comes through. This is what
   the curtain uncovers, so it is also the reason the reveal has a subject at
   all: without it the shutter rises to reveal the same paper it was sitting on. */
.sk-lite { fill: var(--white); stroke: none; }

/* The guides, shown in section. Fill and outline are separate elements in the
   markup so that hatch weight and object weight stay two decisions — hence a
   fill-only rule here with no stroke of its own. */
.sk-hatchfill { fill: url(#sk-hatch); stroke: none; }
.sk-hatch__l  { fill: none; stroke: var(--sk-hair); stroke-width: var(--sk-w-texture); }

/* THE DRUM IS --sk-unknown, NOT --sk-hair.
 *
 * It is the second-most important thing in the casetă after the em dash: it is
 * the only progress indicator the section has, and it is legible from p=0
 * because the casetă is never covered. --grey-200 is a hairline value — at the
 * 0.7 user-units-per-pixel of a 360px phone it is a suggestion of a spiral. The
 * drum is drawn at texture WEIGHT because it is texture, but in the ink the
 * page reserves for what is not yet known, which is exactly what the casetă is.
 * That is also what blueprintSpec asks for; if .sk-tex ever moves to
 * --sk-unknown wholesale, this rule becomes redundant and can go. */
.sk-drum .sk-tex { stroke: var(--sk-unknown); }

/* One hook for the whole leader group, so the pick dots cannot be left behind
   when the leaders are hidden below 721px. Two grey dots floating in the sheet
   margin, pointing at nothing, is the exact failure this guards. */
@media (max-width: 720px) {
  .sk-leadgrp { display: none; }
}

/*
 * THE PAPER BREAK, GUARDED — and the guard is not defensive tidiness.
 *
 * `paint-order: stroke fill` is what makes the figure burn its own gap in the
 * dimension line it sits on, which is most of what separates a drawing from an
 * illustration with labels. WITHOUT paint-order support the stroke paints OVER
 * the fill, and a grey-50 stroke over a grey-50 sheet is a figure that has
 * disappeared — on the only two numbers this page prints, and silently.
 * Unsupported, this way, the number simply sits on an unbroken line: less
 * elegant, still a drawing, still readable.
 *
 * stroke-width is 5 USER UNITS, which is why text is not in the vector-effect
 * list above: the break has to grow with the glyph rather than stay at 5 device
 * pixels while the type doubles.
 */
@supports (paint-order: stroke) {
  .sk-num {
    paint-order: stroke fill;
    stroke: var(--sk-paper);
    stroke-width: 5;
    stroke-linejoin: round;
  }
}

/* ==========================================================================
   SK 4. TYPE IN THE DRAWING                       (shared — merge, do not dup)
   ========================================================================== */
/*
 * Inter, never Archivo. Archivo is the display face for headings; Inter is the
 * hand the generator draws in, and every font-family in RaDrawing.php says so.
 * Tabular figures because a column of dimensions has to align, and `mm` on
 * every figure because the generator prints `%d mm` and a bare 1480 is a number
 * rather than a dimension.
 *
 * SIZED IN USER UNITS, NOT WITH clamp(). SVG text scales with the viewBox, so a
 * single font-size renders at three different pixel sizes across the three
 * figure widths. clamp() would fight that scale rather than compensate for it.
 * The three blocks below exist to satisfy exactly one rule: NO TEXT IN THE
 * DRAWING RENDERS BELOW 11px AT ANY WIDTH.
 */
.sk-dwg text {
  font-family: var(--font);
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
}
.sk-num          { fill: var(--sk-figure);  font-size: 12px; }  /* 13.3px at 520 wide */
.sk-num--unknown { fill: var(--sk-unknown); font-size: 14px; }
.sk-leadlabel    { fill: var(--sk-note);    font-size: 11px; }  /* 12.2px at 520 wide */

@media (max-width: 960px) {
  .sk-num          { font-size: 14px; }   /* 11.9px at 400 wide */
  .sk-num--unknown { font-size: 16px; }
  .sk-leadlabel    { font-size: 13px; }
}
@media (max-width: 720px) {
  .sk-num          { font-size: 16px; }   /* 11.2px at the 328px floor */
  .sk-num--unknown { font-size: 18px; }
}

/*
 * REDUCED MOTION NEEDS NO RULE HERE, AND THAT IS THE POINT.
 *
 * The still state and the finished state are the same picture: the covering is
 * never in the markup, so with script off, with anime missing, or with motion
 * refused the reader gets the complete dimensioned drawing. There is nothing in
 * this file to switch off, because there is nothing in this file that hides
 * anything. Constraint 7 is satisfied structurally rather than by a guard that
 * could be got wrong.
 */

/* ==========================================================================
   SK 5. THE MODULE GRID — the motif, reusable                  (must be added)
   ========================================================================== */
/*
 * ONE MOTIF SEEN FROM TWO SIDES, and the single highest-leverage thing in this
 * file for four gradients and zero JavaScript.
 *
 * A single-pitch grid is a TEXTURE; a two-tier one is a SHEET, and the
 * hierarchy is the entire difference. The hero's room and the facade the shutter
 * is mounted on both draw it, one screen apart in depth, and both are expressed
 * in --sk-mod — which is what makes the paper the drawing is on and the paper
 * the page is on provably the same paper rather than two grids that happen to
 * look alike.
 *
 * ON A PSEUDO-ELEMENT, not on the section itself, because the radial mask has
 * to cut the grid without cutting the words: masking an element masks its
 * content too. z-index -1 under `isolation: isolate` keeps the layer inside its
 * own section — without the isolation it slides behind whatever ancestor
 * happens to own the stacking context and vanishes under the page background.
 *
 * The mask is what stops the grid meeting a hard edge. A drafting sheet that
 * ends in a crisp ruled boundary reads as a table; one that fades reads as
 * paper continuing past the crop.
 *
 * Both tiers are weak enough never to compete with a dimension line. That is
 * the test: the grid is the sheet, not the drawing.
 */
.sk-ground {
  position: relative; isolation: isolate;
  --sk-rule-minor: rgba(35, 31, 32, .035);
  --sk-rule-major: rgba(35, 31, 32, .06);
}
.sk-ground::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    repeating-linear-gradient(to right,  var(--sk-rule-major) 0 1px, transparent 1px calc(var(--sk-mod) * 5)),
    repeating-linear-gradient(to bottom, var(--sk-rule-major) 0 1px, transparent 1px calc(var(--sk-mod) * 5)),
    repeating-linear-gradient(to right,  var(--sk-rule-minor) 0 1px, transparent 1px var(--sk-mod)),
    repeating-linear-gradient(to bottom, var(--sk-rule-minor) 0 1px, transparent 1px var(--sk-mod));
  -webkit-mask-image: radial-gradient(120% 90% at 50% 28%, #000 0%, #000 46%, transparent 82%);
          mask-image: radial-gradient(120% 90% at 50% 28%, #000 0%, #000 46%, transparent 82%);
}

/* The same paper seen from the dark side, which is what the hero's room uses.
   Stronger than the paper values because a light rule on ink loses more to the
   eye than a dark rule on paper does — .042 and .082 measure as the same
   presence as .035 and .06 do on grey-50. */
.sk-ground--ink {
  --sk-rule-minor: rgba(255, 255, 255, .042);
  --sk-rule-major: rgba(255, 255, 255, .082);
}


/* ==========================================================================
   5. THE SHUTTER — the hero
   ========================================================================== */
/*
 * The page's one big move, and the only one: a rolling shutter, seen from the
 * side of the building, that the camera walks round to square-on and then
 * opens — uncovering the dimensioned drawing the application produces.
 *
 * IT IS A REAL OBJECT IN A REAL SPACE, not a picture of one. There is a facade
 * plane at the back, a casetă with a top and an underside, two guide rails with
 * their inner channels showing, an opening recessed into the wall, and a
 * curtain of identical slats hanging in it. Everything is laid out in one
 * perspective, so the camera move is a camera move rather than a skew: the
 * guides' channels open and close, the casetă's underside comes into view, and
 * the recessed sheet drifts against the frame in front of it. None of that can
 * be faked with a 2D transform and all of it is free once the boxes are built.
 *
 * ────────────────────────────────────────────────────────────────────────────
 * EVERY SLAT IS EXACTLY THE SAME HEIGHT, AT EVERY MOMENT OF THE ANIMATION.
 *
 * That is the difference between a rolling shutter and a venetian blind, and
 * the previous build got it wrong: it converged the slats into a pack, so the
 * lower ones visibly shortened as they were overtaken. Here the curtain is ONE
 * element and the only thing that ever changes is its translateY. Nothing is
 * scaled, nothing converges, and the slats cannot compress because there is no
 * per-slat transform to compress them with. Slat height is
 * --sh-open-h / 25.7 — 24 slats plus one bottom rail at 1.7× — so it is derived
 * once and shared by all of them at every viewport size.
 * ────────────────────────────────────────────────────────────────────────────
 *
 * WHAT THE PAGE LOOKS LIKE IF NOTHING RUNS. The curtain is EMPTY in the markup
 * — landing.js builds the slats into it — so with scripting blocked, with the
 * section off screen, or under prefers-reduced-motion there is no covering at
 * all: the opening stands open with the drawing in it and every word of the
 * hero beside it. The pinned scroll is gated on `.sh-live`, which the script
 * adds only when it has actually built something, so a page whose JavaScript
 * never arrives is an ordinary one-screen hero and not three screens of dead
 * scrolling. Nothing here reveals content; it only puts something in front of
 * content and then takes it away.
 */

/* ------------------------------------------------------------- the section --
 * The scroll budget. `.sh` is tall and `.sh-pin` is one viewport of it stuck to
 * the top, which is the whole pinning mechanism — no wheel handler, no
 * preventDefault, no scroll library. The reader's scroll does exactly what it
 * always does; the sticky child just stays put while it happens.
 *
 * THE HEIGHT IS GATED ON .sh-live AND NOT ON html.js. Both would avoid the
 * flash, but only this one is honest about the failure that matters: if
 * landing.js 404s, or throws before it builds, or the reader has asked for
 * reduced motion, there is nothing to choreograph and 300vh of empty scrolling
 * is a worse page than no animation. The class is added after the slats exist.
 * The cost is a scrollbar that resizes shortly after load; nothing above the
 * fold moves, because the pin is already at the top.
 */
/* #1B1718 is also .lp-rail's --rail-bg. The hero and the nameplate strip
   directly under it are meant to read as ONE dark object, and that only holds
   while this colour and the room's last gradient stop track it. */
.sh { position: relative; background: #1B1718; }

.sh-pin {
  position: relative;
  min-height: 100vh;
  display: flex; flex-direction: column;
}

.sh-live { height: 300vh; }
.sh-live .sh-pin { position: sticky; top: 0; height: 100vh; min-height: 0; }

/* ----------------------------------------------------------------- the room */
/*
 * What the shutter is standing in. Deliberately almost nothing: a warm-black
 * gradient with the brand's own red bleeding in from the top right, and the
 * page's two-tier module grid over it (.sk-ground--ink, shared with the sheet
 * further down — one motif, seen from the light side and the dark side).
 */
.sh-room {
  position: relative; flex: 1; isolation: isolate; overflow: hidden;
  display: grid; align-items: center;
  background:
    radial-gradient(1100px 640px at 78% 4%, rgba(237,28,36,.17), transparent 60%),
    radial-gradient(820px 520px at 4% 96%, rgba(237,28,36,.06), transparent 64%),
    linear-gradient(168deg, #322B2C 0%, #231F20 48%, #1B1718 100%);
  color: var(--on-dark);
}

/* ---------------------------------------------------------------- the stage
 * A two-column grid at desk width: the words on the left, the object on the
 * right. It is NOT an overlay. Type over a shutter is fine while the shutter is
 * shut and dark, and illegible the moment it lifts and a sheet of grey-50 paper
 * slides in behind the words — so the two never share ground. The scrim below
 * only softens the object's edge into the copy column; it is not carrying any
 * contrast.
 */
.sh-stage {
  position: relative; z-index: 1;
  width: 100%; max-width: var(--lp-w); margin: 0 auto;
  padding-inline: var(--s-5);
  display: grid; grid-template-columns: minmax(0, 38%) minmax(0, 1fr);
  align-items: center; gap: var(--s-6);
  padding-block: calc(76px + var(--s-5)) var(--s-6);
}

/* --------------------------------------------------------------- the words - */

.sh-copy { position: relative; z-index: 2; }
.sh-copy .lp-title { max-width: 11ch; }
.sh-copy .lp-lede  { max-width: 40ch; }

/* The scroll cue. It is the one instruction the page gives, and it has to be
   given: a pinned hero that only moves when you scroll looks broken if you do
   not. It fades out as soon as the camera starts moving (the script writes the
   opacity), and it is aria-hidden because "scroll down" is not information a
   screen reader needs. */
.sh-cue {
  display: flex; align-items: center; gap: 10px;
  margin-top: var(--s-6);
  font: 500 11.5px/1 var(--font); letter-spacing: .13em; text-transform: uppercase;
  color: rgba(255,255,255,.5);
}
.sh-cue__rail {
  position: relative; width: 46px; height: 2px; border-radius: 2px;
  background: rgba(255,255,255,.16); overflow: hidden;
}
.sh-cue__rail::after {
  content: ""; position: absolute; inset: 0; width: 40%; border-radius: 2px;
  background: var(--brand-red);
}
@media (prefers-reduced-motion: no-preference) {
  .sh-cue__rail::after { animation: sh-cue 2.4s var(--ease-in-out) infinite; }
}
@keyframes sh-cue {
  0%, 100% { transform: translateX(-100%); }
  55%      { transform: translateX(250%); }
}

/* ---------------------------------------------------------------- the scene
 * ONE perspective for the whole object. 1500px is a long lens: at the ~28° the
 * camera starts at, a short lens (600-800px) throws the far guide rail out to a
 * violent taper and the shutter reads as a wedge rather than as a flat curtain
 * seen from an angle. Long enough to be a photograph, short enough to be 3D.
 *
 * perspective-origin sits a little above centre because the object is below the
 * horizon of somebody standing in front of a shopfront.
 */
.sh-scene {
  position: relative; z-index: 1;
  display: grid; place-items: center;
  perspective: 1500px;
  perspective-origin: 50% 42%;
  min-width: 0;
}

/* The camera. This is the ONLY element the script rotates, and it carries the
   whole scene with it — which is what makes the move read as walking round the
   object rather than as the object deforming. */
.sh-cam {
  position: relative;
  width: var(--sh-unit-w);
  transform-style: preserve-3d;
  /* The resting pose, and it is the FINAL pose: square-on, dead centre. With no
     script the reader gets the elevation, which is the right still image. The
     script's first frame overwrites this with the opening angle. */
  transform: none;
}

/* ---------------------------------------------------------- the measurements
 * Every dimension on the object descends from ONE number, the height of the
 * opening, so the whole thing scales as a single machined part and no two
 * elements can round apart at a breakpoint.
 *
 * 25.7 = 24 slats + one bottom rail at 1.7 slats. That divisor is the reason
 * every slat is identical: slat height is a quotient of the opening, not a
 * fixed pixel value that has to be reconciled with one afterwards.
 */
.sh {
  --sh-open-h:  min(66vh, 620px);
  --sh-ratio:   .80;                                        /* opening w ÷ h */
  --sh-open-w:  calc(var(--sh-open-h) * var(--sh-ratio));
  --sh-slat:    calc(var(--sh-open-h) / 25.7);
  --sh-case-h:  calc(var(--sh-open-h) * .088);
  --sh-guide-w: calc(var(--sh-open-w) * .05);
  --sh-sill-h:  calc(var(--sh-open-h) * .028);
  --sh-unit-w:  calc(var(--sh-open-w) + var(--sh-guide-w) * 2);

  /* ======================================================= THE THIRD DIMENSION
   *
   * THE CURTAIN'S PLANE IS z = 0 AND EVERYTHING ELSE IS MEASURED FROM IT. That
   * choice is the whole reason the numbers below stay simple: the one surface
   * that must land exactly on its layout box at every angle is the curtain, and
   * a plane at z = 0 is projected at scale 1 with nothing to compensate for.
   *
   *      +--sh-front ......... the casetă's front face — furthest forward
   *      +--sh-rail .......... the guide rails and the sill
   *       0 .................. the curtain, hanging in the channels
   *      −--sh-recess ........ the sheet, set back through the hole
   *      −--sh-recess − 8 .... the facade
   *
   * THE FIRST BUILD HAD ALL OF IT ON ONE PLANE with the wall 46px behind, and
   * it read as a decal: at the opening angle there was nothing between the front
   * face and the facade to catch the light, so the object had a silhouette and
   * no body. What was missing is not depth as a number, it is FACES — the ends
   * of the box and the outsides of the rails, which are the surfaces whose
   * visible width changes as the camera turns. Those exist now, and --sh-front
   * is what they are made of.
   *
   * THE _-n VARIANTS ARE THE SAME NUMBERS WITHOUT UNITS, and they are not
   * duplication for its own sake. The recessed sheet has to be scaled up by
   * exactly the perspective foreshortening it suffers, (P + d) ÷ P, and scale()
   * takes a NUMBER: a calc() over two lengths is a length, which is invalid
   * there. Keeping both forms means the compensation is derived from the depth
   * instead of being a magic 1.031 that silently stops matching the moment
   * anybody edits it. Move one, move its pair. */
  --sh-persp-n: 1500;                                       /* == .sh-scene's perspective */

  --sh-front-n: 74;    --sh-front:  calc(var(--sh-front-n)  * 1px);
  --sh-rail-n:  40;    --sh-rail:   calc(var(--sh-rail-n)   * 1px);
  --sh-recess-n: 30;   --sh-recess: calc(var(--sh-recess-n) * 1px);

  /* How long the return faces are: from the front of a part all the way back to
     the facade it is screwed to. Nothing here is a separate decision. */
  --sh-case-d: calc(var(--sh-front) + var(--sh-recess) + 8px);
  --sh-rail-d: calc(var(--sh-rail)  + var(--sh-recess) + 8px);

  /* ═══ THE LAPS, AND THEY ARE NOT TRIM ═══════════════════════════════════════
   *
   * How far the casetă hangs down over the top of the curtain, and how far the
   * sill tucks up under the bottom of it.
   *
   * THEY EXIST BECAUSE A PERSPECTIVE MOVES THINGS APART. Two elements that abut
   * exactly in layout do NOT abut on screen once one of them is brought forward:
   * a plane at +z projects away from the perspective origin by roughly its
   * distance from that origin times z ÷ P. The casetă sits well above the origin
   * and stands 74px proud, so its bottom edge lifted about SIXTEEN PIXELS clear
   * of the curtain's top — and you could see the room through the slot. The sill
   * is nearer the origin and only 40px proud, so its gap was about four.
   *
   * Measured, not guessed: elementFromPoint down seven columns of the object
   * found the same two holes in every one of them.
   *
   * They are also what the object actually looks like. A casetă's front panel
   * hangs over the slot the curtain runs into, and a sill tucks under the bottom
   * rail; neither one meets the curtain edge to edge in real life either. Sized
   * off the depth that causes the shift, so they cannot fall behind it at a
   * breakpoint: a third of the projection is comfortably more than the ~5 % of
   * it that the shift can ever be. */
  --sh-lap-case: calc(var(--sh-front) * .34);
  --sh-lap-rail: calc(var(--sh-rail)  * .34);
}

/* ----------------------------------------------------------------- the wall
 * The facade the shutter is mounted on, behind everything else. It is
 * what makes the camera move read: at 28° the frame slides across it by tens of
 * pixels, which is parallax, and parallax is the only cue that says "this is
 * two planes and not one picture".
 *
 * It is oversized on purpose. The recessed sheet behind the opening sits on
 * this same plane and drifts against the frame as the camera swings; without a
 * wall wider than that drift, the sheet would peek out past the guide rails
 * with the room showing through the gap.
 */
.sh-wall {
  position: absolute; top: 50%; left: 50%;
  width: 240%; height: 190%;
  /* EIGHT PIXELS BEHIND THE RECESSED SHEET AND NOT LEVEL WITH IT. They used to
     share a plane, which is a coin toss decided by DOM order — and the cast
     shadow this element carries would then be resolved against the sheet the
     same way. Clearance makes the ordering a fact rather than a convention. */
  transform: translate(-50%, -50%) translateZ(calc(var(--sh-recess) * -1 - 8px));
  background:
    radial-gradient(120% 90% at 50% 34%, rgba(255,255,255,.05), transparent 70%),
    linear-gradient(172deg, #2B2526 0%, #1E1A1B 62%, #141112 100%);
  background-image:
    /* WHAT THE OBJECT THROWS ON THE WALL, and it is painted here rather than as
       a blurred element of its own. A shadow belongs to the surface it falls on:
       drawn as a child of the unit it has to be given a Z, and any Z in FRONT of
       the wall is also in front of the recessed sheet — which is how the first
       build ended up laying a 66 % black radial over the drawing the shutter had
       just uncovered. On the wall it cannot reach the sheet, it costs no extra
       element and no blur filter, and it is offset down and right of the object
       because the light on this stage comes from the upper left. */
    radial-gradient(31% 37% at 52% 55%, rgba(0,0,0,.62), rgba(0,0,0,0) 72%),
    repeating-linear-gradient(to right,  rgba(255,255,255,.055) 0 1px, transparent 1px calc(var(--sk-mod) * 5)),
    repeating-linear-gradient(to bottom, rgba(255,255,255,.055) 0 1px, transparent 1px calc(var(--sk-mod) * 5)),
    repeating-linear-gradient(to right,  rgba(255,255,255,.028) 0 1px, transparent 1px var(--sk-mod)),
    repeating-linear-gradient(to bottom, rgba(255,255,255,.028) 0 1px, transparent 1px var(--sk-mod)),
    linear-gradient(172deg, #2B2526 0%, #1E1A1B 62%, #141112 100%);
  /* The core has to stay opaque out past the object, or the mask eats the
     shadow's own edges and the thing it grounds floats again. */
  -webkit-mask-image: radial-gradient(64% 58% at 50% 46%, #000 46%, transparent 92%);
          mask-image: radial-gradient(64% 58% at 50% 46%, #000 46%, transparent 92%);
}

/* ----------------------------------------------------------------- the unit */

.sh-unit { position: relative; transform-style: preserve-3d; }


/* ---------------------------------------------------------------- the casetă
 * A real box with a front and a top, so that at the opening angle you look
 * slightly down onto it and see its lid. The lid is what tells you the object
 * has a depth before the guides do.
 *
 * The lid folds about its own BOTTOM edge, which is the seam it shares with the
 * front face — rotateX(90deg) with transform-origin at that seam sends it back
 * and up, into the wall. Fold it about the top instead and it swings forward
 * out of the facade, which is a box lying on its back.
 */
.sh-case {
  /* Taller than the box by one lap, and then pulled back up by the same amount
     so the object's overall height is untouched. The extra strip is the front
     panel hanging over the slot the curtain runs into — see --sh-lap-case. */
  position: relative;
  height: calc(var(--sh-case-h) + var(--sh-lap-case));
  margin-bottom: calc(var(--sh-lap-case) * -1);
  transform-style: preserve-3d;
  /* FURTHEST FORWARD OF ANYTHING, because a real casetă is a box bolted over
     the rails rather than flush with them. It overhangs them by --sh-front minus
     --sh-rail, which is also why it renders very slightly larger than its layout
     box and laps over the guides below it — exactly what the object does. */
  transform: translateZ(var(--sh-front));
  background: linear-gradient(to bottom,
      #4A4243 0%, #3A3334 16%, #2B2526 54%, #201B1C 84%, #171314 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.11),
    inset 0 -1px 0 rgba(0,0,0,.55),
    0 14px 30px rgba(0,0,0,.42);
}
.sh-case__lid {
  position: absolute; left: 0; right: 0; bottom: 100%;
  height: var(--sh-case-d);
  transform-origin: 50% 100%;
  transform: rotateX(90deg);
  /* Lit end nearest the viewer: this face is nearly edge-on to the eye and the
     gradient is what stops it reading as a flat black band. */
  background: linear-gradient(to bottom, #100D0E 0%, #241F20 46%, #3E3738 88%, #4E4647 100%);
}

/* ═══ THE END CAPS, AND THIS IS THE ANSWER TO "IT LOOKS FLAT" ═══════════════
 *
 * The two faces that close the ends of the box. They are the widest return
 * surfaces on the object — --sh-front + --sh-recess deep — so at the opening
 * angle one of them presents sin(28°) ≈ 47 % of that width straight at the
 * reader, which is about fifty pixels of lit metal that simply was not there
 * before. Nothing else on a shutter reads as thickness the way these do.
 *
 * ONLY ONE IS EVER VISIBLE, and which one is decided by the sign of the camera's
 * rotateY: at −28° the right-hand edge swings towards the viewer, so the RIGHT
 * cap's outward normal turns to face them and the left cap's turns away. Both
 * are built anyway — the back face is free (it is never rasterised) and the
 * object stays correct if the pan is ever mirrored.
 *
 * They fold about the seam they share with the front face, which is why the
 * origin is the case's own edge and not the cap's centre: rotateY(±90deg) then
 * lays them straight back towards the facade instead of swinging them out
 * through it. */
.sh-case__cap {
  position: absolute; top: 0; bottom: 0;
  width: var(--sh-case-d);
  background: linear-gradient(to bottom, #423B3B 0%, #2A2425 40%, #1A1617 100%);
}
.sh-case__cap--l { left: 0;  transform-origin: 0 50%;    transform: rotateY(90deg); }
.sh-case__cap--r { right: 0; transform-origin: 100% 50%; transform: rotateY(-90deg); }
/* The service seam a real casetă has along its face, and a place for the mark
   to sit. Two hairlines, nothing more. */
.sh-case__seam {
  position: absolute; left: 6%; right: 6%; top: 52%; height: 1px;
  background: rgba(0,0,0,.5);
  box-shadow: 0 1px 0 rgba(255,255,255,.05);
}

/* ------------------------------------------------------------- the guides --
 * Two channels, one each side, running the height of the opening. Each has a
 * front face and an inner side face folded back into the wall — and the side
 * face is the single most valuable element in the whole object, because it is
 * the one thing whose visible WIDTH changes as the camera turns. At 28° you are
 * looking into the near channel; square-on it closes to nothing. That is the
 * camera move, made visible.
 */
.sh-guide {
  position: absolute; top: 0; bottom: 0;
  width: var(--sh-guide-w);
  transform-style: preserve-3d;
  /* Forward of the curtain, behind the casetă. The rails really do stand off
     the facade, and they really are shallower than the box on top of them. */
  transform: translateZ(var(--sh-rail));
  background: linear-gradient(to right,
      #4A4243 0%, #322B2C 36%, #241F20 70%, #191516 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}
.sh-guide--l { left: 0; }
.sh-guide--r { right: 0; background: linear-gradient(to left,
      #4A4243 0%, #322B2C 36%, #241F20 70%, #191516 100%); }

/* THE TWO RETURN FACES OF A RAIL: the channel the curtain runs in, on the
   inside, and the outside of the extrusion. Both are --sh-rail-d deep and both
   fold about the seam they share with the front face.
 *
 * THE SIGNS ARE NOT INTERCHANGEABLE and both were wrong on the first build.
 * rotateY(+90deg) takes the element's +X extent to −Z, i.e. away from the
 * viewer, which is where a return face has to go. A face placed with `left:100%`
 * extends to +X from its seam and takes +90; one placed with `right:100%`
 * extends to −X and takes −90. Get it backwards and the faces stand out of the
 * facade towards the reader — two bright fins with the sheet showing past them.
 *
 * THE OUTSIDE FACE IS THE ONE THAT SELLS THE OBJECT. The channel is hidden by
 * the curtain for the whole of the camera move; the outside is not, and it is
 * the surface whose visible width shrinks to nothing as the shutter turns
 * square-on. That change IS the pan. */
.sh-guide__in,
.sh-guide__out { position: absolute; top: 0; bottom: 0; width: var(--sh-rail-d); }

.sh-guide__in  { background: linear-gradient(to right, #0C0A0A 0%, #1C1819 72%, #292324 100%); }
.sh-guide__out { background: linear-gradient(to right, #373031 0%, #221D1E 62%, #131011 100%); }

.sh-guide--l .sh-guide__in  { left: 100%;  transform-origin: 0 50%;    transform: rotateY(90deg); }
.sh-guide--l .sh-guide__out { right: 100%; transform-origin: 100% 50%; transform: rotateY(-90deg); }
.sh-guide--r .sh-guide__in  { right: 100%; transform-origin: 100% 50%; transform: rotateY(-90deg); }
.sh-guide--r .sh-guide__out { left: 100%;  transform-origin: 0 50%;    transform: rotateY(90deg); }

/* ------------------------------------------------------------------ the sill */
.sh-sill {
  /* The same trade at the other end: one lap taller, pulled up by one lap, so
     the bottom rail comes to rest BEHIND the sill's top edge instead of four
     pixels clear of it with the room showing between. */
  position: relative;
  height: calc(var(--sh-sill-h) + var(--sh-lap-rail));
  margin-top: calc(var(--sh-lap-rail) * -1);
  transform-style: preserve-3d;
  transform: translateZ(var(--sh-rail));
  background: linear-gradient(to bottom, #4A4243 0%, #2C2627 52%, #100D0E 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 12px 26px rgba(0,0,0,.45);
}
/* The underside, folded down and back. We are looking at the object from
   slightly ABOVE, so this face is never seen straight on — but its edge is, and
   without it the sill ends in a hard line with nothing under it.

   DELIBERATELY SHORTER THAN THE OTHER RETURNS. A window sill's underside is a
   lip, not a shelf, and at full --sh-rail-d its far end sticks out past the
   guide as a bright wedge with nothing closing it. Sixty per cent, fading to
   the room's own black, ends it in shadow instead of in a corner. */
.sh-sill__soffit {
  position: absolute; left: 0; right: 0; top: 100%;
  height: calc(var(--sh-rail-d) * .6);
  transform-origin: 50% 0;
  transform: rotateX(-90deg);
  background: linear-gradient(to bottom, #1A1617 0%, #100D0E 55%, #080606 100%);
}

/* ---------------------------------------------------------------- the opening
 * NO overflow HERE. `overflow` other than `visible` forces transform-style to
 * flat, and flattening this element is what would collapse the recess: the
 * sheet behind would snap onto the frame's own plane and the parallax would
 * disappear. The clipping happens one level down, on .sh-clip, whose children
 * are a flat curtain and have nothing to lose by it.
 */
.sh-open {
  position: relative;
  margin-inline: var(--sh-guide-w);
  height: var(--sh-open-h);
  transform-style: preserve-3d;
}

/* WHAT IS BEHIND THE SHUTTER, and the reason the section exists.
 *
 * The drawing the application produces, on its own sheet, set --sh-recess back
 * through the hole — all but on the facade's own plane, because it IS the facade
 * seen through a hole cut in it.
 *
 * THE SCALE IS NOT DECORATION AND IT IS NOT A CONSTANT. A plane pushed d away
 * under a perspective P renders (P + d) ÷ P smaller, so an unscaled sheet would
 * sit inside the opening with a rim of wall showing all round it. Written out of
 * --sh-recess-n and --sh-persp-n rather than as the 1.031 it used to be, so that
 * moving the recess cannot silently leave the compensation behind — which is a
 * failure that shows up as a hairline of facade round a drawing, at one
 * breakpoint, three edits later.
 *
 * IT FADES UP ACROSS THE CAMERA MOVE, and that is a correctness fix rather than
 * a flourish. A plane 46px behind the frame drifts sideways against it by
 * depth × tan(angle) — about 24px at the opening angle — so its lit edge slides
 * out past a guide rail that is only 16px wide and shows as a white fin down the
 * side of a shut shutter. Nothing is lost by hiding it while the camera is
 * turning, because the curtain is covering the opening for every frame of the
 * pan. The script writes the opacity; the resting value here is 1, so a reader
 * with no script gets the sheet.
 */
.sh-behind {
  position: absolute; inset: 0;
  transform:
    translateZ(calc(var(--sh-recess) * -1))
    scale(calc((var(--sh-persp-n) + var(--sh-recess-n)) / var(--sh-persp-n)));
  background: var(--sk-paper);
  display: grid; place-items: center;
  padding: 4% 5%;
  box-shadow: inset 0 0 70px rgba(35,31,32,.2);
}

/* The reveal: the four faces of the hole cut through the facade. A frame drawn
   with shadow rather than with four more elements — at this depth the inner
   faces are two or three pixels of dark and an inset shadow is
   indistinguishable from geometry, at a twenty-fifth of the cost. */
.sh-open::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  box-shadow:
    inset 0 16px 24px -8px rgba(0,0,0,.78),
    inset 0 -8px 16px -8px rgba(0,0,0,.5),
    inset 12px 0 20px -10px rgba(0,0,0,.6),
    inset -12px 0 20px -10px rgba(0,0,0,.6);
}

/* ------------------------------------------------------------- the drawing --
 * The sheet is the figure that used to have a whole section to itself. Its
 * conventions — the four weights, the perpendicular end ticks, `mm` on every
 * number, dashed grey-400 for the casetă nobody has data for — are the sketch
 * language in section 3 above, shared with the deck's card B.
 */
.sh-sheet { margin: 0; width: 100%; height: 100%; }
.sh-sheet .sk-dwg {
  display: block; width: 100%; height: 100%;
  /* Nothing in the drawing is a control, and a hero whose whole promise is
     "your finger scrolls the page normally" should not hand the hit-tester a
     drawing to consider on every touch. */
  pointer-events: none;
}

/*
 * THE TYPE IN THE SHEET IS SIZED FOR THIS OPENING AND NOT FOR THE COLUMN IT
 * USED TO LIVE IN, and it is the one thing that had to be re-derived when the
 * drawing moved.
 *
 * SVG text scales with the viewBox, so a font-size in user units renders at
 * `size × renderedWidth ÷ 470` actual pixels. In its old two-column section the
 * figure was 520px wide on a desk and 12u came out at 13.3px. Inside the opening
 * it is about 360px wide, where the same 12u is 9.2px — under the 11px floor
 * this drawing has always held itself to, on the only two numbers the page
 * prints. 15u puts it back at 11.5px, and the two narrower steps do the same
 * arithmetic against their own openings.
 *
 * Two classes deep, so these beat the breakpoint sizes in section 3 whatever
 * order the file ends up in.
 */
.sh-sheet .sk-num          { font-size: 15px; }
.sh-sheet .sk-num--unknown { font-size: 17px; }
.sh-sheet .sk-leadlabel    { font-size: 14px; }

/* ----------------------------------------------------------------- the clip
 * The opening's mouth. Everything above it is inside the casetă and must not be
 * drawn — which is exactly what a rolling shutter does with the part of itself
 * it has already wound up.
 */
.sh-clip { position: absolute; inset: 0; overflow: hidden; }

/*
 * THE LIGHT, and it belongs to the OPENING rather than to the curtain.
 *
 * A raking band across the face is most of what separates aluminium from grey
 * stripes. Put it on the curtain and it travels with the slats, which is a
 * shutter with a painted-on highlight; put it on the clip and the curtain moves
 * through a light that stays where it is, which is what actually happens.
 */
.sh-clip::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    linear-gradient(97deg,
      rgba(255,255,255,0)   0%,
      rgba(255,255,255,.05) 22%,
      rgba(255,255,255,.10) 37%,
      rgba(255,255,255,.02) 56%,
      rgba(0,0,0,.14)       82%,
      rgba(0,0,0,.30)      100%),
    linear-gradient(to bottom,
      rgba(0,0,0,.45) 0%, rgba(0,0,0,0) 12%,
      rgba(0,0,0,0) 86%, rgba(0,0,0,.2) 100%);
}

/* ---------------------------------------------------------------- the curtain
 * EMPTY IN THE MARKUP. landing.js appends the slats; an empty div covers
 * nothing, so with scripting off or motion reduced the opening simply stands
 * open. There is no class here to get wrong and no state that can be left stuck
 * closed over the drawing.
 *
 * ONE TRANSFORM ON ONE ELEMENT is the entire animation. The slats are laid out
 * by normal flow inside it and never move relative to each other, which is what
 * guarantees they stay identical — see the note at the head of this section.
 */
.sh-curtain {
  position: absolute; left: 0; right: 0; top: 0;
  display: flex; flex-direction: column;
  will-change: transform;
}

/* A SLAT.
 *
 * Seven stops down 40mm of extruded aluminium: the interlock groove at the top,
 * the crown catching the light just under it, a long fall through the body, and
 * the lip at the bottom picking up a little bounce. The crown at 13% rather
 * than at 50% is what makes the profile read as convex — a highlight in the
 * middle of a band reads as a cylinder lit from the front, which is a pipe, not
 * a slat.
 *
 * flex: none matters. Without it the last slats in a curtain slightly taller
 * than its container would be squeezed by flexbox — which is precisely the
 * "smaller at the bottom" failure this build exists to remove.
 */
.sh-slat {
  position: relative; flex: none;
  height: var(--sh-slat);
  background: linear-gradient(to bottom,
      #131011 0%,
      #2B2425 4%,
      #5E5352 13%,
      #473E3E 27%,
      #342D2D 52%,
      #262020 78%,
      #1A1616 93%,
      #0D0A0B 100%);
}

/* The punched vents. Real shutters carry them on a band of slats near the top
   of the opening so a closed room still breathes, and they are the detail that
   makes the object read as a product rather than as a gradient: light behind
   the curtain shows through as a dotted line. They ride up with everything else
   and are gone by the time the shutter is open. */
.sh-slat--vent::after {
  content: ""; position: absolute; left: 14%; right: 14%;
  top: 34%; height: 30%;
  background: repeating-linear-gradient(to right,
      rgba(255,246,232,.62) 0 2px, transparent 2px 9px);
  box-shadow: 0 0 7px rgba(255,240,214,.22);
}

/* THE BOTTOM RAIL — lama finală. 1.7 slats tall, flatter in section because it
   is an extrusion rather than a rolled profile, with the red rail along its
   bottom edge.

   RED AS A RAIL, which §1.1 sanctions by name: it is a fill on an edge, not
   twelve pixels of red text, and it does not mean anything is wrong. It is also
   the one moving red thing on the page, and it leads the whole reveal — the
   first thing that crosses the opening and the last thing to leave it. */
.sh-bar {
  position: relative; flex: none;
  height: calc(var(--sh-slat) * 1.7);
  background: linear-gradient(to bottom,
      #100D0E 0%,
      #4E4544 7%,
      #3B3333 32%,
      #2A2324 62%,
      #1A1617 88%,
      #100D0E 92%);
}
/* CLEAR OF THE SILL, NOT ON THE VERY EDGE. The sill laps up over the bottom of
   the curtain, so a stripe on the bar's last pixel is a stripe nobody sees while
   the shutter is shut — which is the one frame this page opens on. Sat just
   above the lap it reads as an accent line along the rail, which is what it is,
   and it is the first thing across the opening on the way up. */
.sh-bar::after {
  content: ""; position: absolute; left: 0; right: 0;
  bottom: calc(var(--sh-lap-rail) + 2px);
  height: max(2px, calc(var(--sh-slat) * .16));
  background: var(--brand-red);
  box-shadow: 0 0 10px rgba(237,28,36,.45);
}
/* The end lugs that hold the rail in the channels. Two pixels of nothing, and
   the object stops looking like a stack of divs. */
.sh-bar::before {
  content: ""; position: absolute; top: 14%; bottom: 22%; left: 0; right: 0;
  border-left:  3px solid rgba(0,0,0,.42);
  border-right: 3px solid rgba(0,0,0,.42);
}

/* ------------------------------------------------------------------ the glow
 * Light coming out of the opening once the curtain is off it, thrown onto the
 * room. Outside the 3D chain on purpose: an animated opacity inside a
 * preserve-3d subtree is a promoted layer that has to be re-composited against
 * everything around it, and this needs no depth of its own to do its job.
 */
.sh-glow {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(46% 40% at 70% 48%, rgba(255,248,236,.20), transparent 68%);
}

/* ------------------------------------------------------------------ the mark
 * The company's own mark, painted on the shutter the way it is painted on a
 * closed shop's — and it goes up WITH the curtain, because it is part of it.
 *
 * BUILT BY THE SCRIPT, inside the curtain, so it shares the curtain's fate: no
 * script, no slats, no mark, and nothing is missing because the mark is
 * decorative and the wordmark in the bar above names the company. It is
 * deliberately dim: it is paint on a dark surface in a dark room, not a badge.
 */
.sh-mark {
  position: absolute; left: 50%; top: 54%;
  width: 32%; height: auto;
  transform: translate(-50%, -50%);
  fill: rgba(255,255,255,.20);
  filter: drop-shadow(0 1px 0 rgba(0,0,0,.5));
  pointer-events: none;
}

/* ==========================================================================
   THE SHUTTER — the narrow layouts
   ========================================================================== */
/*
 * ONE OBJECT, THREE SIZES, and no second code path. The choreography, the slat
 * count and the divisor are identical at every width; only the opening's height
 * and the angle the camera starts at change, and the angle changes in the
 * script because a 28° swing that reads as a walk-round on a desk reads as a
 * lurch on a 390px screen.
 */

/* The words move above the object and both get the full width. A 42/58 split at
   860px leaves the copy at 340px, which breaks `Din dimensiuni,` across three
   lines, and the object at 460px, which is not a hero. */
@media (max-width: 980px) {
  .sh {
    --sh-open-h: min(50vh, 460px);
    /* The depths come down with the object, but NOT in proportion: they are
       what the eye reads as solidity, and an object two-thirds the size with
       two-thirds the depth just looks like the same flat thing further away. */
    --sh-front-n: 56;    --sh-front:  calc(var(--sh-front-n)  * 1px);
    --sh-rail-n:  30;    --sh-rail:   calc(var(--sh-rail-n)   * 1px);
    --sh-recess-n: 24;   --sh-recess: calc(var(--sh-recess-n) * 1px);
  }
  .sh-stage {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-5);
    padding-block: calc(76px + var(--s-4)) var(--s-5);
    align-content: center;
  }
  .sh-copy .lp-title { max-width: 16ch; }
  .sh-copy .lp-lede  { max-width: 54ch; }
  .sh-live { height: 260vh; }

  /* The leaders go, and nothing is lost: `acționare dreapta` and `ghidaje` are
     both named in the deck two sections down. Keeping them would put 9px type
     inside the opening, and the 134u of right margin they occupy reads as sheet
     margin, which is what it is on a real drawing. */
  .sh-sheet .sk-leadgrp { display: none; }
  .sh-sheet .sk-num          { font-size: 18px; }
  .sh-sheet .sk-num--unknown { font-size: 20px; }
}

/*
 * THE PHONE, and the one number here that is not taste.
 *
 * 100vh on a phone is the LARGE viewport — the height the page has once the URL
 * bar has scrolled away — so the bottom of a pinned 100vh screen is under the
 * browser chrome on arrival. Everything that must be read on landing therefore
 * lives in the top two-thirds: eyebrow, title, lede, both buttons. What sits in
 * the part that may be covered is the bottom of the object and its margin, and
 * the object is 46% of the height, so it is never the part that gets cut.
 *
 * svh/dvh would "fix" this and are not used anywhere in this section, on
 * purpose: dvh changes as the URL bar collapses, which would re-lay the whole
 * scene mid-flick under the reader's thumb. A stable layout that concedes a
 * strip at the bottom beats a correct one that jumps.
 */
@media (max-width: 720px) {
  .sh {
    --sh-open-h: min(40vh, 340px);
    --sh-ratio: .84;
    --sh-front-n: 40;    --sh-front:  calc(var(--sh-front-n)  * 1px);
    --sh-rail-n:  22;    --sh-rail:   calc(var(--sh-rail-n)   * 1px);
    --sh-recess-n: 18;   --sh-recess: calc(var(--sh-recess-n) * 1px);
  }
  .sh-stage {
    padding-inline: var(--s-4);
    padding-block: calc(68px + var(--s-3)) var(--s-3);
    gap: var(--s-4);
  }
  /* EVERY PIXEL RECLAIMED HERE GOES TO THE OBJECT. A pinned screen has a fixed
     budget and the copy has to be read before it: at 390×844 the words take
     about 400px, which leaves 340 for the shutter and nothing for slack. These
     four trims are worth ~36px between them, which is a tenth of the opening. */
  .sh-copy .lp-cta { margin-top: var(--s-4); }
  .sh-copy .lp-cta .btn { min-height: 46px; }
  .sh-copy .lp-honest { margin-top: var(--s-3); }
  .sh-cue { margin-top: var(--s-3); }
  .sh-live { height: 240vh; }

  /* 24u renders at 11.7px against an opening about 230px wide. The drawing has
     never let a figure fall below 11px and it is not going to start inside the
     hero. */
  .sh-sheet .sk-num          { font-size: 24px; }
  .sh-sheet .sk-num--unknown { font-size: 26px; }
}

/* Short and wide — a laptop at 1440×720, or a phone on its side. The object is
   driven by viewport height, so it shrinks correctly on its own; what does not
   is the two-column split, which at 620px tall leaves a hero of two short
   columns. Below that the copy takes the width and the object takes what is
   left, which is the same arrangement as the phone. */
@media (min-width: 981px) and (max-height: 660px) {
  .sh { --sh-open-h: 68vh; }
  .sh-stage { padding-block: calc(76px + var(--s-4)) var(--s-4); gap: var(--s-5); }
}

/*
 * WHEN THERE IS NOT ENOUGH SCREEN TO PIN, DO NOT PIN.
 *
 * A pinned hero has a fixed budget: the words have to be read before the object,
 * and on a 360×640 phone they take two-thirds of the viewport on their own,
 * which leaves about 150px for a shutter. That is not a hero, it is a thumbnail
 * — and it would be a thumbnail that three screens of scrolling were spent on.
 *
 * Below this the section becomes an ordinary block: the copy, then the object,
 * then the page carries on. The choreography is unchanged and still runs; the
 * script reads the pin's COMPUTED POSITION rather than guessing from a width, so
 * it maps the same camera and the same curtain against the window in which the
 * object crosses the screen instead of against a sticky range that is not there.
 *
 * 780px of height and 980 of width, together: this is the short-and-narrow case
 * — small phones and phones on their side — and not a tall narrow one, where the
 * pin fits perfectly well.
 */
@media (max-width: 980px) and (max-height: 780px) {
  .sh-live { height: auto; }
  .sh-live .sh-pin,
  .sh-pin { position: relative; height: auto; min-height: 0; }
  .sh-room { padding-block: var(--s-6); }
  .sh { --sh-open-h: 330px; }
  .sh-stage { padding-block: calc(68px + var(--s-4)) var(--s-4); }
}

/*
 * REDUCED MOTION. The pin is never applied — .sh-live is added by the script
 * and the script does not add it when motion is refused — so this block only
 * has to stop the cue's shuttle and the pinned height in the case where the
 * setting is flipped mid-session with the class already on.
 *
 * There is nothing else to switch off. The still state and the finished state
 * are the same picture: no curtain is ever built, so the reader gets the
 * opening standing open with the drawing in it, which is the image the whole
 * animation exists to arrive at.
 */
@media (prefers-reduced-motion: reduce) {
  .sh-live { height: auto; }
  .sh-live .sh-pin { position: relative; height: auto; min-height: 100vh; }
  .sh-cue { display: none; }
}
