/* ============================================================================
   PUESTO design tokens

   THE source of truth for the site's visual language. Every colour, type step,
   metric, timing and stacking layer the page uses is declared here and nowhere
   else -- so does the WebGL flight, which reads the shared values back through
   src/theme.js. Change a value here and the stylesheets, the character pass and
   the 3D map all follow; there is no second place to remember.

   Not here: the street map's own palette. Those colours are keyed to upstream
   OpenMapTiles layer ids and live with them, in src/mapstyle.js.
   ========================================================================= */
:root{

  /* ---- colour -------------------------------------------------------------
     One black, one white, and a grey ramp between them. Everything else is a
     role pointing at one of those. */
  --ink:#0b0b0b;                 /* the brand black: text, rules, markers      */
  --paper:#ffffff;               /* the page                                    */

  --grey-100:#f4f4f6;            /* hover wash                                  */
  --grey-150:#f1f1f3;            /* pressed wash                                */
  --grey-200:#e4e4e7;            /* hairline inside a bordered surface          */
  --grey-450:#b9b9bf;            /* label text on ink                           */
  --grey-500:#9a9aa0;            /* labels, placeholders, disabled              */
  --grey-550:#8a8a8a;            /* the flight's readout                        */
  --grey-700:#5c6066;            /* secondary body text                         */

  --text-soft:var(--grey-700);
  --text-muted:var(--grey-500);
  --text-faint:var(--grey-550);
  --on-ink:var(--paper);
  --on-ink-muted:var(--grey-450);

  --surface:var(--paper);
  --surface-hover:var(--grey-100);
  --surface-press:var(--grey-150);
  --story-scroll-background:color-mix(in srgb,var(--surface) 50%,transparent);
  --hairline:var(--grey-200);

  --scrim:rgba(11,11,11,.45);          /* over the page, behind a dialog       */
  --wash:rgba(255,255,255,.92);        /* over the map, behind its small print */

  /* the flight's own map, read by src/theme.js. INVERT_MAP there flips land and
     sea; these are the inverted (dark land on light water) values it ships on. */
  --flight-land:#191a1e;
  --flight-sea:#f0f0f2;
  --flight-border:#dcdce2;             /* state lines: gaps through the stipple */
  --flight-mark:var(--ink);            /* a launch city: the densest thing on it */

  /* The landing trailer uses its approved wrap palette and studio material profile from
     assets/wraps/hero.json. Page colour tokens never repaint customer artwork. */

  /* ---- type ---------------------------------------------------------------
     The brand's two faces, declared in styles/fonts.css. GABARITO sets the
     titles: the opening heading, the question, the panel and dialog headings
     and the card titles, and nothing else. JETBRAINS MONO carries everything
     else, which is all of the interface. The glyph stack is a third thing and
     is NOT either of them: it is the character pass's, and it names Monaco and
     SF Mono explicitly because the atlas is rasterised from it and the shapes
     have to be the same ones every time. */
  --font-title:'Gabarito',"Helvetica Neue",Helvetica,Arial,system-ui,sans-serif;
  --font-mono:'JetBrains Mono',ui-monospace,Menlo,monospace;
  --font-glyph:ui-monospace,"SF Mono",Menlo,Monaco,monospace;
  /* Title weight and tracking, from the brand: Gabarito at 700, tightened. */
  --w-title:700;
  --ls-tight:-.01em;

  /* the scale, by size. Every step below is in use; nothing else is allowed in
     a component. Fluid steps are the two that have to survive any viewport. */
                   --fs-9-5:9.5px;  --fs-10:10px;    --fs-11:11px;
  --fs-12:12px;                     --fs-13:13px;
  --fs-13-5:13.5px;--fs-14:14px;    --fs-15:15px;    --fs-17:17px;
  --fs-hero:clamp(30px,5vw,74px);         /* the opening heading               */
  --fs-ask:clamp(20px,2.2vw,31px);        /* the question, in the bar          */
  --fs-panel:clamp(17px,1.5vw,22px);      /* the panel heading beside the map  */
  --fs-lede:clamp(14px,1.35vw,19px);      /* the line under the opening        */
  --fs-application:clamp(24px,3vw,32px);

  --lh-tight:1.3;
  /* The opening heading's own leading, and only its own. Two lines of 72px caps carry their
     own weight; at --lh-tight they sat 43px apart, which is a paragraph's air between two
     halves of one sentence. 1.1 puts 29px between the ink, a third less, measured off the
     pixels rather than off the line box -- a line box is mostly the space above and below
     capitals that a caps-only line never uses. */
  --lh-hero:1.1;
  --lh-snug:1.35;
  --lh-copy:1.6;
  --lh-read:1.75;

  --ls-hud:.04em;
  --ls-button:.06em;
  --ls-tag:.07em;
  --ls-label:.12em;              /* the small uppercase labels                  */
  --ls-label-mid:.13em;

  /* ---- metric -------------------------------------------------------------
     One rule weight and one corner, on everything. The 2px border is the
     page's whole vocabulary of edges; 2px corners keep them sharp rather than
     rounded. The capsule's height is load-bearing: it fixes the height of the
     bar, the bar's height is the margin around the box below it, so a capsule
     that grew with its contents would move the box mid-flight. */
  /* ONE line weight, everywhere: the bar's underside, every box on the page, the divider
     between the two halves. It was 2px with the bar at 1; there is no reason for the page to
     be drawn in two thicknesses. */
  --rule:1px;
  --cap-h:46px;
  /* The bar's height, which is also the margin around the box below it. A smoothed corner
     reaches (1 + smoothing) x its radius along each edge it touches, and one of those edges
     is the side the corner runs up, so the two are tied:
        radius <= (height - rule/2 + nav-bleed) / (1 + smoothing)
     At 66, a 1px line, 1px of bleed and 0.94 smoothing that ceiling is 34.3 -- verified
     against squircle.js, which clamps at 34.28 and not before. 33 is half the bar, which is the
     proportion the first pair of numbers had. src/squircle.js clamps to the ceiling rather
     than distorting, and tools/check.py asserts the corner came out at the radius and
     smoothing asked for -- so if this height comes down again, the radius comes with it. */
  /* THE MARGIN ABOVE THE LOGO, and now the bar's whole vertical rhythm. The question sits in
     the bar and starts at this same distance from the top of the page, so the two line up; on a
     screen too narrow to stand them side by side the question drops below the logo and keeps
     the same margin, measured from the logo instead of from the page. */
  --nav-top:22px;
  --ask-rise:20px;               /* how far above its place the question starts */
  --lede-gap:clamp(14px,1.4vw,24px);   /* the opening heading to the line under it */
  /* How far below the middle of its own room the opening sits: one capital of the heading,
     so it moves with the type at every width. */
  --intro-lower:calc(var(--fs-hero)*0.7);

  /* HOW MUCH OF THE BOX THE PANEL TAKES once the map has slid across.
     A THIRD on a wide screen. It was a half, which is more column than any of these questions
     needs at 1440 and leaves the map -- the thing that is actually being looked at -- with no
     more room than the words about it.
     On a TALL screen the split is horizontal and it goes the other way: the panel takes a
     little more than half, because the top of a phone is where the work is and the bottom half
     is still a perfectly usable map.
     Named once because four rules and the slide's own transform have to agree on it, and the
     transform is half of it: see styles/map.css. */
  --panel-w:34%;
  --panel-h:56%;
  /* The platform cards: one inset, used on all four sides, and the sum of everything in a
     card that is not the picture -- see .card2 in workspace.css. */
  --card-pad:18px;
  --card-gap:var(--card-pad);    /* the picture is inset the same on all four sides */
  /* SCROLL uses the shared surface's --radius-sm and --smoothing geometry. */
  --story-scroll-min-width:108px;
  --story-scroll-min-height:44px;
  --story-scroll-padding:12px 22px;
  --story-scroll-bottom:calc((var(--legal-bar-h) + clamp(20px,4vh,44px)) / 2);
  --story-scroll-weight:500;
  --story-scroll-underline-offset:4px;
  /* One application popup; corners come from the shared surface component. */
  --application-width:680px;
  --application-gutter:clamp(12px,2vw,24px);
  --application-padding:clamp(18px,3vw,28px);
  --application-gap:16px;
  --application-label-gap:8px;
  --application-heading-gap:20px;
  --application-control-height:44px;
  --application-control-padding:12px 14px;
  --application-submit-height:48px;
  --application-checkbox-size:18px;
  --application-link-offset:3px;
  --application-disabled-opacity:.45;
  /* The bar at rest, which is also the page's horizontal inset. It IS --nav-top twice plus the
     wordmark -- 22 + 22 + 22 -- and tools/check.py asserts that, but it is written out as a
     number rather than as that sum: a custom property holding a calc() computes to the calc,
     not to a length, so every parseFloat that reads it (src/theme.js, and the checks) gets NaN.
     The bar grows past this while the question is in it and comes back the moment it is
     answered. */
  --nav-h:66px;
  /* The page's one horizontal inset, and it is the bar's height. It used to be the margin
     around a drawn frame; with the frame gone it is the only thing keeping the wordmark, the
     menu and the panel's text on the same two lines down the page. Read by the stylesheets
     and by placeChrome(), so there is one number and not two. */
  /* HOW FAR DOWN THE PICTURE SITS when the page opens, as a fraction of the window. The page
     does not begin in the flight: it begins with the whole picture pushed down this far, so
     what shows above the fold is its top -- the tops of the buildings -- under a heading. The
     first stretch of scroll moves it up ONE FOR ONE, like any block on any page, until it is
     in place; the flight starts from there.
     Both the scroll track's length and the picture's offset are measured from this, which is
     why it lives here rather than in config.js: the stylesheet needs it too. */
  --intro-drop:0.66;

  --gutter:var(--nav-h);
  /* ...except on a phone, where 66px of inset on each side of a 390px screen leaves 258 for
     everything: the bar's summary was down to a few characters and the panel's two cards had
     nothing to say in them. One number still, so the bar (which reads this through
     placeChrome) and the panel stay on the same line as each other. */

  /* ONE smoothing for everything, two radii: the large one for the bar, the small one for
     what sits inside it. Both are Figma's numbers, and src/squircle.js is Figma's own
     construction, so a shape here matches a frame there at the same pair.
     A smoothed corner reaches (1 + smoothing) x its radius along BOTH edges it touches, so a
     radius is only expressible if the shape is big enough to run it out into -- which is why
     the small radius cannot keep the full smoothing on a 46px-tall capsule (34.9 needed, 23
     available) and squircle.js clamps it to 0.28, exactly as Figma does. */
  --smoothing:0.94;              /* 0 is a plain arc, 1 has no arc left at all */
  --radius-lg:33px;              /* the bar's two BOTTOM corners; the top two stay square */
  --radius-sm:18px;              /* the capsule, and anything else inside the bar */
  /* The map's own chrome, and the name that follows the pointer over a launch city. These are
     the narrowest boxes on the page -- a zoom control is 32px across -- and the number is chosen
     so the SMOOTHING SURVIVES rather than so the corner looks big.
     A corner reaches (1 + smoothing) x radius along each edge, and it only has half the short
     side to run out into: 16px here. At 12 the reach is the whole 16, the smoothing clamps to
     0.33, and with no straight edge left between the corners the control comes out a pill --
     tried, and it read as a blob next to the rest of the page. At 8 the reach is 15.5, just
     inside the budget, so the full 0.94 is kept: the shape hugs the rectangle and then turns,
     which is the same curve the bar and the capsule are drawn with, at the size these are. */
  --radius-xs:8px;
  /* How far the outline runs PAST the window on the left, the right and the top. The stroke is
     centred on the path, so ONE FULL RULE puts the whole width of the side stroke off screen:
     the corner curve runs out of the window instead of straightening into a vertical line
     drawn down the inside of it. Measured at device pixel ratios 1, 2 and 3, the top-left
     corner of the window is clean white. Below a rule a hairline of the side comes back. */
  --nav-bleed:1px;               /* = --rule */
  /* The bar's underside has its own weight. Everything else on the page is --rule; set this
     to var(--rule) to put the bar back in step with it. */

  /* The wordmark is bare letterforms now, not letters inside a drawn box, so this IS the cap
     height rather than the height of a lockup: 34px of boxed lockup put the letters at 16.
     22 gives the bare word the presence the box used to, and lands its footprint (143px)
     about where the old one sat (134px). */
  --brand-h:22px;
  --brand-h-mark:26px;           /* the mark alone, on a bar too narrow for the word */
  --burger-size:38px;
  --burger-pad:9px;              /* the bars sit this far inside the button     */

  /* ---- motion -------------------------------------------------------------
     One easing curve for anything that travels, the browser's own for anything
     that only fades. */
  --ease:cubic-bezier(.33,0,.15,1);
  --t-press:.07s;
  --t-quick:.12s;
  --t-tap:.14s;
  --t-hover:.15s;
  --t-fast:.2s;
  --t-out:.25s;
  --t-base:.35s;
  --t-mid:.4s;
  --t-in:.5s;
  --t-slow:.55s;
  --t-slide:.6s;

  /* ---- layers -------------------------------------------------------------
     Every z-index in the site. Nothing declares a bare number. */
  --z-under:-1;                  /* behind its own parent's contents: the bar's shape    */
  --z-outline:1;                 /* above them: the capsule's own outline                */
  --z-hud:5;                     /* these three are INSIDE the content plane and only     */
  --z-ask:8;                     /* rank against each other. The question moved into the   */
                                 /* bar; this is the opening heading's layer now.          */
  --z-menu:10;
  --z-content:9;                 /* the content: the whole window, edge to edge           */
  --z-over:10;                   /* fixed things that have to clear it: the city name     */
                                 /* on hover                                              */
  --z-nav:11;                    /* the bar, over all of it                               */
  --z-shop:12;                   /* the unit: a whole view, so it covers the bar too      */
  --z-modal:13;                  /* ...and the waitlist still clears everything           */

  /* ---- the unit -----------------------------------------------------------
     The one place on this site with more than two tones, because a floor plan
     and a model of it need a ramp: a wall has to read as heavier than a
     partition, and a soil pipe as heavier than a water line. They are all still
     greys off the ramp above -- there is no colour here either.

     The 2D and the 3D read the same names (src/plan3d.js takes them through
     src/theme.js), so the drawing and the model cannot drift apart. */
  --shop-room:#fafafa;           /* the floor inside a room, on the plan        */
  --shop-room-lit:#f0f0f2;       /* ...and when it is being pointed at          */
  --shop-part-ink:#4a4a52;       /* a partition is lighter poche than a wall    */
  --shop-fx-fill:#ededf0;        /* a fixture, on the plan                      */

  --shop-slab:#e6e6e8;           /* and the same building, in three dimensions  */
  --shop-wall:#f2f2f1;
  --shop-part:#e2e2e4;
  --shop-ceil:#ededed;
  --shop-glass:#cfd6dd;
  --shop-leaf:#3c3c42;           /* door leaves and mullions: the dark joinery  */
  --shop-fx:#b4b4ba;             /* what the shell comes with                   */
  --shop-fit:#6f6f78;            /* what the trade brings, and what changes     */
  /* the four runs, by weight rather than by hue: a 100 mm soil pipe should look
     heavier than 22 mm copper, and on the plan they are told apart by dash */
  --shop-water:#8d8d96;
  --shop-waste:#3a3a41;
  --shop-power:#5b5b64;
  --shop-hvac:#c3c3c9;
}

@media (max-width:520px){ :root{ --gutter:22px; } }

/* THE OPENING SITS LOWER ON A TALL SCREEN. The drop is set against the heading's own cap
   height, which is right on a laptop and too little on a phone: a 390x780 window is mostly
   empty above the picture, and the heading floated near the top of it with the whole of that
   emptiness underneath. Twice the drop puts it where the eye goes first.
   It moves the block by --intro-lower, not by twice it: the block is centred inside the room
   the picture leaves, so adding N to the top padding moves the centre by N/2. See intro.css. */
@media (max-width:860px) and (orientation:portrait){
  /* vh, not a multiple of the heading. On a phone --fs-hero clamps to its 30px floor, so a
     multiplier moves the block by tens of pixels on a screen with hundreds to give: the
     question "how far down the screen" is a question about the SCREEN, so it is answered in
     the screen's own unit. 8vh leaves 239px of air above the heading and 104 below the line
     under it on a 390x844 phone, against 193 and 151 before. */
  :root{ --intro-lower:8vh; }
}
