/* The content: the whole window, edge to edge, with nothing drawn around it. The flight, the
   street map and the choices all live in here, and the bar floats over the top of them --
   over in the z sense, so it covers the top of the picture rather than making room in it.
   Landing does not summon anything, it splits what is already on screen: the map slides
   across into the right half and the choices come up in the left. */
.ws{ position:fixed; inset:0; z-index:var(--z-content); background:var(--paper);
  overflow:hidden; }

/* Present both renderers together after the shop, textures and camera fit are
   ready. The navigation and opening title remain available during loading. */
#frame:not([data-scene-state="ready"]) > canvas{visibility:hidden;}
.scene-load-error{position:absolute;inset:45% 24px auto;text-align:center;font:400 var(--fs-14) var(--font-mono);}
.scene-load-error button{margin-top:12px;padding:10px 18px;border:1px solid currentColor;border-radius:12px;background:var(--paper);font:inherit;cursor:pointer;}

/* the left half: empty and inert until the map has moved over */
/* The top padding clears the bar, which is over this half rather than above it, and the sides
   are the page's gutter -- so the heading starts on the same line as the wordmark above it. */
.ws-l{ position:absolute; left:0; top:0; bottom:0; width:var(--panel-w);
  padding:calc(var(--nav-h) + 30px) var(--gutter) 32px; overflow:auto; background:var(--surface);
  display:flex; flex-direction:column; container-type:inline-size;
  opacity:0; pointer-events:none; transition:opacity var(--t-mid) ease; }
body.split .ws-l{ opacity:1; pointer-events:auto; }
.ws-l > *{ opacity:0; transform:translateY(12px);
  transition:opacity var(--t-mid) ease .06s, transform var(--t-in) var(--ease) .06s; }
.ws-l > * + *{ transition-delay:.13s; }
.ws-l > * + * + *{ transition-delay:.20s; }
body.split .ws-l > *{ opacity:1; transform:none; }
.ws-l h3{ margin:0 0 6px;
  font:var(--w-title) var(--fs-panel)/var(--lh-tight) var(--font-title);
  letter-spacing:var(--ls-tight); text-transform:uppercase; color:var(--ink); }
.ws-l p.sub{ margin:0 0 20px; font:400 var(--fs-13)/var(--lh-copy) var(--font-mono);
  color:var(--text-soft); }
.step + .step{ margin-top:34px; }
/* the setup step is the panel's column: it takes what the heading leaves */
#stepSetup{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
#stepSetup h3{ flex:0 0 auto; }

/* The trade is not asked until the setup has been answered, and it ARRIVES rather than
   appearing: display:none has no state to transition out of, so the entrance is a keyframe
   the newly-rendered element runs once. `both` holds the end state afterwards, which is what
   keeps it from dropping back to the .ws-l entrance transition's starting offset. */
#stepBiz{ display:none; }
body.a-set #stepBiz{ display:block; animation:step-in var(--t-in) var(--ease) both; }

/* ONCE A PLATFORM IS CHOSEN THE QUESTION GOES. Not collapsed, not summarised: gone. The
   answer is in the bar, and the bar is also where it is changed -- clicking the Setup segment
   reopens the same two rows under it. Leaving a shrunken copy of an answered question in the
   panel is the thing the bar exists to stop: the left half is for the question being asked
   now, and nothing else.

   The margin between the two steps goes with it, or the trade question starts 34px down the
   panel with nothing above it -- display:none takes an element out of the layout but not out
   of the sibling chain that rule reads. */
body.a-set #stepSetup{ display:none; }
body.a-set .step + .step{ margin-top:0; }

/* ...and the trade question takes the panel in its place. */
body.a-set #stepBiz{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
body.a-set #stepBiz h3{ flex:0 0 auto; }

/* THE TRADES, ONE CARD EACH. Fifty of them, so this is the part that scrolls rather than the
   panel: the heading stays put and the list moves under it.

   Two columns when the panel is wide enough for two, one when it is not -- asked of the PANEL
   rather than of the window, because the two are not the same question. The panel is half the
   window on a laptop and the whole width of it under 860px, where the split turns horizontal:
   a window media query would give two columns on a phone and one on a wide short screen,
   which is backwards. A container query asks the box the cards are actually in. */
/* `.trades`, NOT `.biz`. The capsule's business segment is `.seg.biz` and has been since the
   bar was built, so a grid called `.biz` landed on it: it took the grid's gap of 8px, its
   flex of 1 1 auto and its 2px right padding, which spread the segment's label and value 6px
   apart and left the answer hard against the capsule's edge with no room for an ellipsis. Two
   components, two names -- the same rule .cap-go follows next door in nav.css. */
.trades{ display:grid; grid-template-columns:1fr; gap:8px; align-content:start;
  flex:1 1 auto; min-height:0; overflow:auto; overscroll-behavior:contain;
  padding-right:2px; }                     /* the outline's own width, so it is not clipped */
@container (min-width:520px){ .trades{ grid-template-columns:1fr 1fr; } }
/* THE TRADE CARDS SIZE THEMSELVES, and that has to be said explicitly. A grid item is
   STRETCHED to its row by default, and the row is sized from the item's own contribution --
   which for a flex column whose picture is a percentage of an as-yet-unknown column width is
   circular, and Chrome breaks the circle at zero. Every card came out 19px tall: the name
   strip, and no picture. `start` takes the item out of the stretch, so its height is its
   content and the row follows it.
   The cards still line up, because the name strip below holds two lines whether it needs them
   or not -- which is a better way to get equal heights than asking the grid for them. */
#bizCards{ align-items:start; grid-auto-rows:max-content; }
/* ...and `start` ALONE is not enough. It gives the ITEM its own height and leaves the TRACK at
   19px, so the cards come out full size and twenty-seven pixels apart, overlapping each other
   down the column. `max-content` on the rows is what actually measures them. */

/* nothing matched what was typed: the grid says so rather than going silently empty */
.trades.none::after{ content:'Nothing of that kind yet. Choose it anyway and we will tell you '
  'when it is ready.'; display:block; grid-column:1 / -1; padding:14px 2px;
  font:400 var(--fs-12)/var(--lh-copy) var(--font-mono); color:var(--text-muted); }

/* ...AND THE TRADE GOES THE SAME WAY once it is answered, for the same reason: it is in the
   bar, the bar is where it is changed, and the panel asks the question that is live now. The
   third question -- where in the city -- takes the panel in its place. */
body.a-biz #stepBiz{ display:none; }
#stepArea{ display:none; }
body.a-biz #stepArea{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0;
  animation:step-in var(--t-in) var(--ease) both; }
body.a-biz #stepArea[hidden]{ display:none; }
body.a-biz #stepArea h3, body.a-biz #stepArea .sub{ flex:0 0 auto; }
#stepArea .sub{ margin:0 0 12px; font:400 var(--fs-12)/var(--lh-copy) var(--font-mono);
  color:var(--text-muted); }
#stepArea .sub:empty{ display:none; }

/* THE DEAD END, when the state has no licence to give. Not an empty list and not a disabled
   button: the reason, the statute it comes out of, and the one thing that does work -- which
   is the other platform, and is a click. */
.stop{ padding:16px 18px; margin-bottom:14px; background:var(--surface-hover); }
.stop .why{ margin:0; font:400 var(--fs-13)/var(--lh-copy) var(--font-mono); color:var(--ink); }
.stop .cite{ margin:8px 0 0; font:400 var(--fs-11)/var(--lh-copy) var(--font-mono);
  color:var(--text-muted); }
.stop .alt{ margin-top:14px; padding:9px 14px; cursor:pointer; background:var(--ink);
  color:var(--on-ink); font:500 var(--fs-13) var(--font-mono); }

/* ODbL, and it belongs where the data is used rather than only under the map. */
.credit{ flex:0 0 auto; margin:10px 0 0;
  font:400 var(--fs-10)/var(--lh-copy) var(--font-mono); color:var(--text-muted); }

/* THE WAY IN, once a site is chosen. It sits under the list rather than on the card: the list
   scrolls and the choice must not scroll away from the thing that acts on it. */
.go{ flex:0 0 auto; display:flex; align-items:center; gap:12px; padding-top:12px;
  margin-top:2px; }
.go .gn{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; font:500 var(--fs-13) var(--font-mono); color:var(--ink); }
/* a SURFACE */
.go button{ flex:0 0 auto; padding:10px 18px; cursor:pointer; background:var(--ink);
  color:var(--on-ink); font:500 var(--fs-13) var(--font-mono);
  transition:transform var(--t-press) ease; }
.go button:active{ transform:translateY(1px); }

/* THE CARD IS THE PICTURE, and the name is a caption under it.
   It used to be the other way round -- a 38px thumbnail beside a line of text -- which spent
   the whole card on the words and none of it on the thing. The unit in each of these has THIS
   trade's machinery in it, which is the only picture that answers what you would be buying, so
   it gets the card and the name gets a strip.
   A SURFACE (styles/surface.css), so no border or radius is declared here. */
.bizcard{ display:flex; flex-direction:column; padding:0; width:100%; overflow:hidden;
  text-align:left; cursor:pointer; min-width:0;
  transition:background-color var(--t-tap) ease, transform var(--t-press) ease; }
.bizcard:hover{ background-color:var(--surface-hover); }
.bizcard:active{ transform:translateY(1px); }
.bizcard.on{ background-color:var(--ink); color:var(--on-ink); }
.bizcard[hidden]{ display:none; }

/* THE IMAGE CARRIES ITS OWN SHAPE. tools/build_trade_shots.py already crops the 1024 square
   to 640x400, and the tag carries those numbers, so the picture has an intrinsic aspect and
   the card has a height before the file has loaded.
   It got that height from `aspect-ratio` on the wrapper first, and every card came out 19px
   tall: a grid sizes its rows BEFORE the width of a 1fr column is known, so a height derived
   from that width resolves against nothing and lands on zero. An <img> with width and height
   attributes has a real intrinsic contribution and does not have the problem. */
.bizcard .pic{ display:block; flex:0 0 auto; width:100%; background:var(--surface);
  overflow:hidden; }
.bizcard .pic img{ display:block; width:100%; height:auto; }
/* ...and if a trade ever has no picture, the card still has to be a card */
.bizcard .pic:empty{ aspect-ratio:16/10; background:var(--surface-hover); }
.bizcard.on .pic{ background:var(--on-ink); }

/* the split, and the name under it at the size it always was */
.bizcard .nm{ display:flex; align-items:center; padding:9px 12px; min-width:0;
  min-height:calc(2*1.35*var(--fs-13) + 18px);       /* two lines, so the cards line up */
  border-top:var(--rule) solid var(--grey-200);
  font:400 var(--fs-13)/1.35 var(--font-mono); }
.bizcard.on .nm{ border-top-color:var(--on-ink-muted); }
@keyframes step-in{ from{ opacity:0; transform:translateY(12px); } to{ opacity:1; transform:none; } }

/* AN AREA, OR A SITE. The same grid as the trades -- one column, two when the panel is wide
   enough -- because it is the same kind of list, but the card carries three lines instead of
   one: what it is, what the ground under it is zoned, and, where there is one, the rule.

   THE SHUT ONES ARE DRAWN. A list that hides what it cannot offer tells nobody why, and the
   why is the only part of this that is worth anything to somebody about to sign a lease. They
   are drawn back rather than struck through: still readable, plainly not clickable. */
.zonecard{ display:flex; align-items:flex-start; gap:10px; padding:10px 12px; width:100%;
  text-align:left; cursor:pointer; min-width:0;
  transition:background-color var(--t-tap) ease; }
.zonecard:hover, .zonecard.hot{ background-color:var(--surface-hover); }
.zonecard.on{ background-color:var(--ink); color:var(--on-ink); }
.zonecard .zt{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.zonecard .nm{ font:500 var(--fs-13) var(--font-mono); line-height:1.35; }
.zonecard .sub{ font:400 var(--fs-11)/var(--lh-copy) var(--font-mono); color:var(--text-soft); }
.zonecard.on .sub{ color:var(--on-ink-muted); }
/* THE INSTRUMENT, BY NAME, above the words it is written in. One overlay -- a commission
   district, a downtown vending programme -- can shut several areas at once, and naming it is
   what turns "not permitted" into something a reader can go and check. */
.zonecard .rule{ font:500 var(--fs-11) var(--font-mono); letter-spacing:var(--ls-tag);
  text-transform:uppercase; color:var(--ink); margin-top:3px; }
.zonecard.shut .rule{ color:var(--text-soft); }
.zonecard.on .rule{ color:var(--on-ink); }
/* the rule, in the words of the code it comes from. Capped at four lines: the whole of it is
   in assets/data/zones.json and on the card is not where a paragraph of statute belongs. */
.zonecard .why{ font:400 var(--fs-11)/var(--lh-copy) var(--font-mono); color:var(--text-muted);
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:4; overflow:hidden; }
/* the same mark the map draws: filled when the area will have you, hollow when it will not */
.zonecard .dot{ flex:0 0 auto; width:9px; height:9px; margin-top:4px; border-radius:50%;
  background:var(--ink); }
.zonecard.on .dot{ background:var(--on-ink); }
.zonecard.shut{ cursor:default; }
.zonecard.shut .nm, .zonecard.shut .sub{ color:var(--text-muted); }
.zonecard.shut .dot{ background:none; box-shadow:inset 0 0 0 var(--rule) var(--text-muted); }
.zonecard.shut:hover{ background-color:transparent; }

/* The field keeps its room while it is away. Clicking BUSINESS in the bar MOVES this field up
   into the bar's dropdown -- there is one of it, not two -- and a hole opening in the panel
   for as long as the dropdown is up would drag everything under it upward. */
.ws-l .field{ min-height:var(--cap-h); max-width:420px; }
/* the trade question's own field, between its heading and its grid */
#stepBiz > .combo{ flex:0 0 auto; margin:2px 0 14px; max-width:none; }
#stepBiz > .combo input{ width:100%; }
.ws-l .combo{ display:block; }
.ws-l .combo input{ width:100%; }
/* the list drops into a panel that scrolls, so it is capped against the panel rather than
   against the window it would have hung over in the question's own line */
.ws-l .menu-list{ max-height:min(300px,40vh); }

/* the divider slides out to the middle with the map */
.ws-div{ position:absolute; top:0; bottom:0; left:0; width:var(--rule); background:var(--ink);
  pointer-events:none; opacity:0;
  transition:left var(--t-slide) var(--ease), opacity var(--t-out) ease; }
/* half a RULE off centre, not half a hardcoded pixel: the divider is one rule wide, and if
   that weight ever changes this is where it would quietly stop being centred */
body.split .ws-div{ left:calc(var(--panel-w) - var(--rule)/2); opacity:1; }

/* Side by side, not stacked: the panel asks a second question underneath now, and two cards
   down the left half used most of the height on their own. */
/* TWO CARDS, ONE ABOVE THE OTHER, AND THEY TAKE THE PANEL. The choice is between two
   objects, so the cards are mostly picture: a name and the unit itself, open, and nothing
   else. They stand in a column rather than side by side because these units are wide, and
   half a half-screen draws them at the size of a stamp.

   The panel is a flex column all the way down -- panel, step, cards, card -- so the two
   share whatever height is left under the heading, at any window, without a number for it
   anywhere. min-height:0 at every level is what lets them SHRINK: a flex item's floor is its
   content, and a 1280px picture would otherwise push the column past the bottom of the page. */
.cards{ display:flex; flex-direction:column; gap:14px; flex:1 1 auto; min-height:0; }

/* EACH CARD IS THE SHAPE OF ITS OWN UNIT, and that is what makes the white around the unit
   even. A picture set in a box that is not its proportion touches two of the four sides and
   floats off the other two: at equal heights the trailer, which is nearly square, sat with
   18px of paper above and below it and 110px either side. `contain` cannot fix that -- it is
   what contain DOES -- so the box has to be the shape of the picture instead.
   Which means the two cards are different heights, because the two units are different
   shapes: a trailer is a box and a shop is a wide low building. The height each one takes is
   both are inset by --card-pad on all four sides and neither is stretched.
   The basis is the card's own furniture -- two paddings, the gap, and the label's line -- and
   the growth is 1/aspect, so the room LEFT OVER after that furniture is split in the ratio the
   two pictures need. Splitting the whole height instead would be off by the furniture. */
/* HALF EACH. These used to be sized by the shape of the unit in them -- flex grow of 1/aspect
   -- so the trailer, being nearly square, took two thirds of the column and the shop a third.
   That is a true statement about the two objects and the wrong one about the two CHOICES:
   they are alternatives of equal standing, and the picture ranked them. Equal halves. */
.card2{ flex:1 1 0; min-width:0; min-height:0; display:flex; flex-direction:column; }
/* a SURFACE (styles/surface.css): no border, no radius, no background of its own */
.card2{ position:relative; padding:var(--card-pad); cursor:pointer; text-align:left;
  width:100%; gap:var(--card-gap);
  transition:background-color var(--t-tap) ease, transform var(--t-press) ease; }
.card2:hover{ background-color:var(--surface-hover); }
.card2:active{ transform:translateY(1px); }
.card2 .t{ font:var(--w-title) var(--fs-17) var(--font-title); flex:0 0 auto; }

/* THE UNIT ITSELF, and it is given the room rather than a size. Both renders are cut out of
   their backdrops on transparent, so `contain` sets each one on the card's paper at its own
   proportion -- the trailer is nearly square and the shop is twice as wide as it is tall, and
   scaling either to fill would crop the thing the card is about. */
.card2 .shot{ flex:1 1 auto; min-height:0; width:100%; height:100%;
  object-fit:contain; object-position:center; display:block; }
/* The units are different shapes -- the trailer is nearly square, the shop twice as wide as it
   is tall -- so `contain` gives each one the room it needs inside its own equal half. */

/* CHOSEN. Filling the card with ink is only safe because by the time anything is chosen the
   pictures have gone -- see body.a-set below -- and both units are rendered in the black they
   are actually built in, so an inked card with a picture still on it would swallow the very
   thing the card is about. The two always change together: panel.show() marks the card in the
   same breath that search.js writes a-set on the body. */
.card2.on{ background-color:var(--ink); color:var(--on-ink); }

/* too narrow to stand two halves side by side: the split turns horizontal rather than
   squeezing both into columns nothing fits in */
@media (max-width:860px){
  .ws-l{ width:auto; right:0; bottom:auto; height:var(--panel-h);
    padding:calc(var(--nav-h) + 16px) var(--gutter) 20px; }
  body.split .ws-div{ left:0; top:calc(var(--panel-h) - var(--rule)/2); bottom:auto;
    width:auto; right:0;
    height:var(--rule); }
  .ws-l h3{ margin-bottom:4px; }
  .ws-l p.sub{ margin-bottom:16px; font-size:var(--fs-12); }
  .cards{ gap:10px; }
  .step + .step{ margin-top:20px; }
  .card2{ padding:11px 14px; gap:8px; }
  .card2 .t{ font-size:var(--fs-15); }
}

