/* shell.css — the app shell every world wears, written ONCE (owner ruling, 13 Sep 2026).
   The frame and its safe areas, the pushed-view bar and its back chevron, the tab
   buttons, the scrim and the bottom sheet, the photo viewer and the snap gallery, the
   stamp and its error twin, the skeleton bones, the empty morning, the need line, the
   send button. These were three (sometimes five) copies that had begun to drift — the
   member app's sheet could not scroll, the office's viewer could not page — and a rule
   fixed in one world was not fixed in the others. A world keeps only what is its own:
   its tab bar's column count, its wide-screen rail, its pill's place. A world that
   redefines one of these selectors fails tools/shared-check.py. */

.ltr{direction:ltr; unicode-bidi:isolate}
.frame{
  position:relative; inline-size:100%; block-size:100%; background:var(--ground);
  overflow:hidden; display:flex; flex-direction:column;
  padding-block-start:env(safe-area-inset-top);
  padding-inline-start:env(safe-area-inset-left);
  padding-inline-end:env(safe-area-inset-right);
}
.main{position:relative; flex:1 1 auto; min-block-size:0}
.view{position:absolute; inset:0; display:flex; flex-direction:column; overflow:hidden; background:var(--ground)}
.view.entering{animation:viewIn var(--d-slow) var(--spring-soft) backwards}
@keyframes viewIn{from{transform:translateY(var(--enter-y)) scale(var(--enter-scale))} to{transform:none}}
.push{z-index:var(--z-overlay)}
.stamp{display:grid; grid-template-rows:0fr; transition:grid-template-rows var(--d-base) var(--spring-soft);
  background:var(--good-quiet); border-block-start:var(--line-w) solid var(--good-line)}
.stamp.show{grid-template-rows:1fr}
.stamp .in{display:flex; gap:var(--s3); align-items:flex-start; padding:var(--s4) var(--gutter)}
.stamp .tick{inline-size:var(--mark); block-size:var(--mark); border-radius:var(--r-pill); background:var(--good); flex:0 0 auto;
  display:grid; place-items:center; margin-block-start:var(--line-w); color:var(--accent-ink); font-size:var(--text-small); font-weight:var(--weight-heavy)}
.stamp.bad{background:var(--bad-quiet); border-block-start-color:var(--bad-line)}
.stamp.bad .tick{background:var(--bad)}
.stamp .act-in{margin-block-start:var(--s2); min-block-size:var(--tap-min); padding-inline:var(--s4); border-radius:var(--r-pill);
  background:var(--surface); border:var(--line-w) solid var(--bad-line); color:var(--bad); font-size:var(--text-small); font-weight:var(--weight-bold);
  display:inline-flex; align-items:center}
.stamp.free{border:var(--line-w) solid var(--good-line); border-radius:var(--r-in); margin:var(--s3) var(--gutter) 0; box-shadow:var(--lift-1)}
.stamp.inrow{border:var(--line-w) solid var(--good-line); border-radius:var(--r-focus)}
.bone{display:block; background:var(--sunk); border-radius:var(--r-pill); block-size:var(--s4); margin-block-end:var(--s2)}
@keyframes breatheSk{0%,100%{opacity:1} 50%{opacity:var(--skeleton-min)}}
.rowsk{display:flex; align-items:center; gap:var(--s3); padding:var(--s4)}
.skeleton > *,.rowsk{animation:breatheSk var(--d-breathe) var(--ease) infinite}
.rowsk .bone{margin:0}
.rowsk .av{inline-size:var(--avatar-md); block-size:var(--avatar-md); border-radius:var(--r-pill); background:var(--sunk); flex:0 0 auto}
.rowsk .lines{flex:1}
.rowsk .lines .bone + .bone{margin-block-start:var(--s2); inline-size:50%}
.done:where(:not(button, a)){margin-block:auto; display:flex; flex-direction:column; align-items:center; text-align:center; padding:var(--s10) var(--s6) var(--s10)}   /* the empty/finished BLOCK — never a button's done state */
.done .at{margin-block-start:var(--s8); display:flex; align-items:center; gap:var(--s2); background:var(--surface); border:var(--line-w) solid var(--line); border-radius:var(--r-pill);
  padding:var(--s3) var(--gutter); font-size:var(--text-small); font-weight:var(--weight-bold); box-shadow:var(--lift-1); min-block-size:var(--tap-min)}
.donefoot{flex:0 0 auto; padding:0 var(--gutter) var(--s10); display:grid; gap:var(--s3)}
.pushbar{display:flex; align-items:center; gap:var(--s2); padding:var(--s4) var(--gutter) var(--s3); flex:0 0 auto}
.back{display:inline-flex; align-items:center; gap:var(--s1); min-block-size:var(--tap-min);
  margin-inline-start:calc(-1 * var(--s2)); padding-inline:var(--s2); border-radius:var(--r-pill);
  font-size:var(--text-small); font-weight:var(--weight-bold); color:var(--ink)}
.back:active{background:var(--sunk)}
.back svg{inline-size:var(--icon-lg); block-size:var(--icon-lg); stroke:var(--ink); fill:none; stroke-width:var(--stroke-chevron); stroke-linecap:round; stroke-linejoin:round}
[dir="rtl"] .back svg{transform:scaleX(-1)}
.sendbtn{inline-size:var(--tap-min); block-size:var(--tap-min); border-radius:var(--r-pill); background:var(--accent); color:var(--accent-ink);
  display:grid; place-items:center; box-shadow:var(--lift-2); flex:0 0 auto; transition:background var(--d-quick) var(--ease), transform var(--d-press) var(--out)}
.scrim{position:absolute; inset:0; z-index:var(--z-toast); background:var(--scrim); opacity:0; pointer-events:none; transition:opacity var(--d-base) var(--ease)}
.scrim.show{opacity:1; pointer-events:auto}
.sheet{position:absolute; inset-inline:0; inset-block-end:0; z-index:var(--z-toast); background:var(--ground);
  border-start-start-radius:var(--r-card); border-start-end-radius:var(--r-card); box-shadow:var(--lift-3);
  padding:var(--s3) 0 max(var(--s6), env(safe-area-inset-bottom));
  transform:translateY(100%); transition:transform var(--d-base) var(--spring-soft); visibility:hidden;
  max-block-size:88%; display:flex; flex-direction:column}
.sheet.show{transform:none; visibility:visible}
.sheet .grab{inline-size:var(--s10); block-size:var(--s1); border-radius:var(--r-pill); background:var(--line-2); margin:0 auto var(--s3); flex:0 0 auto}
.need{margin-block-start:var(--s3); text-align:center; font-size:var(--text-caption); font-weight:var(--weight-regular); color:var(--ink-3); min-block-size:var(--text-caption)}
.tabbar{
  flex:0 0 auto; display:grid; z-index:var(--z-dock);
  /* NAILED TO THE BOTTOM (owner, 11 September 2026). The bar "pops upwards" on the
     desktop browser, leaving ground below it — the symptom of this flex column
     computing SHORTER than the frame, whatever makes it do so for an instant. As the
     last row, `flex:0 0 auto` alone only means "do not stretch me"; it says nothing
     about where the row sits when there is space left over. An auto start-margin eats
     every pixel of that space, so the bar is at the bottom of the frame or the layout
     is broken in a way nothing here could hide. It costs nothing when the column fills
     properly, which is almost always: an auto margin with no free space is zero. */
  margin-block-start:auto;
  background:var(--ground); border-block-start:var(--line-w) solid var(--line);
  padding:var(--s2) 0 max(var(--s2), env(safe-area-inset-bottom));
}
.tab-btn{display:flex; flex-direction:column; align-items:center; justify-content:center; gap:var(--s1);
  min-block-size:var(--control-lg); border-radius:var(--r-in); position:relative; color:var(--ink-3);
  transition:color var(--d-quick) var(--ease), transform var(--d-press) var(--out)}
.tab-btn:active{transform:scale(var(--press-scale))}
.tab-btn svg{inline-size:var(--icon-xl); block-size:var(--icon-xl); fill:none; stroke:currentColor; stroke-width:var(--stroke-chevron); stroke-linecap:round; stroke-linejoin:round}
.tab-btn span{font-size:var(--text-micro); font-weight:var(--weight-medium)}
.tab-btn[aria-selected="true"]{color:var(--ink)}
.gallery{display:flex; gap:var(--s3); padding-block:var(--s2) 0; padding-inline:var(--gutter); overflow-x:auto; scroll-snap-type:x mandatory; scroll-padding-inline:var(--gutter); -webkit-overflow-scrolling:touch}
.gallery .photo{flex:0 0 72%; border-radius:var(--r-in); scroll-snap-align:start; border:var(--line-w) solid var(--line);
  position:relative; aspect-ratio:var(--photo-ratio-card); overflow:hidden; background:var(--sunk); isolation:isolate; padding:0; cursor:zoom-in}
.gallery .photo img.fill{position:absolute; inset:0; inline-size:100%; block-size:100%; object-fit:cover; transform:scale(var(--photo-fill-scale)); filter:blur(var(--photo-fill-blur))}
.gallery .photo::before{content:""; position:absolute; inset:0; background:var(--photo-fill-dim); z-index:1}
.gallery .photo img.pic{position:absolute; inset:0; inline-size:100%; block-size:100%; object-fit:contain; z-index:2}
.viewer{position:fixed; inset:0; z-index:calc(var(--z-toast) + 1); background:var(--ink);
  display:flex; align-items:center; justify-content:center; opacity:0; pointer-events:none;
  transition:opacity var(--d-base) var(--ease); touch-action:none}
.viewer.show{opacity:1; pointer-events:auto}
.viewer .vnav{position:absolute; inset-block:50% auto; transform:translateY(-50%); block-size:var(--control-lg); inline-size:var(--control-lg); display:none; align-items:center; justify-content:center; background:none; border:0; color:var(--ink-inverse); opacity:var(--muted-opacity); cursor:pointer; z-index:1}
.viewer .vnav svg{inline-size:var(--icon-lg); block-size:var(--icon-lg); fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round}
.viewer.many .vnav{display:flex}
.viewer .vnav.prev{inset-inline-start:0}
.viewer .vnav.next{inset-inline-end:0}
.viewer .vnav:focus-visible{outline:var(--line-w-mark) solid var(--ink-inverse); outline-offset:calc(-1 * var(--s2))}
.viewer img{max-inline-size:100%; max-block-size:100%; object-fit:contain; user-select:none; -webkit-user-drag:none;
  transform:translate(var(--vx, 0px), var(--vy, 0px)) scale(var(--vz, 1)); transition:transform var(--d-quick) var(--ease); cursor:grab}
.viewer .vcap{position:absolute; inset-block-end:max(var(--s6), env(safe-area-inset-bottom)); inset-inline:0; text-align:center;
  color:var(--ink-inverse); font-size:var(--text-small); font-weight:var(--weight-bold); pointer-events:none}
.viewer .vcap small{display:block; font-size:var(--text-micro); font-weight:var(--weight-medium); opacity:var(--muted-opacity); margin-block-start:var(--line-w)}
/* a blocked primary button is live but goes nowhere; the need line names what is missing
   only after a press, and flashes when it does (the desk's rule, batch 5 #4 — every world's now) */
.go.blocked{opacity:var(--muted-opacity); cursor:default}
.need.bad{color:var(--bad); animation:needFlash var(--d-base) var(--ease)}
@keyframes needFlash{from{transform:translateY(var(--line-w-mark))} to{transform:none}}

/* A BUTTON THAT IS WORKING (owner, 14 Sep 2026): dimmed, its word followed by moving dots, dead to taps */
.busy{opacity:.6; pointer-events:none}
.busydots{display:inline-block; inline-size:1.2em; text-align:start; animation:busydots 1.2s steps(4, end) infinite}
@keyframes busydots{0%{clip-path:inset(0 100% 0 0)} 100%{clip-path:inset(0 -0.2em 0 0)}}
@media (prefers-reduced-motion: reduce){ .busydots{animation:none} }
/* A LINE ON ITS WAY: dimmed until the server has it; a line that failed keeps its place and offers Retry —
   on its own line under the words, never glued into them (hunt row 51) */
.nbub.pending,.bubble.pending{opacity:.55}
.msgretry{display:flex; inline-size:max-content; max-inline-size:100%; align-items:center; min-block-size:var(--control-sm); margin-block-start:var(--s2); padding-inline:var(--s3); border:0; border-radius:var(--r-pill); background:var(--accent); color:var(--accent-ink); font:inherit; font-weight:var(--weight-bold); cursor:pointer}
/* the keyboard is up (html.kb-open, platform.js): the tab bar steps aside so the field keeps the room */
.kb-open .tabbar{display:none}
