/* Shared explorer layer. pf-layout, sidebar, tree, main, gallery, and cards
   serve inventory, construction projects, tenancies, and metadata. Mosaic and
   lightbox serve media blocks. The historical pf prefix remains a tested public
   hook; renaming it would add churn without user value. */
/* Mobile first: below lg, place the tree above the map instead of squeezing
   content beside a fixed-width sidebar. */
.pf-layout { display:grid; grid-template-columns:minmax(0,1fr); gap:var(--sp-5); align-items:start; margin-top:var(--sp-4); }
/* In a stacked detail layout, show the selected entity before its long navigation rail. */
.pf-layout--detail .pf-main { order:-1; }
/* ONE inset, on the panel. The head used to add its own inline padding on top of
   the panel's, so its title started 16px from the edge while the row dividers —
   which span the panel's content box — started at 8px, and the header floated
   half a step right of the list it belongs to. The inset now lives here alone:
   head, rows and dividers share one leading edge on every explorer that uses
   this panel (property inventory, construction projects, tenancies, workspace,
   shop, metadata catalogue, process documentation). */
.pf-sidebar { position:static; max-height:22rem; overflow:auto; padding:var(--sp-4);
  background:var(--surface-bg); border:var(--stroke-1) solid var(--surface-border);
  border-radius:var(--surface-radius); box-shadow:var(--surface-elevation); }
@media (min-width:1024px){
  .pf-layout  { grid-template-columns:minmax(15rem,18rem) minmax(0,1fr); }
  .pf-layout--detail .pf-main { order:initial; }
  .pf-sidebar { position:sticky; top:var(--sp-4); max-height:calc(100vh - var(--sp-28)); }
}
/* No inline padding of its own — see .pf-sidebar. Only the gap to the first row
   stays, so the title is not glued to it. */
.pf-sidebar__head { display:flex; align-items:center; justify-content:space-between; gap:var(--sp-2); padding:0 0 var(--sp-2); }
.pf-sidebar__title { font-size:var(--text-lg); margin:0; }

/* Structure tree. One reusable component behind five explorers (property
   inventory, construction projects, tenancies, workspace, Plan-Editor) plus three
   surfaces that hand-roll the same markup as a navigation list (metadata
   catalogue, process documentation, shop mobile navigation).

   The design is variant H2 of docs/wireframes/260810 - Standortbaum.html, chosen
   after comparing ten treatments against the real six-level hierarchy:

     · INDENTATION carries affiliation, one step per level. It is applied as row
       padding rather than as a margin on the list, so every row spans the full
       column and its divider therefore reaches both edges. Indenting the list
       instead made each level's divider start further right, and the ragged left
       ends read as broken rather than as hierarchy.
     · DIVIDERS separate rows. They are what makes the column scannable — the eye
       gets a band per row and can compare counts down the column. They are drawn
       as a LEADING rule (border-top). Between two rows the line lands in the
       same place either way, but the column then ends without one: a trailing
       border under the last row read as a stray line hanging beneath the tree
       rather than as a divider between two things. The mirror case cannot arise
       from nesting — a parent's row always precedes its children, so only the
       first TOP-LEVEL row can ever come first, and that one is cleared below.
     · A VERTICAL GUIDE appears only along the SELECTED branch, where the question
       «which parent does this belong to» is actually being asked. Everywhere else
       indentation and dividers carry the row on their own, so the column stays
       quiet. The guide sits on the axis of the parent's disclosure chevron, which
       is what makes it read as intended rather than approximate.
     · The guide passes BEHIND the selected row: that row is where the trace
       arrives, not something it crosses.

   Depth is expressed through nested .pf-tree__children selectors rather than
   aria-level, because the three hand-rolled surfaces nest the same way but carry
   no level attributes. */
.pf-tree {
  --tree-indent:1rem;            /* one level */
  --tree-gutter:var(--sp-2);     /* space before the first level */
  --tree-axis:.5rem;             /* half a chevron: the guide's axis */
  --tree-divider:var(--color-secondary-200);
  list-style:none; margin:0; padding:0;
}
.pf-tree__children { position:relative; list-style:none; margin:0; padding:0; }
:is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { position:relative; display:flex; align-items:center; gap:var(--sp-2); width:100%;
  padding:var(--sp-1-5) var(--sp-2); border:0; border-top:var(--stroke-1) solid var(--tree-divider);
  border-radius:0; background:none; font:inherit; font-size:var(--fs-sm);
  color:var(--color-text); text-align:left; text-decoration:none; cursor:pointer; }
/* The leading rule is kept but made invisible, so the first row stays exactly as
   tall as every other one. `.is-first-row` is set by syncTreeCounts
   (js/ui/spatial-tree.js): filtering can hide the first top-level branch, and
   the row that then leads the column must not draw a line under the head either.
   The static, unfiltered surfaces (metadata catalogue, process documentation,
   shop navigation) carry no such class and rely on :first-child alone. */
.pf-tree > .pf-tree__item:first-child > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub),
.pf-tree :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub).is-first-row { border-top-color:transparent; }
.pf-tree .pf-tree__children > .pf-tree__item > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { padding-left:calc(var(--tree-gutter) + 1 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children > .pf-tree__item > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { padding-left:calc(var(--tree-gutter) + 2 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children > .pf-tree__item > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { padding-left:calc(var(--tree-gutter) + 3 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children > .pf-tree__item > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { padding-left:calc(var(--tree-gutter) + 4 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children > .pf-tree__item > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { padding-left:calc(var(--tree-gutter) + 5 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children > .pf-tree__item > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { padding-left:calc(var(--tree-gutter) + 6 * var(--tree-indent)); }
:is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub):hover { background:var(--color-secondary-50); color:var(--color-text); text-decoration:none; }

/* Two-tone selection: the ancestors of the selection are light, the selection
   itself dark plus a primary edge bar, so selection never rests on colour alone.
   z-index lifts it above the guide. */
.pf-tree__node.is-path, .pf-tree__leaf.is-path { background:var(--color-gray-100); }

/* A row that navigates AND folds. Selecting a record and unfolding it are two
   different intentions: a data table carries up to 75 fields, and dropping them
   into the sidebar on every click buries the tree. So the row splits into two
   controls — the link picks the record, the chevron opens it. Every other row
   keeps doing both at once, because there the branch below IS what was asked for.
   The chevron is pulled back into the gutter so the label stays flush with the
   plain leaves at the same depth. */
/* The block padding moves to the link so the whole row height is clickable. The
   extra `.pf-tree` raises specificity above the coarse-pointer rule below, which
   would otherwise re-pad the split and double its height. */
.pf-tree .pf-tree__split { padding-block:0; padding-right:0; cursor:default; }
.pf-tree__fold { flex:none; align-self:stretch; width:var(--tree-indent);
  margin-left:calc(-1 * var(--tree-indent)); display:inline-flex; align-items:center;
  justify-content:center; padding:0; border:0; background:none;
  color:var(--color-text-light); cursor:pointer; }
.pf-tree__fold:hover { color:var(--color-text); }
.pf-tree__fold[aria-expanded="true"] .pf-tree__chev { transform:rotate(90deg); }
.pf-tree__fold:focus-visible { outline:var(--stroke-2) solid var(--color-focus-ring);
  outline-offset:calc(-1 * var(--stroke-2)); }
/* The link fills the rest of the row so the whole label is a target, but carries
   no chrome of its own — the split around it already drew the row. */
.pf-tree__go { flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:var(--sp-2);
  padding:var(--sp-1-5) var(--sp-2) var(--sp-1-5) 0; color:inherit; text-decoration:none; }
.pf-tree__go:hover { color:inherit; text-decoration:none; }
.pf-tree__go:focus-visible { outline:var(--stroke-2) solid var(--color-focus-ring);
  outline-offset:calc(-1 * var(--stroke-2)); }
@media (pointer:coarse) {
  .pf-tree__fold { width:var(--target-min); margin-left:calc(-1 * var(--target-min)); }
  .pf-tree__go { min-height:var(--target-min); padding-block:var(--sp-2-5); }
}
:is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub).is-active { z-index:var(--z-local-control); background:var(--color-secondary-100);
  color:var(--color-text-900); font-weight:var(--fw-bold); box-shadow:var(--state-bar-primary); }

/* The guide, drawn only on a list that holds the selection or its path. It stops
   half a row short of the bottom so it ends at the last child rather than
   running past it. */
.pf-tree__children:has(.is-active, .is-path)::before { content:""; position:absolute; top:0; z-index:var(--z-local-content);
  width:var(--stroke-1); height:calc(100% - var(--target-min) / 2);
  background:var(--color-secondary-100); }
/* The innermost list — the one that actually holds the selected row — carries the
   accent, so the trace has exactly one visible end. */
.pf-tree__children:has(> .pf-tree__item > .is-active)::before {
  width:var(--stroke-2); background:var(--color-primary-600); }
.pf-tree .pf-tree__children::before { left:calc(var(--tree-gutter) + var(--tree-axis)); }
.pf-tree .pf-tree__children .pf-tree__children::before { left:calc(var(--tree-gutter) + var(--tree-axis) + 1 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children::before { left:calc(var(--tree-gutter) + var(--tree-axis) + 2 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children::before { left:calc(var(--tree-gutter) + var(--tree-axis) + 3 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children::before { left:calc(var(--tree-gutter) + var(--tree-axis) + 4 * var(--tree-indent)); }
.pf-tree .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children .pf-tree__children::before { left:calc(var(--tree-gutter) + var(--tree-axis) + 5 * var(--tree-indent)); }

.pf-tree__chev { flex:none; transition:transform var(--duration-fast); color:var(--color-text-light); }
/* Placeholder for a row with nothing to open (shop categories). It reserves the
   glyph's width so every label in the column starts at the same x, and it is the
   same box the icon would occupy — .icon's own size, not a guessed one. */
.pf-tree__chev--empty { display:inline-block; width:var(--sp-4); height:var(--sp-4); }
.pf-tree__node[aria-expanded="true"] > .pf-tree__chev,
.pf-tree__leaf[aria-expanded="true"] > .pf-tree__chev { transform:rotate(90deg); }
.pf-tree__ico { flex:none; color:var(--color-text-muted); }
:is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub).is-active .pf-tree__ico { color:var(--color-text-800); }
/* Guarantee 44 px targets on touch devices while preserving desktop density. */
@media (max-width:1023.98px), (hover:none){
  :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { min-height:var(--target-min); padding-block:var(--sp-2-5); }
}
/* The key column: monospace, tabular, measured in `ch` so the segments of one
   level line up exactly and the labels all start at the same x. A width in rem
   left slack that read as a gap between the key and the name. */
/* Treat the ordering code as part of the name; retain only its alignment width. */
.pf-tree__id { flex:none; min-width:2ch; }
:is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub).is-active .pf-tree__id { color:var(--color-text-700); }
.pf-tree__label { flex:0 1 auto; min-width:4ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The count reads as «(7)». The parentheses are presentation, added here rather
   than in the markup, so the element's text stays the bare number that
   scripts/check-tree.mjs and the app suites parse. */
.pf-tree__n { flex:none; margin-left:var(--sp-1); font-size:var(--fs-xs);
  color:var(--color-text-light); font-variant-numeric:tabular-nums; }
.pf-tree__n::before { content:"("; }
.pf-tree__n::after { content:")"; }
:is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub).is-active .pf-tree__n { color:var(--color-text-700); }
:is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub):focus-visible { outline:var(--stroke-2) solid var(--color-focus-ring); outline-offset:calc(-1 * var(--stroke-2)); }

/* Plain variant for surfaces without meaningful entity icons. Indentation now
   comes from the shared depth rules, so the variant only has to close the gap
   the missing icon column leaves. */
.pf-tree--plain :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) { padding-left:calc(var(--tree-gutter) + var(--sp-4)); }
.pf-tree--plain .pf-tree__children > .pf-tree__item > :is(.pf-tree__node,.pf-tree__leaf,.pf-tree__sub) {
  padding-left:calc(var(--tree-gutter) + var(--sp-4) + var(--tree-indent)); }

.pf-main { min-width:0; }

/* Portal-native intranet shop using the CD shopping vocabulary where applicable. */
.shop-head { max-width:60rem; }
.shop-layout { margin-top:var(--sp-6); }
/* The category tree STACKS above the results below 1024, exactly as every other
   explorer does (.pf-layout above is a single column there, sidebar first in the
   DOM). It used to be hidden instead and duplicated into the filter panel, which
   cost a second copy of the same tree in the page — two `aria-current` rows for
   one selection — and left a shop without visible categories on the screens
   where browsing by category matters most (user decision, 2026-08-12).
   The taller cap is the only shop-specific part: this tree is a deep product
   taxonomy, not a handful of branches. */
.shop-layout .pf-sidebar { max-height:28rem; }
@media (min-width:1024px){
  .shop-layout .pf-sidebar { max-height:calc(100vh - var(--sp-32)); }
}
/* CD's `gap--top` on the results grid (search.postcss:196-201) is the space
   between the result header's rule and the first card, and it is right wherever
   the grid IS the thing that follows that rule — every other catalogue in the
   portal. The shop puts the grid in a column instead, beside the categories, and
   `.shop-layout` above already carries the separation for BOTH columns. Left in,
   the padding pushed the cards down while the category column stayed at the
   layout top, so the two started 48px apart at 1440 and 40px at 1024 (measured,
   user finding 2026-08-12). Suppressed here only: the grid keeps `gap--top`
   everywhere it is not in a column. Descendant, not child: catalogueResults
   wraps the grid in its own <section> region. */
.shop-layout .pf-main .gap--top { padding-top:0; }

.shop-card .card__title a { color:inherit; text-decoration:none; }
.shop-card .card__footer__action { position:relative; z-index:var(--z-local-control); }
.shop-card .card__footer__action > .btn { pointer-events:auto; position:relative; z-index:var(--z-local-control); }
.shop-card .card__footer__info { color:var(--color-text); }
.shop-card__image { background:var(--color-secondary-50); }
.shop-card__image > img { z-index:var(--z-local-content); object-fit:contain; padding:var(--sp-4); box-sizing:border-box; }
.shop-card__badge { position:absolute; z-index:var(--z-local-control); top:var(--sp-3); left:var(--sp-3); }
@media (max-width:767.98px){
  .catalogue-grid .shop-card .card__image { display:block; padding-bottom:56.25%; }
  .catalogue-grid .shop-card.card--default { box-shadow:var(--shadow-lg); border-bottom:0; }
  .catalogue-grid .shop-card .card__body { padding:var(--sp-6); }
  .catalogue-grid .shop-card .card__footer { padding:0 var(--sp-6) var(--sp-6); }
}

.shop-product-hero { display:grid; grid-template-columns:minmax(0,1fr); gap:var(--gap-responsive); align-items:center; }
@media (min-width:768px){ .shop-product-hero { grid-template-columns:minmax(16rem,24rem) minmax(0,1fr); } }
.shop-product-hero__media { display:flex; align-items:center; justify-content:center; min-height:18rem; padding:var(--sp-6);
  background:var(--surface-bg-muted); border:var(--stroke-1) solid var(--surface-border);
  border-radius:var(--surface-radius); box-shadow:var(--surface-elevation); }
.shop-product-hero__media img { max-width:100%; max-height:24rem; object-fit:contain; }
.shop-thumbs { display:grid; grid-template-columns:repeat(auto-fill,minmax(7rem,1fr)); gap:var(--sp-3); margin-top:var(--sp-4); }
.shop-thumbs img { width:100%; aspect-ratio:4/3; object-fit:contain; background:var(--surface-bg-muted); border:var(--stroke-1) solid var(--surface-border); border-radius:var(--radius); padding:var(--sp-2); }
.shop-price { font-size:var(--text-2xl); font-weight:var(--fw-bold); margin-block:0 var(--sp-4); }

.shopping__cart-card-list { list-style:none; margin:0; padding:0; }
.shopping__card { display:grid; grid-template-columns:minmax(0,1fr); gap:var(--sp-4); padding:var(--sp-4) 0; border-bottom:var(--stroke-1) solid var(--color-secondary-200); }
.shopping__card:first-child { border-top:var(--stroke-1) solid var(--color-secondary-200); }
@media (min-width:1024px){ .shopping__card { grid-template-columns:10.5rem minmax(0,1fr) 9.5rem 8rem; align-items:center; } }
/* The two layers inside a card come from the documented local rungs. They used to be a
   private 30/40 scale declared as custom properties, which the token gate skipped
   because it exempted `--*` declarations — the one place a raw scale could hide. */
.shopping__card__image { position:relative; display:flex; align-items:center; justify-content:center; min-height:10.5rem; background:var(--color-secondary-50); overflow:hidden; }
.shopping__card-image-background { position:absolute; inset:0; z-index:var(--z-local-content); background:var(--color-secondary-50); border:var(--stroke-2) solid var(--color-bg); opacity:.7; }
.shopping__card__image > img { position:relative; z-index:var(--z-local-control); max-width:90%; max-height:90%; width:auto; height:auto; object-fit:contain; }
.shopping__card-details-container { min-width:0; }
.shopping__card-details-container .card__title { margin-bottom:var(--sp-2); }
.shopping__card-action-container { display:flex; flex-direction:column; justify-content:space-between; gap:var(--sp-4); }
.shopping__card-price, .shopping__card-price-mobile { font-weight:var(--fw-bold); margin:0; }
@media (min-width:1024px){ .shopping__card-price { text-align:right; } .shopping__card-price-mobile { display:none; } }
@media (max-width:1023.98px){ .shopping__card-price { display:none; } }
.shopping__card-action { align-self:flex-start; }
@media (min-width:1024px){ .shopping__card-action { align-self:flex-end; } }
.shopping__card-amount-input { max-width:9.5rem; }
.shop-qty-input { width:100%; }
.shopping__cart-order-overview-container { display:grid; grid-template-columns:minmax(0,1fr); gap:var(--gap-responsive); align-items:start; }
@media (min-width:1024px){ .shopping__cart-order-overview-container { grid-template-columns:minmax(0,1fr) 22rem; } }
.shopping__cart-delivery-summary { position:static; }
@media (min-width:1024px){ .shopping__cart-delivery-summary { position:sticky; top:var(--sp-4); } }
/* This secondary map rule contains additions only; height belongs to the base rule. */
.pf-map { border-radius:var(--radius-lg); }
/* Stretch cards so row heights and footers align. */
.pf-gallery { display:grid; grid-template-columns:repeat(auto-fill, minmax(15rem, 1fr)); gap:var(--sp-5) var(--sp-4); }
/* Cap wide galleries at three columns so media cards remain comfortably readable. */
@media (min-width:1280px){
  .pf-gallery { grid-template-columns:repeat(3, minmax(0, 1fr)); }
}
.pf-card { display:flex; flex-direction:column; overflow:hidden; }
/* Fix the media box with padding so stretched flex cards cannot collapse
   its aspect ratio or introduce space above the footer. */
/* Use the shared 16:9 card-image ratio instead of a third media proportion. */
.pf-card__vis { position:relative; height:0; padding-bottom:56.25%; flex:none; overflow:hidden; background:var(--color-secondary-100); }
.pf-card__img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.pf-card__parcel { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:var(--color-primary-600);
  background:repeating-linear-gradient(45deg, var(--color-secondary-50), var(--color-secondary-50) 10px, var(--color-secondary-100) 10px, var(--color-secondary-100) 20px); }
/* --sp-3, the ONE inset every overlay on card media uses — see the note at
   .card__chips (css/components/card.css). `.pf-card__country` was excised with
   it: the chips are ordinary badges now (js/apps/portfolio.js), so the gallery
   no longer carries a second pill shape of its own. */
.pf-card__chips { position:absolute; top:var(--sp-3); left:var(--sp-3); right:var(--sp-3); display:flex; flex-wrap:wrap; gap:var(--sp-1); }
/* Gallery-only compact rhythm: name, ID, address, then aligned category and area footer. */
.pf-card .card__body { padding:var(--sp-5) var(--sp-6) var(--sp-4); gap:0; }
.pf-card .card__title { font-size:var(--text-lg); line-height:1.3; }
.pf-card .card__description { margin:var(--sp-2-5) 0 0; font-size:var(--text-sm); }
/* Cards fill their grid row and margin-top:auto pins varied-content footers to
   a common bottom edge. */
.pf-card { height:100%; }
.pf-card .card__content { height:100%; justify-content:flex-start; }
.pf-card .card__footer { margin:auto var(--sp-6) 0; padding:var(--sp-3-5) 0 var(--sp-5); font-size:var(--text-sm); }
.pf-typ { display:inline-flex; color:var(--color-text-muted); }
