/* ═══════════════════════════════════════════════════════════════════════════
 * THE FIRST SHARED STYLESHEET THIS PROJECT HAS HAD
 *
 * Three files carry the four deal views, and until now every one of them
 * declared its own copy of the same twenty-six custom properties, its own
 * copy of the grain utility, its own hero, its own popover, its own order
 * row, its own emblems. The files said so out loud — "the honest cost of a
 * set of pages with no shared stylesheet" — and paid for it with tests that
 * compare pages to each other character by character.
 *
 * That cost was affordable while the pages were small. It is not affordable
 * now: the same lifecycle is drawn four times, and four drawings of one thing
 * drift. This file is the shared layer those tests were standing in for.
 *
 * ── WHAT IS DELIBERATELY NOT IN HERE ──────────────────────────────────────
 *
 * The atmosphere. `.sky`, `.spot`, the two keyframes and the body ground stay
 * inline in every page, byte-identical to rooms.html, because that is what
 * makes walking between pages feel like one site — and because the suites
 * compare them to rooms.html character for character. Moving them here would
 * pass a refactor and quietly break the thing they protect. They are not
 * duplication; they are a contract with the rest of the site.
 *
 * The site bar is not here either. It lives in a shadow root with
 * `:host{all:initial}`, so nothing in this file can reach it — which is why
 * "keep the bar exactly as it is" costs nothing at all.
 * ═════════════════════════════════════════════════════════════════════════*/

:root{
  /* ── The palette, unchanged. Every value below already existed in three
     places; this is the same set, declared once. Changing one now changes
     four views, which is the entire point. ─────────────────────────────── */
  --navy-0:#000000; --navy-1:#02090C; --navy-2:#04141A;
  --edge:#0E3A47; --edge-lit:#1B6478;
  --white:#EAF7FB; --dim:#93B6C0; --faint:#5C818C;
  --mint:#00B4D7; --blue:#58E9FF; --gold:#F2B33D; --red:#FF6B6B; --good:#3BE3B0;
  --sell:#B98BFF;
  --display:"Outfit",system-ui,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,Menlo,monospace;
  --ease:cubic-bezier(.22,.68,.24,1);
  --void:#00070A;
  --ink:var(--void);
  --panel:rgba(4,20,26,.72);

  --s1:6px; --s2:10px; --s3:16px;
  --s4:clamp(18px,2.2vw,24px);
  --s5:clamp(26px,3.4vw,36px);
  --s6:clamp(34px,5vw,56px);
  --gutter:clamp(18px,4vw,40px);

  --surf-a:rgba(6,26,33,.92);
  --surf-b:rgba(2,11,15,.92);
  --lip:inset 0 1px 0 rgba(150,235,255,.07);
  --lift-1:0 2px 4px -2px rgba(0,0,0,.6), 0 14px 34px -22px rgba(0,0,0,.95);
  --lift-2:0 3px 8px -3px rgba(0,0,0,.65), 0 26px 56px -26px rgba(0,0,0,1), 0 0 0 1px rgba(27,100,120,.35);
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E");

  /* ── ONE COLOUR PER STATE, AND THE PAGE DOES NOT GET A VOTE ─────────────
   * A deal that is funded is amber wherever you meet it. The tone names in
   * deal-ui.js point here and nowhere else, so a status colour is a property
   * of the lifecycle rather than a decision each view makes for itself.
   *
   * Chosen so the four a reader meets most are distinguishable at a glance
   * AND distinguishable to the roughly one man in twelve who cannot separate
   * red from green: cyan, amber and violet carry the load, and green is only
   * ever the finished state, where it is confirming something already said in
   * words. Nothing here relies on colour alone — every pill carries its word.
   */
  --tone-wait:#93B6C0;   /* open, nobody has answered — quiet on purpose */
  --tone-live:var(--blue);   /* accepted, something is happening */
  --tone-hold:var(--gold);   /* funded, money is held and a clock is running */
  --tone-good:var(--good);   /* delivered */
  --tone-off:#5C818C;    /* refunded, cancelled, expired — over, not bad */

  /* ── DEPTH INSTEAD OF OUTLINES ──────────────────────────────────────────
   * The old pages made every box the same way: same darkness inside as out,
   * one hairline border doing all the work. Fifty of those on a page reads as
   * a wireframe, and it is why a well-organised page still looked unfinished.
   * Three surface levels, so a card can sit ON something rather than beside
   * it, and borders become a choice rather than the only tool.
   */
  --sunk:rgba(0,4,6,.55);          /* below the page: wells, inputs, tracks */
  --rise-1:linear-gradient(180deg,var(--surf-a),var(--surf-b));
  --rise-2:linear-gradient(180deg,rgba(9,34,43,.94),rgba(3,15,20,.94));
  --hair:1px solid rgba(27,100,120,.28);
  --hair-lit:1px solid rgba(88,233,255,.34);

  --r1:10px; --r2:14px; --r3:18px; --r4:24px;

  /* ── TYPE ───────────────────────────────────────────────────────────────
   * A scale, so a heading size is an answer rather than an opinion. Every
   * step clamps, because the pages already learned that a phone is not the
   * desktop with the same numbers in a narrower tube.
   */
  --t-hero:clamp(34px,6vw,58px);
  --t-h2:clamp(22px,3vw,30px);
  --t-h3:clamp(16px,1.7vw,19px);
  --t-body:15px;
  --t-small:13px;
  --t-micro:11px;

  --focus:0 0 0 2px var(--void), 0 0 0 4px var(--blue);

  /* Written by deal-pointer.js on the element under the cursor. Declared here
     with a neutral centre so the gradient resolves to something sensible when
     the script never runs at all — a touch device, a blocked module, a
     reduced-motion setting. An unresolvable var() invalidates the whole
     declaration silently, which is precisely the bug that once left this
     project's phone layout with no background. */
  --mx:50%; --my:50%;
}

/* ── MOTION IS A PREFERENCE, NOT A DECORATION ──────────────────────────────
   Declared once, at the top, so no component has to remember. Everything
   below moves with transform and opacity only, which is what keeps this
   smooth on the average phone rather than only on the machine it was built
   on. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
}

/* ── THE GRAIN, WHICH IS THE ONE TEXTURE ───────────────────────────────────
   A single very quiet noise layer over raised surfaces. It is what stops a
   dark panel reading as flat black, and it must never be strong enough to
   cost a word of legibility — which is why it sits at 3% and is switched off
   entirely behind reduced-motion-free small screens where it costs more in
   paint than it returns. */
.tex{position:relative;isolation:isolate}
.tex::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background-image:var(--grain);opacity:.03;mix-blend-mode:overlay;z-index:-1;
}
.tex-lit::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(120% 80% at 50% -20%,rgba(88,233,255,.10),transparent 70%);
  z-index:-1;
}

/* ── SURFACES ──────────────────────────────────────────────────────────────
   Three levels and a well. Use the level, not a border, to separate things.
   A border here is for saying "this one is selected" or "this one needs
   attention", which is a job it can only do if it is not also saying
   "this is a box". */
.surf{background:var(--rise-1);border-radius:var(--r3);box-shadow:var(--lip),var(--lift-1)}
.surf-2{background:var(--rise-2);border-radius:var(--r3);box-shadow:var(--lip),var(--lift-2)}
.well{background:var(--sunk);border-radius:var(--r2);box-shadow:inset 0 1px 2px rgba(0,0,0,.5)}

/* ── THE STATUS PILL ───────────────────────────────────────────────────────
   Small, quiet, and never the loudest thing in a row — the price and the
   name are what a person scans for. It earns its colour from the state and
   carries the word regardless, so it never depends on colour alone. */
.pill{
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--mono);font-size:var(--t-micro);letter-spacing:.09em;
  text-transform:uppercase;white-space:nowrap;
  padding:4px 9px;border-radius:999px;
  color:var(--tone);background:color-mix(in srgb,var(--tone) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--tone) 34%,transparent);
}
.pill::before{
  content:"";width:5px;height:5px;border-radius:50%;background:var(--tone);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--tone) 18%,transparent);
}
.pill[data-tone="wait"]{--tone:var(--tone-wait)}
.pill[data-tone="live"]{--tone:var(--tone-live)}
.pill[data-tone="hold"]{--tone:var(--tone-hold)}
.pill[data-tone="good"]{--tone:var(--tone-good)}
.pill[data-tone="off"] {--tone:var(--tone-off)}

/* ══════════════════════════════════════════════════════════════════════════
 * THE ESCROW RAIL — the signature
 *
 * Four nodes and three links: Offer · Accept · Fund · Deliver. The same
 * object explains the mechanic on the deals page, reports live state on a
 * board listing, tracks a checkout on hire and shows an order's history on
 * the orders page. One component, four appearances — not four progress bars
 * that happen to look alike.
 *
 * The link between two reached nodes carries a slow travelling highlight, and
 * ONLY on `.erail-live`, which a view sets when the deal is genuinely still
 * moving. Animation that runs on a settled deal is animation that means
 * nothing, and this project has spent a day removing things that looked
 * informative and were not.
 * ═════════════════════════════════════════════════════════════════════════*/
.erail{
  --tone:var(--tone-wait);
  display:flex;align-items:center;gap:0;
  --dot:9px; --line:2px;
}
.erail[data-tone="live"]{--tone:var(--tone-live)}
.erail[data-tone="hold"]{--tone:var(--tone-hold)}
.erail[data-tone="good"]{--tone:var(--tone-good)}
.erail[data-tone="off"] {--tone:var(--tone-off)}
.erail-c{--dot:7px; --line:2px}

.erail-node{
  flex:none;width:var(--dot);height:var(--dot);border-radius:50%;
  background:rgba(147,182,192,.22);
  box-shadow:inset 0 0 0 1px rgba(147,182,192,.3);
  transition:background .3s var(--ease),box-shadow .3s var(--ease),transform .3s var(--ease);
}
.erail-node.on{background:var(--tone);box-shadow:inset 0 0 0 1px transparent}
/* The node the deal is actually AT, which is the one a reader is looking for.
   A halo rather than a size change, so nothing reflows as a deal moves. */
.erail-node.now{box-shadow:0 0 0 4px color-mix(in srgb,var(--tone) 20%,transparent)}
/* And an ending, which a progress bar cannot say by itself: a refund and a
   delivery both stop at the last node, and drawing a refund as "nearly
   finished" would be the rail telling a comfortable lie. */
.erail-node.end{border-radius:2px;transform:rotate(45deg)}

.erail-link{
  flex:1 1 auto;min-width:14px;height:var(--line);
  background:rgba(147,182,192,.18);border-radius:2px;position:relative;overflow:hidden;
  transition:background .3s var(--ease);
}
.erail-link.on{background:color-mix(in srgb,var(--tone) 55%,transparent)}
.erail-live .erail-link.on::after{
  content:"";position:absolute;inset:0;border-radius:inherit;
  background:linear-gradient(90deg,transparent,var(--tone),transparent);
  transform:translateX(-100%);
  animation:erailpulse 2.6s var(--ease) infinite;
}
@keyframes erailpulse{ 0%{transform:translateX(-100%)} 60%,100%{transform:translateX(100%)} }

/* Labelled, for the one place that explains rather than reports. */
.erail-lab{display:grid;gap:var(--s2)}
.erail-lab .erail-words{display:flex;justify-content:space-between;
  font-family:var(--mono);font-size:var(--t-micro);letter-spacing:.08em;
  text-transform:uppercase;color:var(--faint)}

/* ══════════════════════════════════════════════════════════════════════════
 * CONTROLS
 * ═════════════════════════════════════════════════════════════════════════*/
.btn{
  --bg:var(--rise-2);
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font:inherit;font-weight:650;font-size:14px;line-height:1;cursor:pointer;
  padding:12px 18px;border-radius:var(--r1);border:var(--hair);
  color:var(--white);background:var(--bg);
  box-shadow:var(--lip);
  transition:transform .16s var(--ease),filter .16s var(--ease),box-shadow .16s var(--ease);
  min-height:44px;                     /* a touch target, everywhere, always */
}
/* An anchor styled as a button is a button, and a button is not underlined.
   Every page that used .btn on an <a> had to remember to say this; two of
   them did and the third shipped underlined buttons. It belongs here. */
a.btn{text-decoration:none}
.btn-1{
  color:var(--void);border-color:rgba(88,233,255,.5);
  background:linear-gradient(180deg,#7ff0ff,var(--blue));
  box-shadow:0 6px 22px -12px rgba(88,233,255,.8);
}
.btn:disabled{cursor:default;filter:grayscale(.5) brightness(.75);transform:none;box-shadow:none}
.btn:focus-visible{outline:none;box-shadow:var(--focus)}
@media (hover:hover) and (pointer:fine){
  .btn:not(:disabled):hover{transform:translateY(-1px);filter:brightness(1.07)}
}
/* Touch gets a press instead of a hover — the same feedback, delivered the
   way the device actually reports intent. */
.btn:not(:disabled):active{transform:translateY(0) scale(.985)}

.chip{
  display:inline-flex;align-items:center;gap:7px;
  font:inherit;font-size:var(--t-small);font-weight:600;cursor:pointer;
  padding:9px 13px;border-radius:999px;min-height:38px;
  color:var(--dim);background:var(--sunk);border:1px solid transparent;
  transition:color .16s var(--ease),background .16s var(--ease),border-color .16s var(--ease);
  white-space:nowrap;
}
.chip[aria-pressed="true"],.chip.on{
  color:var(--white);background:rgba(88,233,255,.10);border-color:rgba(88,233,255,.36);
}
.chip:focus-visible{outline:none;box-shadow:var(--focus)}
.chip .n{font-family:var(--mono);font-size:var(--t-micro);color:var(--faint)}
.chip[aria-pressed="true"] .n,.chip.on .n{color:var(--blue)}

/* A row of chips must be swipeable on a phone and must never push the PAGE
   sideways — the scroll belongs to the strip. */
.chiprow{display:flex;gap:var(--s2);overflow-x:auto;scrollbar-width:none;
  -webkit-overflow-scrolling:touch;scroll-snap-type:x proximity;padding-bottom:2px}
.chiprow::-webkit-scrollbar{display:none}
.chiprow>*{scroll-snap-align:start}

.field{
  width:100%;font:inherit;font-size:16px;   /* 16px or iOS zooms the page */
  color:var(--white);background:var(--sunk);
  border:var(--hair);border-radius:var(--r1);
  padding:13px 14px;min-height:46px;
  transition:border-color .16s var(--ease),box-shadow .16s var(--ease);
}
.field::placeholder{color:var(--faint)}
.field:focus{outline:none;border-color:rgba(88,233,255,.5);
  box-shadow:0 0 0 3px rgba(88,233,255,.12)}
.field[aria-invalid="true"]{border-color:rgba(255,107,107,.55)}

/* ══════════════════════════════════════════════════════════════════════════
 * WAITING
 *
 * A skeleton says "this shape is coming". A spinner says "something is
 * happening somewhere". The first is worth building, and the difference is
 * not decoration: on the orders page a page that shows "you have no orders"
 * while it is still reading has told a returning customer something false,
 * and corrected itself half a second later.
 *
 * ── THE THREE COPIES THIS REPLACES ────────────────────────────────────────
 *
 * Every one of the three files had its own, and two of the three were built
 * the expensive way:
 *
 *   deals   .skel + .sl, animating BACKGROUND-POSITION on a 400%-wide
 *           gradient. Background position is not a composited property. Every
 *           frame repaints the element on the main thread, once per line, and
 *           a loading board carried twelve of them.
 *   orders  .skelrow, the same technique on a 300% gradient, three at a time,
 *           and no internal shape at all — three grey bars that resemble
 *           nothing the page is about to draw.
 *   hire    nothing. The page reads capacity and order history on load and
 *           showed an empty box while it did.
 *
 * ── WHAT THIS DOES INSTEAD ────────────────────────────────────────────────
 *
 * ONE ANIMATED ELEMENT PER CARD, not one per line, and it is a TRANSFORM: a
 * single band of light travelling across the card on the compositor. Five
 * bars pulsing out of step reads as a page glitching; one light passing over
 * reads as a page loading. On a four-card list that is four animations
 * instead of twenty, all of them off the main thread.
 *
 * AND THE SHAPE IS THE SHAPE THAT IS COMING. A silhouette that matches the
 * real row means nothing jumps when the data lands — same height, same
 * padding, same gaps. A grey bar of the wrong height is a layout shift with
 * a delay on it.
 * ═════════════════════════════════════════════════════════════════════════*/
.skel{display:grid;gap:var(--s2)}
.skel[hidden]{display:none}

.skel-card{
  position:relative;overflow:hidden;isolation:isolate;
  display:grid;gap:var(--s2);align-content:start;
  /* The real cards' padding, so the wait is the same size as the answer. */
  padding:var(--s3);border-radius:var(--r2);
  background:var(--sunk);border:var(--hair);
}
.skel-card::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(100deg,transparent 20%,rgba(147,182,192,.085) 50%,transparent 80%);
  transform:translateX(-100%);
  animation:skelsweep 1.6s var(--ease) infinite;
  /* Staggered down the list, so it reads as one light moving through the
     page rather than every card flashing in unison. --i is the card's index,
     written by the one function that builds these. */
  animation-delay:calc(var(--i,0) * .13s);
}
@keyframes skelsweep{ to{transform:translateX(100%)} }

/* The parts. Nothing here has a colour of its own — one flat tint at the
   weight of text that has not arrived. */
.skel-row{display:flex;align-items:center;gap:var(--s2)}
.skel-col{display:grid;gap:var(--s1);flex:1 1 auto;min-width:0}
/* Weighted to read as "text that has not arrived" against the sunk surface
   they sit on — light enough not to be mistaken for content, dark enough to
   be visible on a phone in daylight, which .11 was not. */
.skel-line,.skel-tile,.skel-pill{background:rgba(147,182,192,.15)}
.skel-line{height:11px;border-radius:999px;width:var(--w,100%)}
.skel-line[data-s="head"]{height:18px;border-radius:var(--r1);width:var(--w,52%)}
.skel-line[data-s="micro"]{height:8px;width:var(--w,32%)}
.skel-tile{flex:none;width:var(--w,38px);height:var(--h,38px);border-radius:var(--r1)}
.skel-pill{flex:none;height:22px;width:var(--w,74px);border-radius:999px}
.skel-gap{flex:1 1 auto}

/* ── AND WHEN IT IS TAKING TOO LONG ────────────────────────────────────────
   Four seconds is the point at which a skeleton stops being informative and
   starts being a page that is broken in a tasteful way. This says so, and it
   costs no JavaScript and no timer: an opacity fade with a four-second delay
   that never runs if the skeleton is removed first.

   The delay survives reduced motion on purpose. The global rule at the top of
   this file collapses DURATION, not delay, so a visitor who asked for less
   motion still gets the sentence — at four seconds, without the fade. */
.skel-say{
  padding:var(--s3) var(--s2) 0;text-align:center;
  font-size:var(--t-small);line-height:1.6;color:var(--faint);
  opacity:0;animation:skelsay .45s var(--ease) 4s forwards;
}
@keyframes skelsay{ to{opacity:1} }

/* ── A CONTROL THAT IS BUSY ────────────────────────────────────────────────
   Hire's checkout is four network steps and the button used to report them by
   changing its own label to "Signing…", which is a button that has stopped
   looking like it is doing anything. A ring, in the button, beside the word
   that says which step this is — the label still carries the meaning and the
   ring carries the fact that it is still going.

   `rotate` on a bordered box: one composited property, no repaint, and no
   image to download. */
/* Any button, not just .btn: hire's primary control is `.go`, its own older
   class, and the point of putting this here is that a working button looks
   the same on all four views. */
button[data-busy="1"]{pointer-events:none}
button[data-busy="1"]::before{
  content:"";width:15px;height:15px;flex:none;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;
  opacity:.85;animation:btnspin .7s linear infinite;
}
@keyframes btnspin{ to{transform:rotate(360deg)} }
/* Reduced motion gets a ring that does not spin rather than no ring at all:
   the fact that the control is working is information, and it is the
   spinning that was the preference. */
@media (prefers-reduced-motion: reduce){
  button[data-busy="1"]::before{border-top-color:currentColor;opacity:.45}
}

/* ══════════════════════════════════════════════════════════════════════════
 * COPY
 *
 * Every one of these views puts strings on screen that are only useful
 * somewhere else: a 56-character did:key, a 66-character contract id, an
 * offer id, a room name, the JSON a developer is meant to post. Reading one
 * off a screen and typing it into a terminal is where a wrong character comes
 * from, and a did:key with a wrong character fails in a way that looks like
 * the site being broken.
 *
 * ONE CONTROL, so a copy is the same gesture and the same confirmation
 * wherever it appears. It is 34px because a 20px icon button is a target for
 * a cursor and a miss for a thumb, and it does not change size when it
 * succeeds — a control that grows on click moves the row underneath it.
 * ═════════════════════════════════════════════════════════════════════════*/
.copy{
  display:inline-grid;place-items:center;flex:none;
  width:34px;height:34px;padding:0;cursor:pointer;
  border-radius:var(--r1);border:1px solid transparent;
  color:var(--faint);background:transparent;
  transition:color .16s var(--ease),background .16s var(--ease),border-color .16s var(--ease);
}
.copy svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;grid-area:1/1;
  transition:opacity .18s var(--ease),transform .18s var(--ease)}
.copy .yes{opacity:0;transform:scale(.6)}
@media (hover:hover) and (pointer:fine){
  .copy:hover{color:var(--blue);background:rgba(88,233,255,.09);border-color:rgba(88,233,255,.22)}
}
.copy:active{transform:scale(.94)}
.copy:focus-visible{outline:none;box-shadow:var(--focus)}
.copy[data-done="1"]{color:var(--good);background:rgba(59,227,176,.10);border-color:rgba(59,227,176,.3)}
.copy[data-done="1"] .no{opacity:0;transform:scale(.6)}
.copy[data-done="1"] .yes{opacity:1;transform:none}
/* It failed. Clipboard access is refused often enough — an insecure origin, a
   browser setting, a permission — that "nothing happened" is the wrong
   answer; the row falls back to selecting the text so it can be copied by
   hand, and the button says which of the two just happened. */
.copy[data-done="no"]{color:var(--gold);border-color:rgba(242,179,61,.34)}

/* A long string and its button, on one line, with the string doing the
   shrinking. `min-width:0` on the text is the whole trick: without it a flex
   child refuses to go below its content width and the button is pushed off
   the end of a 360px phone. */
/* ── MIDDLE TRUNCATION ─────────────────────────────────────────────────────
   Two spans in a row: the head shrinks and clips, the tail never does. The
   browser recomputes it on every reflow, so it is right at any width, after a
   late font, and through a zoom — none of which a measured-in-script version
   would survive. See midText() in deal-ui.js. */
.midtrim{display:inline-flex;min-width:0;max-width:100%;align-items:baseline}
.midtrim .mt-head{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.midtrim .mt-tail{flex:none;white-space:nowrap}

.copyrow{display:flex;align-items:center;gap:var(--s1);min-width:0}
.copyrow>.did,.copyrow>code{flex:1 1 auto;min-width:0}

.blank{
  display:grid;gap:var(--s2);justify-items:center;text-align:center;
  padding:var(--s5) var(--s4);border-radius:var(--r3);
  background:var(--sunk);color:var(--dim);
}
.blank b{color:var(--white);font-size:var(--t-h3)}
.blank p{font-size:var(--t-small);line-height:1.6;max-width:44ch}

/* ══════════════════════════════════════════════════════════════════════════
 * THE (i)
 *
 * The disclosure control that opens an explanation, on every one of these
 * views. It was declared TWICE — byte for byte, in the deals page and in
 * hire, down to the comment about the font shorthand that was broken in both
 * because it named a custom property neither file declared. Two copies of a
 * bug is what this stylesheet exists to stop.
 * ═════════════════════════════════════════════════════════════════════════*/
.info{
  display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;
  width:17px;height:17px;margin-left:6px;border-radius:50%;flex:none;
  border:1px solid var(--edge);background:rgba(127,178,214,.08);color:var(--dim);
  font:600 10.5px/1 var(--display);cursor:pointer;padding:0;
  transition:color .18s,border-color .18s,background .18s,transform .18s;
}
.info:hover,.info[aria-expanded="true"]{color:var(--blue);border-color:var(--blue);
  background:rgba(127,178,214,.16);transform:scale(1.12)}
.info:focus-visible{outline:2px solid var(--blue);outline-offset:2px}

/* ══════════════════════════════════════════════════════════════════════════
 * THE HIT AREA IS NOT THE INK
 *
 * MEASURED, across four views and seven widths: on a phone this product had
 * eleven controls under 40 pixels, and three of them under 22 — a progress
 * dot three pixels tall, a disclosure caret twelve, a "see all" link
 * twenty-two. Every one of them is a thing somebody is meant to press with a
 * thumb, and Apple and Google both publish 44 as the floor.
 *
 * The wrong fix is to make them bigger. A 44px circle beside a sentence is
 * not an (i), it is a button with an (i) in it, and a 44px progress dot is
 * not a progress dot. So the DRAWING does not change by a pixel and the
 * TARGET grows underneath it: a transparent pseudo-element, centred on the
 * control, at least 44 square, and only where the pointer is coarse. On a
 * desktop none of this exists and a 17px (i) beside a cursor is exactly
 * right.
 *
 * ── WHY THE PAGES' OWN CLASS NAMES ARE IN THE SHARED SHEET ────────────────
 *
 * Because CSS has no way to say "and this one too" from another file, and the
 * alternative was this rule copied into three stylesheets. This file is
 * loaded by exactly three pages and by nothing else; one list that names all
 * of their small controls is one thing to keep true. A list per page is three
 * things that agree until somebody edits one.
 *
 * Adding a control here is a two-second job. Forgetting to is what the sweep
 * in scripts/probe-space.mjs is for.
 */
@media (pointer: coarse){
  .info, .copy,
  .wantbtn, .link, .raillink, .stagedots button,     /* the deals page */
  .try, .histall, .codecopy,                         /* hire */
  .hbtn{                                             /* the orders page */
    position:relative;
  }
  .info::after, .copy::after,
  .wantbtn::after, .link::after, .raillink::after, .stagedots button::after,
  .try::after, .histall::after, .codecopy::after,
  .hbtn::after{
    content:"";position:absolute;left:50%;top:50%;
    width:max(100%,44px);height:max(100%,44px);
    transform:translate(-50%,-50%);
    /* Nothing is drawn and nothing is caught that the control would not have
       caught anyway — this sits INSIDE the control, so it is the control's
       own hit area getting bigger, not a new object over the page. */
  }
  /* ── AND THE ONES WHERE THE PSEUDO DOES NOT WORK ────────────────────────
     Three of these were measured unreachable even with the box in place, for
     two different reasons, and both are reasons to stop being clever:

       · SOMETHING PAINTS OVER IT. hire's suggestion chips sit directly under
         the brief field, and the field's wrapper — positioned, and later in
         the document — covers the four pixels the chip's box reaches up
         into. A transparent target under an opaque box is not a target.
       · THE PAGE OWNS THE POSITION. `.codecopy` is absolutely positioned in
         the corner of a code block by hire's own stylesheet, which loads
         after this one and wins; the pseudo then resolves its 100% against
         the wrong box and comes out 517px tall.

     For these the honest fix is the boring one: be taller. They are real
     controls with a visible surface, so height costs nothing but height. */
  .tab, .field, .chip, .try, .search input{min-height:44px}
  .codecopy, .link{min-height:40px}
}

/* ── LONG TECHNICAL STRINGS ────────────────────────────────────────────────
   A did:key is 56 characters and a contract is 66. They appear on every one
   of these views, and on a 360px phone an untruncated one is what pushes a
   whole page sideways. */
.did{font-family:var(--mono);font-size:var(--t-small);color:var(--dim);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0}

/* ── FOCUS, EVERYWHERE, VISIBLY ────────────────────────────────────────────
   Not negotiable and not a detail: it is how the page is used by anybody not
   holding a mouse. */
a:focus-visible,button:focus-visible,summary:focus-visible,[tabindex]:focus-visible{
  outline:none;box-shadow:var(--focus);border-radius:var(--r1);
}

/* ══════════════════════════════════════════════════════════════════════════
 * POINTER LIGHT
 *
 * The site already has one pointer effect: the fixed `.spot` glow behind
 * everything, which is part of the site's atmosphere and is not touched here.
 * This is a different, LOCAL thing — a surface catching light where the
 * cursor is over it — and it is deliberately built to sit UNDER that glow
 * rather than compete with it: no blur, no colour of its own, low opacity.
 *
 * Fine pointers only. On a touch screen `--mx`/`--my` are never written, so
 * the gradient sits at its neutral position and costs nothing; the media
 * query means the listener is never even attached.
 * ═════════════════════════════════════════════════════════════════════════*/
@media (hover:hover) and (pointer:fine){
  .lit{position:relative}
  .lit::before{
    content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
    background:radial-gradient(260px circle at var(--mx,50%) var(--my,50%),
               rgba(150,235,255,.055),transparent 60%);
    opacity:0;transition:opacity .25s var(--ease);z-index:0;
  }
  .lit[data-lit="1"]::before{opacity:1}
  .lit>*{position:relative;z-index:1}
}

/* ── SCROLL REVEALS (touch, and any pointer) ───────────────────────────────
   Structure arriving, once, as you reach it. Never re-firing, and never a
   whole page of things wobbling at every small scroll — the brief is right
   that motion has to communicate, and "this section exists" is the only
   thing a reveal can honestly say. */
[data-rise]{transition:opacity .5s var(--ease),transform .5s var(--ease)}
[data-rise="out"]{opacity:0;transform:translateY(10px)}
[data-rise="in"]{opacity:1;transform:none}
@media (prefers-reduced-motion: reduce){ [data-rise="out"]{opacity:1;transform:none} }

/* ── A BOTTOM SHEET, WHICH IS WHERE A PHONE PUTS SECONDARY THINGS ──────────
   Filters, an order summary, the technical detail under a row. It opens from
   the edge the thumb is nearest and it is dismissible by the same gesture
   people already use everywhere else. Desktop never sees it. */
.sheet{
  position:fixed;left:0;right:0;bottom:0;z-index:60;
  max-height:82vh;overflow:auto;-webkit-overflow-scrolling:touch;
  padding:var(--s4) var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
  background:var(--rise-2);border-radius:var(--r4) var(--r4) 0 0;
  box-shadow:0 -24px 60px -24px rgba(0,0,0,1),var(--lip);
  transform:translateY(101%);transition:transform .32s var(--ease);
}
.sheet[data-open="1"]{transform:none}
.sheet::before{
  content:"";display:block;width:38px;height:4px;border-radius:999px;
  background:rgba(147,182,192,.35);margin:0 auto var(--s3);
}
.scrim{
  position:fixed;inset:0;z-index:59;background:rgba(0,4,6,.6);
  opacity:0;pointer-events:none;transition:opacity .32s var(--ease);
}
.scrim[data-open="1"]{opacity:1;pointer-events:auto}

/* ── A STICKY ACTION BAR ───────────────────────────────────────────────────
   The one thing a phone must never do is put the button that completes a
   flow below a screen and a half of summary. */
.dock{
  position:sticky;bottom:0;z-index:20;
  padding:var(--s3) 0 calc(var(--s3) + env(safe-area-inset-bottom));
  background:linear-gradient(180deg,transparent,var(--void) 28%);
}
@media (min-width:861px){ .dock{position:static;background:none;padding:0} }
