/* ==========================================================================
   The plan engine. Loaded only by the listing pages that carry a plan.

   NOT opportunities.css. That file is loaded by opportunities.html too and
   already owns .chip — which fit-switcher.js binds and opportunities.html then
   re-declares in its own <style>. A third definition would fight both.

   NAMESPACE IS `pe-`. `pl-` is taken: en-02.html and en-03.html define
   .pl-set / .pl-f / .pl-svg / .pl-fl / .pl-o / .pl-g in page <style> blocks,
   for the nine-type strip this section is meant to replace. Overlapping them
   would break the thing being replaced while replacing it.

   No :root, no @font-face, no new colour, no third radius. Everything comes
   from style.css's tokens, so a change there moves this with it.
   ========================================================================== */

.pe { border-top: 1px solid var(--line-strong);
  padding: clamp(2.4rem, 5vw, 3.6rem) var(--gutter); }

.pe-head { max-width: 44rem; }
.pe-head h2 { font-family: var(--display); font-weight: 500; letter-spacing: -.02em;
  font-size: clamp(1.9rem, 4.6vw, 3.4rem); line-height: 1.06; margin-top: .8rem; }

/* ---------- the figures row ---------- */
.pe-figs { display: flex; flex-wrap: wrap; gap: 1rem 2.2rem;
  margin: clamp(1.4rem, 3vh, 2rem) 0 0; padding-bottom: 1.2rem;
  border-bottom: 1px solid var(--line); }
.pe-figs div { min-width: 5rem; }
.pe-figs dt { font-size: .56rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--mist); order: 2; margin-top: .3rem; }
.pe-figs dd { margin: 0; order: 1; font-family: var(--display); font-weight: 500;
  font-size: clamp(1.3rem, 2.6vw, 1.7rem); line-height: 1.1; letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; }
.pe-figs div { display: flex; flex-direction: column; }
.pe-figs .pe-w { color: var(--water); }

/* ---------- who lives here ---------- */
.pe-cast { padding: 1.4rem 0; border-bottom: 1px solid var(--line); }
.pe-cast-l { font-size: .62rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--mist); }
.pe-chips { display: flex; flex-wrap: wrap; gap: .5rem; margin: .9rem 0 1.4rem; }
/* the site's pill: 999px, inverting to porcelain when pressed. min-height is the
   one addition — the shipped padding lands a chip at 41px, three under the tap
   floor, on a control this section is built around. */
.pe-chip { min-height: 44px; border: 1px solid var(--line-strong); border-radius: 999px;
  padding: .62rem 1.25rem; color: var(--grey); background: rgba(11, 14, 15, .4);
  font-family: var(--sans); font-size: .72rem; letter-spacing: .14em; text-transform: uppercase;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: color .3s var(--ease), border-color .3s var(--ease), background-color .3s var(--ease); }
.pe-chip:hover { color: var(--porcelain); border-color: var(--porcelain); }
.pe-chip[aria-pressed="true"] { background: var(--water); border-color: var(--water);
  color: var(--ink); }
.pe-chip:focus-visible { outline: 1px solid var(--water); outline-offset: 3px; }

.pe-verdicts { list-style: none; margin: .8rem 0 0; padding: 0; display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1.2rem; }
@media (max-width: 640px) { .pe-verdicts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pe-verdicts b { display: block; font-family: var(--display); font-weight: 500;
  font-size: clamp(1.1rem, 2.2vw, 1.3rem); line-height: 1.1; }
.pe-verdicts i { display: block; font-style: normal; font-size: .56rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--mist); margin: .25rem 0 .5rem; }
.pe-verdicts span { display: block; font-size: .88rem; line-height: 1.45; color: var(--porcelain); }
.pe-verdicts span.pe-no { color: var(--mist); }
html.anim .pe-verdicts span { transition: opacity .4s var(--ease), color .4s var(--ease); }

/* A PRICE CARRIES ITS OWN DATE. The band row is the only place on these pages
   a reader meets money, and the sentence saying which undated list it came off
   used to live in whatever hand-written section the page happened to have. It
   now renders here, from the manifest, on every listing that publishes prices —
   and plan_schema.check_price_note refuses to let money ship without it. */
.pe-band-src { margin: 1rem 0 0; font-size: .74rem; line-height: 1.6; color: var(--mist);
  max-width: 62ch; }

/* ---------- the type picker ----------
   Its chips carry each type's net area, so the row reads 34,78 · 46,28 · 61,29
   … 144,57 and states the range in passing. That was the argument the nine
   footprints were making before they were cut, made in words that fit. */
.pe-pick { display: flex; flex-wrap: wrap; gap: .5rem; padding: 1.4rem 0;
  border-bottom: 1px solid var(--line); }
.pe-type { min-height: 44px; border: 1px solid var(--line-strong); border-radius: 2px;
  padding: .5rem .85rem; background: rgba(11, 14, 15, .4); color: var(--grey);
  font-family: var(--sans); cursor: pointer; text-align: left;
  -webkit-tap-highlight-color: transparent; font-size: .72rem; letter-spacing: .14em;
  text-transform: uppercase; display: flex; flex-direction: column; gap: .2rem; }
html.anim .pe-type { transition: color .3s var(--ease), border-color .3s var(--ease),
  background-color .3s var(--ease); }
.pe-type i { font-style: normal; font-size: .62rem; letter-spacing: .06em;
  text-transform: none; color: var(--mist); font-variant-numeric: tabular-nums; }
.pe-type:hover { color: var(--porcelain); border-color: var(--porcelain); }
.pe-type[aria-pressed="true"] { background: var(--porcelain); color: var(--ink);
  border-color: var(--porcelain); }
.pe-type[aria-pressed="true"] i { color: rgba(11, 14, 15, .62); }
.pe-type:focus-visible { outline: 1px solid var(--water); outline-offset: 3px; }
/* JS hides the bodies you are not looking at. With JS dead every one stays
   visible and the section is the whole set, stacked. */
.pe-body[hidden] { display: none; }
.pe-body + .pe-body { border-top: 1px solid var(--line); margin-top: 1.4rem;
  padding-top: 1.4rem; }
.pe-body[hidden] + .pe-body { border-top: 0; margin-top: 0; padding-top: 0; }

/* ---------- the plan ---------- */
.pe-grid { display: grid; gap: clamp(1.6rem, 3vw, 2.6rem);
  margin-top: clamp(1.6rem, 4vh, 2.4rem); align-items: start; }
@media (min-width: 1024px) { .pe-grid { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); } }
.pe-fig { margin: 0; min-width: 0; }
.pe-svg { display: block; width: 100%; height: auto; overflow: visible;
  color: var(--porcelain); }
/* A PLAN IS AS TALL AS ITS SHAPE, AND ONE SHAPE IS ALMOST SQUARE. Scaled to the
   column width, 2+1 A came out 759px tall against a 438px schedule — half again
   as tall as the next worst type, and it made the picker jump every time you
   moved between types. Capping the height lets a square plan sit at the same
   scale as a long one instead of being enlarged to fill a width it does not
   want. It bites only where the drawing is tall: the wide types are already
   under it and do not move. */
@media (min-width: 1024px) {
  /* ONE SCALE FOR EVERY TYPE. Stretched to the column, a 34 m2 studio and a
     144 m2 four-bed came out the same width — and the "2 M" bar drawn under
     each of them was a different length on every plan, which is the one thing
     a scale bar must never be. --pe-vw is the drawing's own width in px at
     30 px per metre, so this sizes each plan from its metres. 1.12 is the
     largest factor that still fits the tallest type (4+1 A) inside the cap.
     A 70 m2 flat now looks smaller than a 144 m2 one, because it is.

     The cap is what stops a long plan running off the page rather than what
     sizes a normal one: EN-03's tallest type comes out 498px at this scale and
     never meets it. EN-02's 6+1 is thirty metres from the living room to the
     master and would be 1114px, so it is the only drawing the cap actually
     bites on, and it still lands at nine tenths of everything else. */
  /* LEFT, NOT CENTRED. At one scale the drawings differ in width by nearly
     2x — 323px for the 1+1 A against 630px for the 3+1 B — and centring each
     one left the small types floating in the middle of a column sized for the
     largest, using 41% of it. Pinned to a common origin they grow rightward
     from the same corner as you move through the picker, which is how a set of
     type plans is presented on a sheet, and it is what makes the size
     difference readable rather than merely true. */
  .pe-svg { width: calc(var(--pe-vw, 600) * 1.12px); max-width: 100%;
            max-height: min(1000px, 88vh); margin-inline: 0; }
}

.pe-fl { fill: var(--porcelain); fill-opacity: .05; }
html.anim .pe-fl { transition: fill-opacity .4s var(--ease), fill .4s var(--ease); }
.pe-room { cursor: pointer; outline: none; }
.pe-room:hover .pe-fl, .pe-room.is-on .pe-fl { fill: var(--water); fill-opacity: .24; }
.pe-room:focus-visible .pe-fl { stroke: var(--water); stroke-width: 1.5; }

.pe-furn { fill: var(--porcelain); fill-opacity: .04; stroke: var(--porcelain);
  stroke-opacity: .42; stroke-width: 1; pointer-events: none; }
.pe-furn path { fill: none; }
/* FITTED JOINERY READS HEAVIER THAN THE SOFA IN FRONT OF IT. A worktop is built
   in; a sideboard is not; and on a room the developer names "salon + kitchen"
   that difference is the only thing telling a reader which end cooks. The sheet
   makes it with stone against wood — this drawing has one ink, so it makes it
   with weight. */
.pe-fit { fill-opacity: .11; stroke-opacity: .66; }
/* the marks themselves: two-bowl sink, four-ring hob, fridge door. Never
   filled — a filled ring stops reading as a burner and starts reading as a
   stool. */
/* OUTDOOR FURNITURE KNOCKS THE HATCH OUT. The balcony hatch means "outside";
   a chair drawn over it read as a chair behind bars. Its outline is painted on
   ink so the hatching stops at its edge. */
.pe-out rect:not(.pe-ap):not(.pe-soft), .pe-out circle:not(.pe-ap):not(.pe-soft),
.pe-out .pe-pot { fill: var(--ink); fill-opacity: 1; }
.pe-soft { fill: none; stroke: var(--porcelain); stroke-opacity: .28; stroke-width: 1; }
.pe-ap { fill: none; stroke: var(--porcelain); stroke-opacity: .58; stroke-width: 1; }

/* A CHANGE OF USE IS NOT A WALL, so it is dashed and it is quiet, and it is
   drawn only where the zone actually parts from the rest of its room — never
   along an edge the shell has already drawn two pixels thick. */
.pe-zone-e { fill: none; stroke: var(--porcelain); stroke-opacity: .38;
  stroke-width: 1; stroke-dasharray: 4 4; pointer-events: none; }
/* the zone's floor, a shade off the room's. Deliberately below the hover tint,
   which is .24 — a reader hovering the room must still see one room. */
.pe-zone-f { fill: var(--porcelain); fill-opacity: .055; }
/* structure — built into the sheet, absent from the schedule. Hatched and
   unlabelled: it has no area to print, and without this it reads as a room
   somebody forgot to name. */
.pe-struct { color: var(--mist); pointer-events: none; }
.pe-struct-o { fill: none; stroke: var(--porcelain); stroke-opacity: .22; stroke-width: 1; }

.pe-walls { fill: none; stroke: rgba(243, 241, 234, .34); stroke-width: 1; pointer-events: none; }

/* EVERY PLAN GETS THE SAME LINE WEIGHT, whatever size the apartment is.
   ------------------------------------------------------------------------
   Stroke widths here are SVG user units, so they scale with the drawing — and
   the drawing scales to fit the column, so a BIG apartment is drawn SMALL and
   its walls come out thin. Measured at 390px on EN-01:

       1+1 D1        scale 1.257    wall 1.26 px
       6+1 duplex    scale 0.835    wall 0.83 px

   A third off the line weight, and it falls on exactly the wrong plan: the
   thirty-room duplex is the one that needs the most definition and was getting
   the least, at sub-pixel width where a phone anti-aliases it into grey.

   vector-effect: non-scaling-stroke makes stroke-width mean screen pixels
   instead of drawing units, so all twenty-two plans now draw at one weight and
   the numbers above become 1.00 px on both. The ratios between the classes are
   unchanged — an exterior wall is still twice an internal one, and a door gap
   is still wider than the wall it erases, which is what makes it erase it. */
.pe-soft, .pe-ap, .pe-struct-o, .pe-walls, .pe-ext, .pe-gap, .pe-door,
.pe-glass, .pe-fl, .pe-zone-e, .pe-zone-f, .pe-fit, .pe-out {
  vector-effect: non-scaling-stroke;
}

.pe-ext { fill: none; stroke: var(--porcelain); stroke-opacity: .8; stroke-width: 2;
  stroke-linejoin: miter; pointer-events: none; }
/* the door gaps are painted in the page ground, so they read as holes in the
   wall rather than as another line */
.pe-gap { stroke: var(--ink); stroke-width: 3.2; pointer-events: none; }
.pe-door { fill: none; stroke: var(--porcelain); stroke-opacity: .6; stroke-width: 1;
  pointer-events: none; }
.pe-glass { stroke: var(--water); stroke-width: 2.5; stroke-opacity: .95; pointer-events: none; }
.pe-scalebar path { fill: none; stroke: var(--porcelain); stroke-opacity: .6; }

/* labels sit over the drawing, so they carry the ground as a stroke rather than
   a plate — paint-order keeps the letterform crisp inside it */
.pe-lbl, .pe-num, .pe-key-t { paint-order: stroke; stroke: var(--ink); stroke-width: 3px;
  stroke-linejoin: round; pointer-events: none; }
.pe-lbl { font-size: 10px; letter-spacing: .16em; text-transform: uppercase; fill: var(--porcelain); }
.pe-num { font-size: 11px; fill: var(--porcelain); font-variant-numeric: tabular-nums; }
.pe-dim { fill: var(--mist); stroke: none; }
/* ONE LAYER, NOT TWO. The plan used to print every room's name and area over
   the furniture at desktop and swap to the sheet's keys below 600px. That was
   the same fact twice — the schedule beside the drawing already carries key,
   name and area for every room — and three lines of type stacked on a sofa is
   what it cost. The developer's own sheet numbers the rooms and puts the names
   in a legend; so does this now, at every width. */
.pe-key-b { fill: var(--ink); fill-opacity: .82; stroke: var(--porcelain); stroke-opacity: .5; }
.pe-key-t { font-size: 14px; fill: var(--porcelain); font-variant-numeric: tabular-nums; }


/* ---------- two storeys of one apartment ----------
   A duplex is one flat on two floors, and the sheet draws them side by side at
   the same scale so you can read the stair from one to the other. So does this.
   Below 760 they stack, because two 12-metre floors side by side on a phone are
   two things too small to read. */
.pe-levels { display: grid; gap: clamp(1.2rem, 3vw, 2.2rem); align-items: start; }
@media (min-width: 760px) { .pe-levels { grid-template-columns: repeat(2, auto);
  justify-content: start; } }
.pe-level { min-width: 0; }
/* the floor's name sits UNDER its drawing, where the scale bar's "2 M" sits.
   It is a caption, not a note on the plan — the plans themselves stay wordless. */
.pe-level-n { margin: .5rem 0 0; font-size: .62rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--mist); }
.pe-level-n i { font-style: normal; color: var(--grey); margin-left: .5rem; }
/* and the schedule splits the same way the sheet splits it */
.pe-lv-h { padding: .9rem .5rem .3rem; font-size: .62rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--mist); border-top: 1px solid var(--line);
  margin-top: .4rem; list-style: none; }
.pe-list li:first-child.pe-lv-h { border-top: 0; margin-top: 0; padding-top: 0; }

/* ---------- the life line ---------- */
.pe-life { margin: 1.4rem 0 0; padding: 1rem 0 0; border-top: 1px solid var(--line);
  font-size: 1rem; line-height: 1.55; color: var(--porcelain); min-height: 5.2em;
  max-width: 60ch; }
.pe-life-k { display: block; font-size: .62rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--mist); margin-bottom: .45rem; }
.pe-life-t { display: block; }

/* ---------- the schedule ---------- */
.pe-side { min-width: 0; }
.pe-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line-strong); }
.pe-list li { border-bottom: 1px solid var(--line); }
.pe-list button { display: grid; grid-template-columns: 1.3rem 1fr auto; align-items: baseline;
  gap: .9rem; width: 100%; padding: .55rem .5rem; border: 0; border-radius: 2px;
  background: none; cursor: pointer; font: inherit; color: var(--porcelain); text-align: left;
  -webkit-tap-highlight-color: transparent; }
html.anim .pe-list button { transition: background-color .4s var(--ease); }
.pe-list button:hover, .pe-list button.is-on { background: rgba(243, 241, 234, .06); }
.pe-list button:focus-visible { outline: 1px solid var(--water); outline-offset: 2px; }
.pe-list button.is-on .pe-n { color: var(--water); }
.pe-n { font-size: .62rem; letter-spacing: .18em; color: var(--mist); font-variant-numeric: tabular-nums; }
.pe-t { font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; }
.pe-m { font-size: .9rem; font-variant-numeric: tabular-nums; }
.pe-r { font-style: normal; color: var(--water); margin-left: .5rem; letter-spacing: .14em; }
.pe-r:empty { display: none; }
/* where the kitchen is, said in words — because below 600px the plan drops
   every name for the sheet's keys, and the schedule is all a phone has left */
.pe-zn { display: block; margin-top: .3rem; font-style: normal; font-size: .62rem;
  letter-spacing: .1em; text-transform: none; color: var(--grey); }
/* a room whose drawn size and printed figure disagree says so here, in the
   schedule, beside the number it disagrees with */
.pe-note-i { color: var(--mist); }
.pe-note { margin: .9rem 0 0; font-size: .78rem; line-height: 1.5; color: var(--grey);
  max-width: 46ch; }

.pe-tot { margin: .9rem 0 0; padding-top: .9rem; border-top: 1px solid var(--line-strong);
  display: grid; gap: .4rem; }
.pe-tot div { display: flex; justify-content: space-between; gap: 1rem;
  font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; }
.pe-tot dt { color: var(--porcelain); }
.pe-tot div:nth-child(2) dt, .pe-tot div:nth-child(2) dd { color: var(--grey); }
.pe-tot dd { margin: 0; font-size: .9rem; font-variant-numeric: tabular-nums;
  letter-spacing: 0; text-transform: none; }

.pe-price { margin: 1.2rem 0 0; font-size: .9rem; line-height: 1.55; color: var(--grey); }
.pe-price b { color: var(--porcelain); font-weight: 500; }
.pe-gross { margin: 1.4rem 0 0; padding-top: 1.4rem; border-top: 1px solid var(--line);
  font-size: .9rem; line-height: 1.55; color: var(--grey); max-width: 58ch; }
.pe-gross b { color: var(--porcelain); font-weight: 500; }

/* ---------- motion ----------
   Scoped to this section, and it restores rather than merely disabling: the
   prototype's blanket `*{animation:none}` under reduced motion would have left
   anything mid-fade stuck wherever it was. Nothing here hides content — the
   plan, every label and the whole schedule are in the DOM at first paint and
   readable with JS dead. */
/* THE DEFAULT IS DRAWN. Written the other way round — hidden until revealed —
   the outline sat at stroke-dashoffset 2200 forever on any page that does not
   load the reveal observer, which is exactly how this repo once shipped
   elements stuck at opacity 0. Only an animating page that has not yet
   revealed the figure collapses it. --pe-len is the outline's own measured
   length, emitted per type by the renderer. */
html.anim .pe-fig[data-reveal]:not(.is-in) .pe-ext {
  stroke-dasharray: var(--pe-len, 2000); stroke-dashoffset: var(--pe-len, 2000); }
html.anim .pe-fig[data-reveal] .pe-ext { transition: stroke-dashoffset 1.5s var(--ease); }
html.js-failed .pe-ext { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }

@media (prefers-reduced-motion: reduce) {
  html.anim .pe-fig[data-reveal]:not(.is-in) .pe-ext,
  html.anim .pe-fig[data-reveal] .pe-ext { stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important; transition: none !important; }
  .pe-fl, .pe-list button, .pe-chip, .pe-verdicts span { transition: none !important; }
}
