/* ============================================================================
   LinkkWinkk — demo app shell
   Real app, not screenshots. Full viewport, no phone chrome, safe-area aware.
   Depends on ../system/linkkwinkk.css for tokens + components.
   ========================================================================== */

html,body{height:100%;overflow:hidden;background:var(--paper);overscroll-behavior:none}
body{position:fixed;inset:0;-webkit-user-select:none;user-select:none}

/* Pinch-zoom. The shell is fixed to the *layout* viewport, which does not move
   when iOS zooms — so at scale > 1 the UI renders outside the visible rect and
   the drag that would pan it is taken by .vscroll instead. The app then looks
   frozen and clipped on both edges, which is what the 9 Sep recording shows.
   Transforming body makes every fixed descendant resolve against it, so the
   whole shell tracks the visual viewport and simply reflows narrower. */
body.vv{inset:auto;left:0;top:0;width:var(--vvw);height:var(--vvh);
        transform:translate(var(--vvx),var(--vvy));transform-origin:0 0}

/* Narrow viewports. Nothing here was ever designed below 320px, because no phone
   is — but a zoomed page is: at scale 2.2 the shell above becomes a 177px
   viewport, and the pass card's headline ran out of its own box while the two
   stat columns overlapped. Zoom is meant to make type bigger, so this trims the
   display sizes rather than the body: the reader still nets a large gain and the
   layout survives. `.inp` keeps its 16px floor through the `max()` in the fields
   block below, so shrinking `--t-l` cannot reintroduce iOS focus-zoom.

   Two selectors, because a media query cannot see this. `max-width` is measured
   against the *layout* viewport, which does not change when the page is zoomed —
   so the media query alone would never fire on the case it was written for.
   `body.narrow` is set from `visualViewport.width` in app.js; the media query
   stays for devices that really are this narrow. */
/* The commuter pass and the tab bar, which are what actually broke. Both rows
   carried their layout inline, and an inline `flex:1;min-width:0` cannot be
   overridden from a stylesheet without `!important` — so they are classes now
   and the inline layout is gone. */
.passtop{padding:var(--s5) var(--s5) var(--s4);display:flex;gap:var(--s4);align-items:center}
.passstats{padding:var(--s4) var(--s5);display:flex;gap:var(--s3)}
.passstats .ps1{flex:1 1 0;min-width:0}
.passstats .ps2{flex:2 1 0;min-width:0}
.tab .tl{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

body.narrow{--t-l:17px; --t-xl:20px; --t-xxl:26px;
            --s5:18px; --s6:24px; --s7:36px}
body.narrow .passtop{flex-wrap:wrap;gap:var(--s3)}
body.narrow .passstats{flex-wrap:wrap;gap:var(--s2)}
body.narrow .passstats .ps1,
body.narrow .passstats .ps2{flex-basis:100%}
body.narrow .tabs{padding-left:0;padding-right:0}
body.narrow .tab{font-size:8.5px;letter-spacing:0;gap:2px;min-width:0;padding:0 2px}
body.narrow .tab svg{width:20px;height:20px}
/* Fixed-pixel compositions. The wink stage is 230px of overlapping rings and the
   pill tags do not wrap, so both ran past a 177px viewport. */
body.narrow .winkstage{width:150px;height:98px}
body.narrow .wring{width:98px;height:98px;border-width:10px}
body.narrow .tag{max-width:100%;white-space:normal;overflow-wrap:anywhere}
body.narrow .srow{flex-wrap:wrap}

/* Wide viewports — which on a phone means landscape. iOS Safari ignores the
   manifest's portrait lock, so the app rotates whatever the manifest asks, and
   the answer is to look deliberate when it does rather than to keep asking. Left
   alone the pass card stretched into a 2532px band. The column is capped and
   centred instead; nothing is hidden and nothing moves on rotation but the
   gutters. Also covers a tablet and a desktop browser, which testers use. */
@media (min-width:520px){
  /* The scrolling column is capped and centred. The tab bar and the header are
     chrome and stay full-bleed — a floating band with visible edges reads as a
     broken panel, not a considered one — so they take the same cap as padding
     and their contents line up with the column above them. */
  .vscroll.vpad,.vbottom{max-width:520px;margin-inline:auto;width:100%}
  .tabs,.apphead,.navbar{padding-inline:max(var(--s3),calc((100% - 520px) / 2))}
}

@media (max-width:340px){
  :root{--t-l:17px; --t-xl:20px; --t-xxl:26px;
        --s5:18px; --s6:24px; --s7:36px}
  .passtop{flex-wrap:wrap;gap:var(--s3)}
  .passstats{flex-wrap:wrap;gap:var(--s2)}
  .passstats .ps1,.passstats .ps2{flex-basis:100%}
  .tabs{padding-left:0;padding-right:0}
  .tab{font-size:8.5px;letter-spacing:0;gap:2px;min-width:0;padding:0 2px}
  .tab svg{width:20px;height:20px}
  .winkstage{width:150px;height:98px}
  .wring{width:98px;height:98px;border-width:10px}
  .tag{max-width:100%;white-space:normal;overflow-wrap:anywhere}
  .srow{flex-wrap:wrap}
}

/* ── viewport --------------------------------------------------------- */
#app{position:fixed;inset:0;overflow:hidden;
     --safeT:env(safe-area-inset-top,0px);
     --safeB:env(safe-area-inset-bottom,0px)}

.view{position:absolute;inset:0;display:flex;flex-direction:column;
      background:var(--paper);will-change:transform,opacity;
      padding-top:var(--safeT);
      transform:translateZ(0)}
.view.dark{background:var(--teal-ink)}

/* ── transitions ------------------------------------------------------- */
/* push: incoming slides from the right, outgoing parallaxes left + dims   */
@keyframes pushIn   {from{transform:translateX(100%)}            to{transform:none}}
@keyframes pushOut  {from{transform:none}                        to{transform:translateX(-24%);filter:brightness(.82)}}
@keyframes popIn    {from{transform:translateX(-24%);filter:brightness(.82)} to{transform:none;filter:none}}
@keyframes popOut   {from{transform:none}                        to{transform:translateX(100%)}}
/* sheet: rises from the bottom                                            */
@keyframes sheetIn  {from{transform:translateY(100%)}            to{transform:none}}
@keyframes sheetOut {from{transform:none}                        to{transform:translateY(100%)}}
/* tabs: crossfade with a whisper of lift                                  */
@keyframes fadeIn   {from{opacity:0;transform:translateY(8px)}   to{opacity:1;transform:none}}
@keyframes fadeOut  {from{opacity:1}                             to{opacity:0}}

.view.push-in  {animation:pushIn   .34s cubic-bezier(.32,.72,0,1) both}
.view.push-out {animation:pushOut  .34s cubic-bezier(.32,.72,0,1) both}
.view.pop-in   {animation:popIn    .30s cubic-bezier(.32,.72,0,1) both}
.view.pop-out  {animation:popOut   .30s cubic-bezier(.32,.72,0,1) both}
.view.sheet-in {animation:sheetIn  .38s cubic-bezier(.32,.72,0,1) both}
.view.sheet-out{animation:sheetOut .30s cubic-bezier(.4,0,1,1)    both}
.view.fade-in  {animation:fadeIn   .26s var(--ease) both}
.view.fade-out {animation:fadeOut  .18s var(--ease) both}
.view.hidden{display:none}

@media (prefers-reduced-motion:reduce){
  .view[class*="-in"],.view[class*="-out"]{animation-duration:.01s}
}

/* content scrolls; chrome does not */
.vscroll{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
.vscroll::-webkit-scrollbar{display:none}
.vpad{padding:0 var(--s5)}
.vbottom{flex:none;padding:var(--s3) var(--s5) calc(var(--s5) + var(--safeB))}
.tabs{padding-bottom:calc(var(--s3) + var(--safeB))}

/* ── splash ------------------------------------------------------------ */
#splash{position:fixed;inset:0;z-index:100;background:var(--teal-ink);
        display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s5);
        transition:opacity .5s var(--ease),visibility .5s}
#splash.gone{opacity:0;visibility:hidden}
/* The real logo, 8 September 2026. This was two CSS circles in teal and coral
   with a dot between them — a stand-in drawn before there was a logo. The mark
   carries its own wordmark, so the separate `.swd` line is gone with it.

   Width is set on the element and the file is 512px square, so the box is
   reserved before the image decodes and nothing on the splash shifts. */
.smark{width:132px;height:132px;border-radius:26px;overflow:hidden;
  box-shadow:0 10px 34px rgba(0,0,0,.28);animation:smIn .55s var(--pop) both}
.smark img{display:block;width:100%;height:100%}
@keyframes smIn{from{transform:scale(.86);opacity:0}to{transform:none;opacity:1}}
.sbarline{width:150px;height:3px;border-radius:2px;background:rgba(251,247,244,.16);
     overflow:hidden;position:relative;animation:fadeIn .4s 1s both}
.sbarline i{position:absolute;inset:0;width:38%;border-radius:2px;background:var(--coral);
     animation:travel 1.15s var(--ease) infinite}
@keyframes travel{0%{left:-38%}100%{left:100%}}

/* ── real inputs ------------------------------------------------------- */
/* 16px is a floor, not a taste. Below it, iOS Safari zooms the viewport when a
   field takes focus and does not zoom back — and now that pinch-zoom is allowed
   again (index.html), nothing suppresses that any more. Four fields carried an
   inline 15px override and have had it removed rather than being exempted. */
.inp{width:100%;min-height:44px;border:none;outline:none;background:transparent;
     font-family:var(--body);font-size:var(--t-l);font-weight:600;color:var(--ink);
     -webkit-user-select:text;user-select:text}
.inp,.sel{font-size:max(16px,var(--t-l))}
.inp::placeholder{color:var(--faint);font-weight:500}

/* A native <select>, because the industry list is fifteen entries deep and the
   sub-industry list ten: a chip row that long scrolls sideways past the edge of
   a phone and hides most of its own options. The native control gets the
   platform's own picker — a full-height wheel on iOS — which is the one list
   widget a member already knows how to use.
   `appearance:none` strips the platform chrome so the field matches the text
   inputs beside it; the chevron is drawn by `.selectfield::after` instead,
   marked `pointer-events:none` so tapping it still opens the picker.
   Font size is a literal 16px rather than a type token: below 16px iOS Safari
   zooms the viewport when the control takes focus, and the page does not zoom
   back out. The viewport meta currently blocks that zoom, but relying on
   `user-scalable=no` to hide a sizing bug breaks the moment pinch-zoom is
   allowed back — which accessibility work will do. */
.selectfield{position:relative}
.selectfield::after{content:'';position:absolute;right:2px;top:50%;
  width:8px;height:8px;margin-top:-6px;pointer-events:none;
  border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);
  transform:rotate(45deg)}
.sel{width:100%;min-height:44px;padding:0 22px 0 0;border:none;outline:none;
     background:transparent;appearance:none;-webkit-appearance:none;
     font-family:var(--body);font-size:16px;font-weight:600;color:var(--ink);
     border-radius:0}
.sel:disabled{color:var(--faint)}
.sel:focus-visible{box-shadow:inset 0 0 0 2px var(--teal);border-radius:var(--r2)}
.field.live{cursor:text;transition:box-shadow .2s var(--ease)}
.field.live:focus-within{box-shadow:inset 0 0 0 2px var(--teal)}
.field.live.bad{box-shadow:inset 0 0 0 2px var(--danger)}
.prefix{font-size:var(--t-l);font-weight:600;color:var(--muted);flex:none;margin-right:var(--s2)}

.otpwrap{position:relative}
.otpwrap input{position:absolute;inset:0;opacity:0;font-size:16px;
  -webkit-user-select:text;user-select:text}
.otp i{transition:box-shadow .18s var(--ease),transform .18s var(--ease)}
.otp i.fill{box-shadow:inset 0 0 0 2px var(--teal);transform:translateY(-2px)}
.otp i.bad{box-shadow:inset 0 0 0 2px var(--danger);color:var(--danger);animation:shake .4s}
@keyframes shake{0%,100%{transform:none}25%{transform:translateX(-5px)}75%{transform:translateX(5px)}}
.caret{display:inline-block;width:2px;height:26px;background:var(--teal);
  animation:blink 1s steps(2) infinite;vertical-align:middle}
@keyframes blink{50%{opacity:0}}

/* composer input */
.composer .inp{min-height:48px}

/* ── toast ------------------------------------------------------------- */
#toast{position:fixed;left:50%;bottom:calc(96px + var(--safeB));transform:translate(-50%,14px);
  background:var(--teal-ink);color:var(--on-dark);border-radius:var(--rp);
  padding:11px 18px;font-size:var(--t-s);font-weight:600;z-index:90;
  opacity:0;pointer-events:none;transition:opacity .26s var(--ease),transform .26s var(--ease);
  box-shadow:var(--e2);max-width:88vw;text-align:center}
#toast.on{opacity:1;transform:translate(-50%,0)}
#toast b{color:var(--coral)}

/* ── press feedback ---------------------------------------------------- */
.btn,.act,.chip,.pick,.tab,.iconbtn,.row,.card,.conv,.srow,.metc{
  -webkit-tap-highlight-color:transparent}
.btn:active,.act:active,.chip:active{transform:scale(.97)}
.pick:active,.row:active,.card.tapme:active{transform:scale(.99)}
.btn,.act,.chip,.pick,.row,.card.tapme{transition:transform .12s var(--ease),
  background .18s var(--ease),box-shadow .18s var(--ease)}

/* ── wink stage (live, not a screenshot) -------------------------------- */
.winkstage{position:relative;width:230px;height:150px;margin:0 auto var(--s5)}
.wring{position:absolute;top:0;width:150px;height:150px;border-radius:50%;border:15px solid}
.wring.you{left:0;border-color:var(--teal)}
.wring.them{right:0;border-color:var(--coral)}
.wring .fc{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:96px;height:96px;border-radius:50%;overflow:hidden}
.wspark{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) scale(0);
  width:17px;height:17px;border-radius:50%;background:var(--on-dark);z-index:3;
  box-shadow:0 0 0 7px rgba(251,247,244,.14),0 0 34px 6px rgba(244,81,44,.75)}
.view.playing .wring.you {animation:slideL .8s var(--ease) both}
.view.playing .wring.them{animation:slideR .8s var(--ease) both}
.view.playing .wspark    {animation:smP .5s .78s var(--pop) both}
.view.playing .wfade     {animation:fadeIn .55s 1s both}
@keyframes slideL{from{transform:translateX(-46px);opacity:0}to{transform:none;opacity:1}}
@keyframes slideR{from{transform:translateX(46px);opacity:0}to{transform:none;opacity:1}}

/* map line draw-in */
.view.playing .ln.draw{stroke-dasharray:520;stroke-dashoffset:520;
  animation:draw 1.1s var(--ease) .15s forwards}
@keyframes draw{to{stroke-dashoffset:0}}
.view.playing .newnode{animation:smP .5s .75s var(--pop) both}

/* typing indicator */
.typing{display:inline-flex;gap:4px;align-items:center;padding:var(--s3) var(--s4)}
.typing i{width:7px;height:7px;border-radius:50%;background:var(--faint);animation:bob 1.1s infinite}
.typing i:nth-child(2){animation-delay:.15s}
.typing i:nth-child(3){animation-delay:.3s}
@keyframes bob{0%,60%,100%{transform:none;opacity:.5}30%{transform:translateY(-5px);opacity:1}}

/* chat */
.thread{flex:1;display:flex;flex-direction:column;justify-content:flex-end;gap:var(--s3);
  padding:var(--s4) var(--s5) var(--s3);overflow-y:auto}
.bub{max-width:78%;padding:var(--s3) var(--s4);border-radius:var(--r3);
  font-size:var(--t-m);line-height:1.45;animation:fadeIn .3s var(--ease) both}
.bub.me{align-self:flex-end;background:var(--teal-deep);color:#fff;border-bottom-right-radius:6px}
.bub.them{align-self:flex-start;background:var(--sheet);box-shadow:var(--e1);border-bottom-left-radius:6px}
.bub.sug{align-self:flex-start;background:transparent;box-shadow:inset 0 0 0 1px var(--line);
  border-style:dashed;color:var(--teal-deep);font-weight:600;cursor:pointer}
.daysep{align-self:center;font-size:var(--t-xs);font-weight:700;color:var(--muted);
  background:var(--sunk);padding:4px 12px;border-radius:var(--rp)}
.composer{flex:none;display:flex;gap:var(--s2);align-items:center;
  padding:var(--s3) var(--s5) calc(var(--s4) + var(--safeB))}
.composer .box{flex:1;min-height:48px;border-radius:var(--rp);background:var(--sheet);
  box-shadow:inset 0 0 0 1px var(--line);display:flex;align-items:center;padding:0 var(--s4)}
.composer .snd{width:48px;height:48px;border-radius:50%;background:var(--teal);
  display:grid;place-items:center;flex:none}
.composer .snd svg{width:20px;height:20px;stroke:#fff;fill:none;stroke-width:2}

/* met rail + conversations (from decks) */
.met{display:flex;gap:var(--s3);overflow-x:auto;padding:0 var(--s5) var(--s2)}
.met::-webkit-scrollbar{display:none}
.metc{flex:none;width:104px;background:var(--sheet);border-radius:var(--r3);
      box-shadow:var(--e1);padding:var(--s3);text-align:center}
.conv{display:flex;gap:var(--s3);align-items:center;padding:var(--s3) var(--s5)}
.conv + .conv{box-shadow:inset 0 1px 0 var(--line)}
.unread{width:9px;height:9px;border-radius:50%;background:var(--coral);flex:none}
.pick{display:flex;gap:var(--s3);align-items:flex-start;padding:var(--s4);
      background:var(--sheet);border-radius:var(--r3);box-shadow:var(--e1)}
.pick.on{background:var(--teal-tint);box-shadow:inset 0 0 0 2px var(--teal)}
.pick .ic{width:44px;height:44px;border-radius:var(--r2);background:var(--sunk);
      display:grid;place-items:center;flex:none;color:var(--teal-deep)}
.pick.on .ic{background:var(--teal);color:#fff}
.radio{width:26px;height:26px;border-radius:50%;box-shadow:inset 0 0 0 2px var(--line);
      flex:none;margin-top:2px;display:grid;place-items:center}
.pick.on .radio{background:var(--teal);box-shadow:none}
.radio svg{width:14px;height:14px;stroke:#fff;stroke-width:3;fill:none;opacity:0}
.pick.on .radio svg{opacity:1}
.srow{display:flex;gap:var(--s3);align-items:center;padding:var(--s4) 0}
.srow + .srow{box-shadow:inset 0 1px 0 var(--line)}
.sic{width:44px;height:44px;border-radius:var(--r2);display:grid;place-items:center;flex:none}
.stars{display:flex;gap:var(--s2);justify-content:center}
.stars button{width:48px;height:48px;display:grid;place-items:center}
.stars svg{width:36px;height:36px;stroke:var(--teal);stroke-width:1.6;fill:none;
  transition:fill .18s var(--ease),transform .18s var(--pop)}
.stars button.on svg{fill:var(--teal);transform:scale(1.08)}
.i{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2;
   stroke-linecap:round;stroke-linejoin:round}

/* Floating screen index — a build tool, not a product control.
   Hidden by default from 8 September 2026: it sat over the tab bar on every
   screen and testers read it as a menu that did nothing they wanted. It is still
   in the DOM and still works, because jumping straight to a screen is how this
   app is reviewed; add ?dev=1 to the URL to bring it back. */
#idxBtn{display:none}
body.dev #idxBtn{position:fixed;right:14px;bottom:calc(88px + var(--safeB,0px));width:46px;height:46px;
  border-radius:50%;border:none;background:rgba(11,59,59,.9);color:#FBF7F4;font-size:19px;
  z-index:80;box-shadow:var(--e2);display:grid;place-items:center;backdrop-filter:blur(8px)}
#idxBtn:active{transform:scale(.93)}


/* menu clearance — nothing should sit under the floating index button */
.view .vscroll > :last-child{scroll-margin-bottom:64px}
.view .vscroll{padding-bottom:var(--s6)}
.view .empty{padding-bottom:var(--s7)}

/* Corridor screen with a real basemap. The illustration sizes the panel by its
   own height, so once it is hidden the panel needs one. The map fills everything
   above the 60px footer band, which stays teal-ink so its light type still reads;
   the dot texture belongs to the illustration, not to a street map. */
.map.has-map{height:clamp(300px,82vw,420px)}
.map.has-map svg{display:none}
.map.has-map::after{content:none}
.map.has-map .lwmap{position:absolute;top:0;left:0;right:0;bottom:60px;height:auto;z-index:1}
