/* NALA staff UI, second dress. See STYLEGUIDE.md and mock-style.html.

   WHY THIS IS A SECOND FILE AND NOT AN EDIT TO nala-ui.css
   --------------------------------------------------------
   The dress changes shared tokens, and nala-ui.css is linked by thirteen
   pages. Editing it restyles all of them in one publish, on a live system,
   with no way to move one page at a time. So the new dress is opt in: a
   page joins it by adding `ui2` to its body class and linking this file,
   and every page that has not joined is untouched, byte for byte.

   This file is a MIGRATION SEAM, not a permanent second stylesheet. When
   the last page carries `ui2`, its contents move into nala-ui.css, the
   class goes, and this file is deleted. Until then, two dresses is the
   price of moving five pages instead of twenty two at once.

   It is also SELF SUFFICIENT on purpose. nala-ui.css turned out to be a
   layout system as well as a control sheet: it sets body to a flex column
   and takes its colours from --ctl-* tokens that only exist on a body
   carrying tier-app or tier-print, so a page that linked it for the
   hamburger alone got a reflow, and a page without a tier class drew a
   menu button nobody could see. That is why tag.html, flags.html and
   publish.html each hand copied the nav instead. Nothing here needs a tier
   class, so those copies can go.                                      */

/* The reset every page already carries, identical in all twenty of them. */
*{ margin:0; padding:0; box-sizing:border-box; }

/* The type keeps its size when the phone turns. The owner, 19 Oct: "When
   tipped to landscape and zooms in then when tipping back to portrait it
   stays zoomed in no way to get out of that state". Not a zoom: an iPhone
   enlarges, on its own, the text of a column wider than the phone is held
   upright, and on its side the page's column is. Chat's bubbles, its
   villa line and "Sends by SMS" grew while its name, box and bar did not,
   and turning back never shrank them: the page held them until it was
   opened afresh. 100%, not none, which has stopped a browser's own text
   zoom. nala-ui.css says it again for the pages without this sheet, the
   guest pages each say it themselves, and pages_suite holds them to it. */
html{ -webkit-text-size-adjust:100%; text-size-adjust:100%; }

/* ── the colour law · values unchanged from nala-ui.css and the boards ──
   A dress may say what a control looks like. It may not say what a colour
   means. Every value below is the one the live boards use today.      */
:root{
  --law-green:#5E7D67;  --law-green-t:rgba(122,160,130,.26);
  --law-green-b:rgba(122,160,130,.65);
  --law-terra:#9E6455;  --law-terra-t:rgba(184,106,90,.16);
  --law-terra-b:rgba(184,106,90,.45);
  --law-amber:#F6EAD5;  --law-amber-b:#C29A55;  --law-amber-ink:#8A6A2F;
  --law-red:#A8321E;
  /* Queued: a menu written ahead for its night, waiting for the chef or
     the admin to publish it on the day. The owner, 5 Oct: "Amber is the
     colour of an unpublished menu so perhaps a light blue should be for
     queued". Ink 6:1 on its fill. */
  --law-queued:#E6EEF8; --law-queued-b:#7FA3CF; --law-queued-ink:#2D5A8E;
  --law-wait:rgba(28,28,26,.045);
  --law-save-bg:#E4EDE2; --law-save-bdr:#7E937A; --law-save-ink:#5E7D67;
}

body.ui2{
  /* ── ground and ink ────────────────────────────────────────────────
     White rather than cream: these are tools, and every tool the owner
     pointed at (Drive, Earth, Zoom) sits on white with one accent.   */
  --ground:#FFFFFF;
  --card:#F5F6F8;          /* a quiet fill for secondary buttons and cards */
  --ink:#1B1B19;
  /* #999990 measured 2.69:1 on cream. WCAG AA wants 4.5:1 for text this
     size, and this token carries most of the app's secondary text at
     11px. #5F5F58 is 4.9:1 on white and keeps the warm grey.         */
  --mid:#5F5F58;
  --rule:#E6E6E1;

  /* ── one accent, and it means one thing ────────────────────────────
     "You can act here, or this is selected." Blue because it is the
     only hue not already spoken for: sage is done, terracotta is
     declined, amber is chase this, red is failed. An accent that
     borrowed one of those would put interaction and state in the same
     channel, which is what the boards already suffer from.          */
  --accent:#1A66C2;
  --accent-ink:#FFFFFF;
  --accent-soft:#E8F0FE;

  /* ── what a SELECTED pill looks like ───────────────────────────────
     Ruled by the owner, 29 Aug: a selected pill gives grey feedback,
     not black and not blue. Before this it was blue on six pages, ink
     on five and green on two, and a black circle sat beside a blue
     segment on the same sheet.

     Grey, and deliberately not the colour law's waiting grey, which is
     a fifth of this and carries no border: this is a control saying
     "you picked me", not a record saying "sent, waiting". The ink
     border is what does the work - a dark edge reads as chosen at a
     glance without spending a colour that means something.          */
  --sel-bg:rgba(28,28,26,.10);
  --sel-bdr:var(--ink);

  /* ── scales · pick from these, never by eye ────────────────────────
     Spacing is a 4px grid, which is what Material, Bootstrap and Apple
     all land on. The type scale is the styleguide's own 8/11/15/20/27
     with the bottom two steps raised: 8px and 9px text is not density,
     it is unreadable, and it is most of why the boards look busy.   */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px;
  --t1:12px; --t2:14px; --t3:16px; --t4:20px; --t5:27px;
  --r:8px;            /* every control and card */
  --pill:999px;       /* chips and status pills only */
  --tap:44px;         /* Apple HIG minimum; Material says 48 */
  /* The one page width, owned here (rule 1, CLAUDE.md). The app is phone-
     first and does not grow to fill a desktop screen: every ui2 page sits
     in a single centred column of this width, so a PC shows the same column
     a phone does instead of a different width per page. It was fanned out as
     `max-width:520px` in ten pages and drifted - 430 on the guest profile,
     absent (full-bleed) on spa, invitations, the SMS pages and the boards -
     which is the inconsistency this closes. Change it here, once.
     --appw is the CONTENT width, matching the old `.wrap { max-width:520 }`
     which carried no padding. --appgut is the side gutter the pages put on
     body; the frame cap below adds it back so the padding sits OUTSIDE the
     column and the content stays 520, not 520-minus-two-gutters. Getting
     that wrong shrank every page by a gutter and cost the Cleans board a
     pixel on its landscape badge. */
  --appw:520px;
  --appgut:18px;

  --ui-font:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,
            Helvetica,Arial,sans-serif;

  /* ── aliases, transitional ─────────────────────────────────────────
     A converted page keeps its own component rules, and those rules ask
     for the token names the old sheet used. Rather than find-and-replace
     hundreds of them and risk a typo per page, the old names are kept
     here pointing at the new values. An undefined custom property does
     not fall back, it computes to nothing: var(--cream) with no --cream
     is a transparent background and var(--ink) is inherited text, which
     is exactly how the hamburger came to be invisible. These go with the
     seam, when the last page has joined and the names can be swept.   */
  --cream:var(--ground);
  --paper:var(--ground);
  /* --green and --greent are deliberately NOT aliased. --green is #E4EDE2,
     a pale fill, on five pages and #5E7D67, a dark ink, on two. Claiming
     one of those meanings in the shared sheet would repeat exactly the
     fault this work exists to fix, and would turn the Cleans board's ready
     tint into a dark green. The pages keep their own until the name is
     split properly, which is the de-aliasing job still outstanding.    */
  --green-t:var(--law-green-t);  --green-b:var(--law-green-b);
  --greenb:var(--law-save-bdr);
  --resgreen:var(--law-green);   --resgreen-t:var(--law-green-t);
  --resgreen-b:var(--law-green-b);
  --terra:var(--law-terra);      --terra-t:var(--law-terra-t);
  --terra-b:var(--law-terra-b);
  --resterra:var(--law-terra);   --resterra-t:var(--law-terra-t);
  --resterra-b:var(--law-terra-b);
  --amber:var(--law-amber);      --amberb:var(--law-amber-b);
  --ambert:var(--law-amber-ink); --amberink:var(--law-amber-ink);
  --red:var(--law-red);
  --save-bg:var(--law-save-bg);  --save-bdr:var(--law-save-bdr);
  --save-ink:var(--law-save-ink);
  --num-font:var(--ui-font);
  /* nala-ui.css's tier tokens, so a page that links both sheets and still
     has rules reaching for --ctl-* keeps drawing while it is converted. */
  --ctl-bg:var(--ground); --ctl-bdr:var(--rule); --ctl-ink:var(--ink);
  --ctl-soft:#454540;

  background:var(--ground);
  color:var(--ink);
  font-family:var(--ui-font);
  -webkit-tap-highlight-color:transparent;
  /* Double tap to zoom is a hazard on a board of tap targets. This turns
     that gesture off. Pinch zoom is off too, by each page's viewport
     meta (user-scalable=no): the owner, 6 Oct, "only the live menu
     page is pinched zoom". pages_suite holds every page to it.      */
  touch-action:manipulation;
}

/* ── paper ───────────────────────────────────────────────────────────
   Ruled by the owner, 27 Sep, off mock-paper.html: "Use the same as this
   app", holding up the Claude app. Its ground, sampled from his
   screenshot, is #F9F9F7; its quiet pills #F0EFEC; its hairline
   #E1E1DF. Seen built, on his phone, the same day: "The paper colour is
   probably a little bit too dark." His phone drew #F9F9F7 exactly, so
   the value moved, not the sampling: two steps lighter, the same warmth,
   #FBFBF9. Shown three lighter beside it (#FCFCFA, #FDFDFB, #FEFEFC),
   he kept this one. A page joins with one class, `paper`, beside `ui2`, and
   leaves by dropping it: eleven pages wear it, the rest stay white.

   His first objection is the rule that matters: "Cards and box
   backgrounds are transparent so there is no contrast." On paper every
   card, row and tile is an opaque --surface, lifted by --lift, and a
   colour-law tint is drawn OVER that white as a gradient layer:
     background: linear-gradient(<tint>, <tint>), var(--surface);
   so each state is the same colour it always was and the law's own
   rgba values stay the ones in the stylesheet. A box that keeps a
   transparent fill shows the paper through it; paper_suite fails the
   page by name when one does, in its sheets and menus as well.        */
body.ui2.paper{
  --ground:#FBFBF9;
  --card:#F0EFEC;
  --rule:#E1E1DF;
  --surface:#FFFFFF;
  --lift:0 1px 2px rgba(28,28,26,.06), 0 2px 10px rgba(28,28,26,.035);
}
/* Outlined buttons sit on the paper as white, like the app's own, and so
   does the menu, which floats over the page. */
body.ui2.paper .btn.ghost, body.ui2.paper .navdrop{ background:var(--surface); }

/* ── sentence case, everywhere ───────────────────────────────────────
   The loudest single difference between this dress and the last. Not
   one label in Drive, Earth or Zoom is uppercase or letterspaced; the
   look dates to Material 1 and Bootstrap 3. Where a page still asks for
   caps, this overrides it rather than requiring every rule be found. */
body.ui2 .seclabel, body.ui2 .stat-l, body.ui2 .legend,
body.ui2 .btn, body.ui2 .sec > span, body.ui2 .sec a,
body.ui2 .dnav.today, body.ui2 h1, body.ui2 .date{
  text-transform:none; letter-spacing:0;
}

/* ── page frame ──────────────────────────────────────────────────────
   Deliberately NOT a flex column. nala-ui.css makes body one, which is
   the rule that reflowed pages that only wanted a button.           */
/* Page padding is the PAGE's business and is not set here. It was, and at
   body.ui2, which outranks every `body { padding-top: ... }` a page has
   inside a media query: the Cleans board's landscape rule asks for 6px at
   the top so seventeen tiles fit a 320px-tall screen, and a sheet-level
   shorthand quietly held it at 24 and pushed the grid off the screen.  */
/* The width IS set here, and only here (--appw above). The column is centred
   so a wide screen gets margins, not a stretched page. The cap is the content
   width PLUS the two side gutters, because box-sizing is border-box and the
   pages' side padding lives on body: cap the content width alone and that
   padding eats into it, leaving 520-minus-two-gutters of content and a page a
   gutter narrower than the old `.wrap`. Adding the gutters back keeps the
   content at --appw exactly, gutters outside it, matching the wrap pages.
   Full-bleed chrome is exempt on purpose: .bar/.selbar/.savebar and the
   .backdrop/.cardov overlays are position:fixed to the viewport, so this cap
   never reaches them - only their inner column follows --appw. A page that
   wants its own frame (guest, the boards) reads --appw too; none restates the
   number. */
body.ui2{ max-width:calc(var(--appw) + 2*var(--appgut)); margin-inline:auto; }
body.ui2 h1{ font-size:var(--t4); font-weight:600; margin-bottom:var(--s1); }
body.ui2 .lede{ font-size:var(--t2); color:var(--mid); line-height:1.55;
                margin-bottom:var(--s2); max-width:58ch; }
body.ui2 .seclabel{ font-size:var(--t2); font-weight:500; color:var(--mid);
                    margin:var(--s5) 0 var(--s2); }
body.ui2 .secnote{ font-size:var(--t2); color:var(--mid); line-height:1.5;
                   margin-bottom:var(--s3); max-width:58ch; }

/* ── header row ──────────────────────────────────────────────────────
   Same shape as today: one row, in flow, controls at 40px, menu right.
   Floating corner menus stay banned.                                 */
body.ui2 .daterow{ display:flex; align-items:center; gap:var(--s2);
                   margin-bottom:var(--s4); }
/* --t3, not --t4. At 20px "Sat 29th Aug" is 151px wide, and on a 320pt
   screen that leaves too little for Today, the two arrows and the menu:
   the hamburger was pushed 6px off the right edge. A date can never trim
   - the trim rule says so - so it has to fit instead. 16px is still a
   clear step up from the 13px it was before the dress.               */
body.ui2 .date{ flex:1; font-size:var(--t3); font-weight:600; }
/* 36px, not the 44px tap floor: the header law in STYLEGUIDE.md rules that
   every control in this row is 36 and tally_suite measures it. The project's
   own rule outranks the general one.                                      */
body.ui2 .dnav{ width:36px; height:36px; display:inline-flex; align-items:center;
                justify-content:center; border:0; background:var(--card);
                border-radius:var(--pill); color:var(--ink); cursor:pointer;
                font-family:var(--ui-font); font-size:var(--t3); }
body.ui2 .dnav.today{ width:auto; padding:0 var(--s3); font-size:var(--t2); }
/* The corner law: the footer row sits hard against the bottom of the screen,
   so it squares off, and only the outer lower corners round. body.ui2 .btn
   outranks .foot .btn in nala-ui.css, so the law has to be restated here or
   the row grows four rounded corners and floats.                          */
body.ui2 .foot .btn{ border-radius:0; }
body.ui2 .foot .btn:first-child{ border-bottom-left-radius:var(--r); }
body.ui2 .foot .btn:last-child { border-bottom-right-radius:var(--r); }
body.ui2 .dnav:disabled{ opacity:.35; cursor:default; }
body.ui2 .dnav:active{ background:var(--accent-soft); }

/* ── the hamburger and its menu ──────────────────────────────────────
   Written against real colours, not --ctl-*, so a page with no tier
   class still draws it. That was the whole reason for the copies.   */
body.ui2 .navwrap{ position:relative; margin-left:auto; z-index:500; }
body.ui2 .navbtn{ width:42px; height:36px; border:0; background:transparent;
                  border-radius:var(--pill); display:flex; flex-direction:column;
                  align-items:center; justify-content:center; gap:4px;
                  cursor:pointer; -webkit-tap-highlight-color:transparent; }
body.ui2 .navbtn:active{ background:var(--card); }
body.ui2 .navbtn span{ display:block; width:17px; height:1.7px;
                       background:var(--ink); border-radius:2px; }
body.ui2 .navdrop{ display:none; position:absolute; right:0; top:42px;
                   background:var(--ground); border:1px solid var(--rule);
                   border-radius:var(--r); min-width:200px; padding:var(--s1) 0;
                   box-shadow:0 2px 6px rgba(0,0,0,.06),0 10px 28px rgba(0,0,0,.10); }
body.ui2 .navdrop.open{ display:block; }
body.ui2 .navdrop a{ display:flex; align-items:center; gap:var(--s3);
                     padding:var(--s3) var(--s4);
                     font-size:var(--t2); line-height:1.3; color:var(--ink);
                     text-decoration:none; }
/* Each row's icon, the page's own (PAGE_ICONS, nala-shared.js), drawn as
   the tab bar draws it; a group's name stands where its rows' names do. */
body.ui2 .navic{ flex:0 0 auto; display:flex; width:24px; height:24px; color:var(--mid); }
body.ui2 .navic svg{ width:100%; height:100%; fill:none; stroke:currentColor;
                     stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
body.ui2 .navlabel{ flex:1 1 auto; min-width:0; }
body.ui2 .navgrp > span{ padding-left:calc(24px + var(--s3)); }
body.ui2 .navdrop a:active{ background:var(--accent-soft); }
body.ui2 .navgrp{ display:flex; align-items:center; justify-content:space-between;
                  gap:var(--s4); width:100%; padding:var(--s3) var(--s4);
                  font-size:var(--t2); line-height:1.3; font-family:inherit;
                  text-align:left; color:var(--ink); background:none; border:0;
                  border-top:1px solid var(--rule); cursor:pointer; }
body.ui2 .navchev{ flex:0 0 auto; color:var(--mid); transition:transform .15s ease; }
body.ui2 .navgroup.open .navchev{ transform:rotate(180deg); }
body.ui2 .navsub{ display:none; }
body.ui2 .navgroup.open .navsub{ display:block; }
body.ui2 .navsub a{ padding-left:var(--s6); }
body.ui2 .navdrop a.navaction,
body.ui2 .navdrop a.hasact{ display:flex; align-items:center;
                            justify-content:space-between; gap:var(--s3); }
body.ui2 .navmark{ display:inline-flex; flex:0 0 auto; }
body.ui2 .navmark.on{ color:var(--law-green); }  /* the law's green ink (was a green of its own until 27 Sep) */
body.ui2 .navmark.off{ color:#9A9A92; }
body.ui2 .navnote{ flex:0 0 auto; font-size:var(--t1); color:var(--mid); }
/* A sentence stands under the word, not beside it (the owner's photo of
   Spa in Safari, 27 Oct: "Notifications" drawn over "Add to Home Screen
   first"). The menu from the foot is 260 wide, which leaves a Settings row
   180 for the word and its note, and side by side they need 250: the word,
   free to shrink, gave its room to the note and kept its letters, which
   ran on over it. Under it, the word keeps its line and the note is its
   caption, the bell beside both. "working" is the one note drawn over the
   state it is leaving, on or off: one word, which fits beside, and moved
   under it the menu would bounce for the moment the switch works. */
body.ui2 .navdrop a:has(> .navnote):not([data-state="on"], [data-state="off"]){
  display:grid; grid-template-areas:"ic word" "ic note"; grid-template-columns:auto 1fr;
  column-gap:var(--s3); row-gap:2px; }
body.ui2 .navdrop a > .navic{ grid-area:ic; }
body.ui2 .navdrop a > .navlabel{ grid-area:word; }
body.ui2 .navdrop a > .navnote{ grid-area:note; }
body.ui2 .navdrop a.signout{ border-top:1px solid var(--rule);
                             margin-top:var(--s1); padding-top:var(--s3);
                             color:var(--mid); }
/* A count of what waits on you is the accent's blue, on the bar and in
   the menu alike: the owner, 30 Sep, "Counter is blue", having tried the
   phone's red and found it confusing beside the red that means failure. */
body.ui2 .navbadge{ flex:0 0 auto; min-width:20px; height:20px; padding:0 6px;
                    border-radius:var(--pill); background:var(--accent);
                    color:var(--accent-ink); font-size:var(--t1);
                    display:inline-flex; align-items:center; justify-content:center; }

/* ── the tab bar ─────────────────────────────────────────────────────
   The icons along the foot of the screen, drawn by buildTabs in
   nala-shared.js: the pages a login uses most, five at most, the owner's
   ask of 30 Sep. It is the one fixed element pinned over the page, so the
   header law's ban on floating menus is answered here rather than
   ignored: nothing may sit under it.

   Drawn to the iPhone's own bar (iOS 26), the owner's ask the same day:
   "Study apple iPhone best practice including space below and icon line
   size". So the staff meet the bar they already know:
   - a capsule 62pt tall, floating 21pt above the foot of the screen and
     21pt in from its sides (further in from a notch), with 2pt inside
     it to the tabs. The home indicator lives in the 21pt below it.
   - icons in a 28pt box, the largest 23pt.
   - the page you are on in the phone's blue, and no pill: the owner, 30
     Sep, "no grey pill, just change icon colour to blue", which is also
     what the phone does. The one place selection wears blue, the accent's
     other meaning ("you can act here, or this is selected").
   - five pages and the menu after them, the iPhone's More tab, which
     raises the menu from the foot beside itself.
   - every login's bar the same capsule, however few its icons: the
     owner, 1 Oct, "This bar is not consistent across employees". It
     parts from iOS 26 here, which narrows a bar of under four, and that
     narrow bar is why: sized from its contents, Safari drew the waiter's
     two pages and the menu as one icon in a pill, the next beside it and
     the menu off the screen, where Chromium, the suites', drew it whole.
     Its width comes from its sides, as the admin's always has.
   - held sideways, the compact bar: 44pt, smaller icons, and nearer the
     foot, so the Cleans board keeps its villas on a screen.
   Apple's numbers: the HIG (tab bars, SF Symbols) and the system bar as
   measured for FabBar. STYLEGUIDE.md has the sources.

   Where it parts from the phone, the owner chose to, 30 Sep: line icons
   where the phone fills its own, Lucide's (nala-shared.js), drawn
   thinner than Lucide's own ("Make icon line thinner"), and no name under
   them ("No text below icon"). And one for a rule of this app: the bar is
   solid white, not glass, as nothing on paper lets the page through.

   - The page ends above it. body.hastabs::after is room for the bar at
     the end of every page, so the last row always scrolls clear.
   - The page's own footer stands ON it, never under it: .foot, sticky or
     fixed, is lifted by the bar's room.
   - A sheet, a select bar or a save bar rising from the foot covers it,
     as a sheet covers the page: z-index 5 sits under all of theirs.
   - #tabBar is the whole strip the bar floats in, and what scrolls under
     it fades into the ground, as the phone's content does under its
     bars, so a row is never cut by the capsule. The strip passes a
     finger through to the page; only the capsule takes it.

   - In the iPhone's app the page scrolls inside body, and the screen
     itself never moves, so nothing can carry the bar up it: the shell,
     below.

   --tabroom is the height of that strip, and 0px where there is no bar,
   for a page's own fixed bar to stand on. */
body.ui2{ --tabroom:0px; }
body.ui2.hastabs{ --tabroom:83px; }          /* 62 of bar and 21 under it */
body.ui2 .tabbar{ position:fixed; left:0; right:0; bottom:0; z-index:5;
                  height:var(--tabroom); pointer-events:none;
                  background:linear-gradient(to bottom, transparent, var(--ground) 50%); }
/* Under a page's own footer, or its own bar wearing onbar (Publish's),
   the strip is solid ground instead: the two read as one band, and no row
   shows between them. */
body.ui2.hastabs:has(> .foot, > .onbar) .tabbar{ background:var(--ground); }
/* It stays put from page to page (the owner, 30 Sep: "It should stay
   there"). Every tap opens a new page, which auth.js covers until the
   login lands; drawn at once (nala-shared.js), the bar stands on that
   cover while it only waits, and the cover wears the page's own ground
   rather than its cream, so a tap reads as the page changing under a bar
   that did not move. A cover with a passcode or a word on it covers the
   bar as it covers everything. */
body.ui2.hastabs:has(> #nalaCover.waiting) .tabbar{ z-index:10000; }
body.ui2 > #nalaCover.waiting{ background:var(--ground) !important; }
/* While a field is being typed in on a phone, the bar stands down, as the
   phone's own apps' bars do under the keyboard. Safari lifts a fixed bar
   above the keyboard and can leave it there once the keyboard has gone:
   the owner, 1 Oct, "Not sticky on settings page", the bar 294pt up on
   Settings - the number pad its 07:30 and 18:00 call up, 216, with its
   44 of Done bar and 34 of home strip. In the app the shell, below,
   keeps it at the foot once the keyboard goes. What stands on the bar comes down
   with it, onto the keyboard, as the phone's own box does: Chat's box
   and Send, a page's footer. A mouse brings no keyboard: there it stays.
   The two rules ask the same question, one for the room, one for the
   bar. */
@media (pointer:coarse){
  body.ui2.hastabs:has(:is(textarea, select, [contenteditable="true"],
      input:not([type="checkbox"], [type="radio"], [type="button"], [type="submit"],
                 [type="reset"], [type="range"], [type="color"], [type="file"],
                 [type="image"], [type="hidden"])):focus){ --tabroom:0px; }
  body.ui2.hastabs:has(:is(textarea, select, [contenteditable="true"],
      input:not([type="checkbox"], [type="radio"], [type="button"], [type="submit"],
                 [type="reset"], [type="range"], [type="color"], [type="file"],
                 [type="image"], [type="hidden"])):focus) .tabbar{ display:none; }
}
body.ui2 .tabrow{ position:absolute; bottom:21px; left:max(21px, env(safe-area-inset-left));
                  right:max(21px, env(safe-area-inset-right)); max-width:var(--appw);
                  margin:0 auto; height:62px; padding:2px; display:flex;
                  border-radius:var(--pill); background:var(--surface, #fff);
                  box-shadow:0 0 0 .5px rgba(28,28,26,.10), 0 1px 3px rgba(28,28,26,.06),
                             0 6px 20px rgba(28,28,26,.10);
                  pointer-events:auto; }
body.ui2 .tabbar a, body.ui2 .tabbar .tabmenu{ flex:1 1 0; min-width:0; display:flex;
                    flex-direction:column; align-items:center; justify-content:center;
                    padding:0; border:0; background:none; font:inherit; cursor:pointer;
                    border-radius:var(--pill); color:var(--mid); text-decoration:none;
                    -webkit-tap-highlight-color:transparent; }
body.ui2 .tabbar .tabic{ position:relative; display:flex; width:28px; height:28px; }
/* Lucide's line drawn thinner than its own 2, at the owner's ask: 1.5 on
   its 24 grid, 1.75pt on the bar. Round ends, as Lucide's. */
body.ui2 .tabbar svg{ width:100%; height:100%; display:block; fill:none;
                      stroke:currentColor; stroke-width:1.5;
                      stroke-linecap:round; stroke-linejoin:round; }
/* No name under the icon: the owner, 30 Sep, "No text below icon". The
   name stays in the link, out of sight, so a screen reader still says
   "Chat, 1" (and a mouse has the link's title). */
body.ui2 .tabbar .tablbl{ position:absolute; width:1px; height:1px; overflow:hidden;
                          clip-path:inset(50%); white-space:nowrap; }
body.ui2 .tabbar a[aria-current],
body.ui2 .tabbar .tabmenu.here,
body.ui2 .tabbar .tabmenu[aria-expanded="true"]{ color:var(--accent); }
body.ui2 .tabbar a[aria-current]{ cursor:default; }
body.ui2 .tabbar a[href]:active, body.ui2 .tabbar .tabmenu:active{ opacity:.55; }
body.ui2 .tabbar .navbadge{ position:absolute; top:-3px; left:calc(50% + 5px);
                            min-width:18px; height:18px; padding:0 5px; }
@media (orientation:landscape) and (max-height:500px){
  body.ui2.hastabs{ --tabroom:calc(44px + max(8px, env(safe-area-inset-bottom))); }
  body.ui2 .tabrow{ height:44px; bottom:max(8px, env(safe-area-inset-bottom)); }
  body.ui2 .tabbar .tabic{ width:22px; height:22px; }
  body.ui2 .tabbar .navbadge{ top:-7px; left:auto; right:-7px; min-width:16px;
                              height:16px; padding:0 4px; font-size:11px; }
}
/* The menu from the foot. Where the bar is drawn its last icon raises the
   menu (the owner, 30 Sep, off mock-menu-rise.html): the size the dropdown
   always was ("It doesn't need to be full width, same as before is ok"),
   rising from above the bar beside its icon, over a light shade that a tap
   shuts it from; the bar stays above the shade, so its icon shuts it too.
   The hamburger at the top stands down. A page without the bar keeps the
   hamburger, and its menu drops from it. */
body.ui2.hastabs .navbtn{ display:none; }
body.ui2.hastabs .navdrop{ display:block; position:fixed; z-index:1; top:auto; left:auto;
                           bottom:calc(var(--tabroom) + 10px);
                           right:max(21px, env(safe-area-inset-right), calc((100vw - var(--appw)) / 2));
                           width:260px; min-width:0; max-height:calc(100dvh - var(--tabroom) - 34px);
                           overflow-y:auto; overscroll-behavior:contain; border:0;
                           border-radius:16px; background:var(--surface, #fff); padding:6px 0;
                           box-shadow:0 2px 6px rgba(0,0,0,.06), 0 12px 32px rgba(0,0,0,.14);
                           transform-origin:calc(100% - 28px) 100%;
                           transform:translateY(18px) scale(.94); opacity:0; visibility:hidden;
                           transition:transform .24s cubic-bezier(.2,.9,.25,1), opacity .18s ease,
                                      visibility 0s linear .24s; }
body.ui2.hastabs .navdrop.open{ transform:none; opacity:1; visibility:visible;
                                transition:transform .24s cubic-bezier(.2,.9,.25,1), opacity .18s ease; }
body.ui2.hastabs .navdrop a, body.ui2.hastabs .navgrp{ font-size:var(--t3); padding:11px 16px; }
body.ui2.hastabs .navsub a{ padding-left:28px; }
body.ui2 .menushade{ display:none; }
body.ui2.hastabs .navdrop.open + .menushade{ display:block; position:fixed; inset:0; z-index:0;
                                            background:rgba(28,28,26,.16); }
body.ui2.hastabs:has(.navdrop.open) .tabbar{ z-index:600; }
body.ui2.hastabs::after{ content:''; display:block; flex:0 0 auto; height:var(--tabroom); }
body.ui2 .foot{ bottom:var(--tabroom); }
body.ui2.hastabs .foot{ padding-bottom:10px; }

/* ── the shell: in the iPhone's app, the page scrolls inside body ──────
   The bar would not stay at the foot of the screen: Settings, 1 Oct, 294pt
   up; Chat, 2 Oct, 400; the Dashboard, 8 Oct, the owner: "Sometimes it
   works without issues, then all of a sudden, it's back to lifting with
   the scroll". iOS 26 does it, and iOS 27's betas still: once its keyboard
   has been up, Safari can go on believing it is, for as long as the app
   stays open. Everything fixed - the bar, a footer, Chat's box, a sheet -
   it pins to a frame it keeps clear of that keyboard, and it moves the
   frame only as the document scrolls: so they rode up the screen as a page
   scrolled down, a keyboard's height at most, and settled as it scrolled
   back. Nothing on a page can talk Safari out of it, and Safari's own
   report of the screen is as wrong as its frame: moving the bar by the
   report hid it below the screen (7 Oct).

   So in the Home Screen app, where the staff work, the document never
   scrolls, and the frame, with nothing to move it, stays the screen. html
   is the screen and does not move; body is the page and scrolls inside
   it, with its footer and the room at its end. Whatever iOS scrolls the
   document by to show a field over the keyboard is put back once the
   keyboard goes (nala-shared.js). Body keeps the column: on a screen
   wider than it, the margins are its own border, see-through, so a
   finger there still scrolls the page; and it is what anything placed
   absolutely without a placed parent sits in, so that scrolls with the
   page as it did. A Safari tab keeps the document's scroll, which its
   toolbar and its own pull to refresh need.

   Three things keep the page as it was laid out in the document. Body
   stands in the frame the bar stands in, fixed to the screen's four
   edges, so no unit sizes it and the page ends where the bar's screen
   does. Both units missed: iOS 26 has started a Home Screen app a
   status bar short in 100% until its first scroll, which this one
   never makes, and 100vh, used from 8 Oct, is a status bar taller than
   a page that starts below the status bar: the owner, 9 Oct, "not
   possible to scroll to that booking", Reservations' last row held
   47pt under the bar on a 390 by 844 iPhone. Body's rows do not
   shrink, unless a page asks (Cleans' grid does): body is a flex
   column of a fixed height now, where it grew with its rows before.
   And the room at the end of a page is its last row, never body's
   padding: Safari 26.4 can leave a scrolling flex column's end padding
   out of what scrolls (WebKit 316902), so the page's own padding at its
   foot (--padfoot, read off it by shellOn) joins the bar's room in
   body::after.

   shellOn, nala-shared.js, puts it on. Under it a page reads its scroll
   through pageScroller(), never the window's, which never moves:
   tabs_suite names a page that reads the window's. */
html.shell{ height:100%; overflow:hidden; overscroll-behavior:none; }
html.shell body.ui2{ position:fixed; inset:0; height:auto; min-height:0; max-width:none; margin:0;
                     padding-bottom:0; border:0 solid transparent;
                     border-inline-width:max(0px, (100vw - var(--appw) - 2 * var(--appgut)) / 2);
                     overflow-y:auto; overscroll-behavior-y:contain; }
:where(html.shell body.ui2) > *{ flex-shrink:0; }
html.shell body.ui2::after{ content:''; display:block; flex:0 0 auto;
                            height:calc(var(--tabroom) + var(--padfoot, 0px)); }
@media print{ html.shell, html.shell body.ui2{ height:auto; overflow:visible; }
              html.shell body.ui2{ position:relative; } }
/* Pull to refresh's mark (pullToRefresh, nala-shared.js): a white disc
   with a turning arrow that follows the finger down from the top, grey
   while a release would do nothing and ink once it would reload. */
body.ui2 .ptr{ position:fixed; top:calc(8px + env(safe-area-inset-top)); left:50%;
               z-index:6; width:36px; height:36px; margin-left:-18px;
               border-radius:50%; background:var(--surface, #fff);
               box-shadow:0 1px 2px rgba(28,28,26,.10), 0 2px 10px rgba(28,28,26,.08);
               display:flex; align-items:center; justify-content:center;
               color:var(--mid); opacity:0; pointer-events:none;
               transform:translateY(-60px); }
body.ui2 .ptr svg{ width:20px; height:20px; fill:none; stroke:currentColor;
                   stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
body.ui2 .ptr.ready{ color:var(--ink); }
body.ui2 .ptr.spin svg{ animation:ptrspin .8s linear infinite; }
@keyframes ptrspin{ to{ transform:rotate(360deg); } }


/* ── buttons · the button law, redressed but not rewritten ───────────
   One solid primary per surface; quiet is secondary; destructive wears
   terracotta outline and always asks first. Only the dress changed:
   the solid is the accent rather than ink, and quiet is a fill rather
   than an outline, so a screen of secondary actions stops reading as a
   screen of identical bordered rectangles.                          */
body.ui2 .btn{ display:inline-flex; align-items:center; justify-content:center;
               width:100%; padding:var(--s3) var(--s4); min-height:var(--tap);
               border:0; border-radius:var(--r); background:var(--card);
               color:var(--ink); font-family:var(--ui-font); font-size:var(--t3);
               font-weight:500; cursor:pointer; text-decoration:none; }
/* ── the four roles, and there are only four ───────────────────────────
   Ruled 29 Aug, after three screenshots in which the primary action was
   green on Reservations, black on Spa and absent on Front Desk, where
   Save, Close and Confirm were the same button three times.

   PRIMARY      the one thing a surface is for. Solid accent. One per
                surface. Never confirms: if it feels like it needs a
                warning the label is wrong, not the dialog missing.
   SECONDARY    everything else that writes nothing. Quiet grey fill.
   DESTRUCTIVE  destroys or walks back something a guest holds.
                Terracotta outline, and it always asks first.
   SELECTED     a pill you picked. Grey fill, ink border. Not an action.

   A page may set the LAYOUT of the row its buttons sit in. It may not
   dress them again.                                                   */
body.ui2 .btn.solid,
body.ui2 .is-primary    { background:var(--accent); color:var(--accent-ink);
                          border:0; font-weight:500; }
body.ui2 .is-secondary,
body.ui2 .opt.quiet,
body.ui2 .cbtn.quiet,
body.ui2 .pbtn.ghost    { background:var(--card); color:var(--ink); border:0; }
body.ui2 .is-destructive{ background:transparent; color:var(--law-terra);
                          border:1px solid var(--law-terra-b); }
/* Each page's own name for "the primary action on this surface". They are
   aliases, not new dresses: one fill, defined once above.              */
body.ui2 .pubbtn, body.ui2 .savebtn, body.ui2 .addbtn,
body.ui2 .cbtn.solid, body.ui2 .pbtn.solid, body.ui2 .opt.solid,
body.ui2 .sum-btns button.go, body.ui2 .selbtn.comb
                        { background:var(--accent); color:var(--accent-ink);
                          border:0; font-weight:500; }
/* Armed to delete. It was solid red on two pages, and red is failure's
   alone: a delete waiting for a second press is not a failure, it is a
   destructive action mid-confirmation.                                */
body.ui2 .btn.arm, body.ui2 .mtog.mdel, body.ui2 .rmbtn.arm,
body.ui2 .del.arm       { background:transparent; color:var(--law-terra);
                          border:1px solid var(--law-terra); font-weight:500; }
/* Selected, everywhere: chips, segments, pickers, toggles, day pills. */
body.ui2 .is-selected,
body.ui2 .chip.on, body.ui2 .pax.on, body.ui2 .seg button.on,
body.ui2 .rmp.on, body.ui2 .grp button.on, body.ui2 .sfbtn.on,
body.ui2 .knob button.on, body.ui2 .sec a.active,
body.ui2 .opt.on, body.ui2 .allbtn.on
                        { background:var(--sel-bg); color:var(--ink);
                          border-color:var(--sel-bdr); font-weight:500; }
/* Reservations' state setters and its select bar show what the record
   will BECOME, in the colour law's own dining green, declined terracotta
   and vacant slate. Grey would throw the meaning away to win an argument
   about consistency.

   .opt.din is every button carrying id oIn, whatever its label. It writes
   status:'in' on the Add reservation sheet exactly as it does on a villa
   tile, so "Add" is green because adding a reservation IS marking them
   dining. Ruled by the owner, 29 Aug, after a first split that went by
   label and made the same write look like two different actions.     */
body.ui2 .opt.din, body.ui2 .opt.out, body.ui2 .opt.vac,
body.ui2 .selbtn.din, body.ui2 .selbtn.out, body.ui2 .selbtn.vac
                        { color:#fff; border:0; font-weight:500; }
body.ui2 .btn.ghost{ background:transparent; border:1px solid var(--rule);
                     color:var(--ink); }
body.ui2 .btn.terra{ background:transparent; border:1px solid var(--law-terra-b);
                     color:var(--law-terra); }
body.ui2 .btn.warn { background:transparent; border:1px solid var(--law-terra-b);
                     color:var(--law-terra); }
body.ui2 .btn:disabled{ opacity:.35; cursor:default; }
body.ui2 .btn:active{ filter:brightness(.94); }

/* What a press says back. Grey while writing is the colour law's "sent,
   waiting on the other side"; green is "done", and is worn only after
   the write. Saved rests until there is more to save.               */
body.ui2 .btn.saving, body.ui2 .btn.saved{ opacity:1; cursor:default; }
body.ui2 .btn.saving{ background:var(--law-wait); color:var(--mid); }
body.ui2 .btn.saved { background:var(--law-save-bg); color:var(--law-save-ink); }
/* Failure is words, in red, under the button that failed. Never a red
   fill: a red tile reads as a state the record is in, and the record is
   simply unsaved.                                                    */
body.ui2 .savefail{ display:none; font-size:var(--t2); line-height:1.45;
                    color:var(--law-red); margin-top:var(--s2); }
body.ui2 .savefail.show{ display:block; }

/* ── the trim rule: which field gives way when a row will not fit ─────
   Ruled by the owner, 29 Aug, off a Pre-arrival SMS board where a long
   name pushed the phone number, the confidence tick and the edit pencil
   off the right edge. "Georgina Whitcombe +61491570156 ." and "Harriet
   Lindgren +614915701..." - the two things reception needed were the
   ones that went.

   THE NAME IS THE ONLY FIELD THAT TRIMS. It is the one that survives
   being half shown: "Harriet Lindg..." on villa 14 is still Harriet on
   villa 14. Nothing else in a row is like that.

   A NUMBER NEVER TRIMS. Half a phone number is not a phone number, and
   worse, it looks like one - somebody will read it out. Same for a time
   or a date: they are right or they are useless.

   A CONTROL NEVER TRIMS. A pencil clipped to a couple of pixels is not
   a control, it is a thing that cannot be pressed and does not look
   like it was meant to be.

   The fault was structural rather than a width: the ellipsis sat on the
   LINE, which held the name and everything after it, so it ate whichever
   field happened to be last. The name needs to be its own box for the
   ellipsis to have anything to bite.                                  */
body.ui2 .trimrow      { display:flex; align-items:baseline; min-width:0; }
body.ui2 .trimrow > .trim {
  flex:1 1 auto; min-width:0;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* Everything else in the line keeps its full width and is never squeezed. */
body.ui2 .trimrow > :not(.trim) { flex:0 0 auto; }

/* ── rows and lists ──────────────────────────────────────────────────
   Separated by an inset hairline rather than boxed. A tinted row keeps
   its own corner, because a tint that runs to the screen edge stops
   reading as one record.                                             */
body.ui2 .rowlist > * + *{ border-top:1px solid var(--rule); }

/* ── the access message ──────────────────────────────────────────────
   Plain typography, never a bordered or filled box: that dress belongs
   to buttons, and a message wearing it reads as something to tap.   */
body.ui2 .noaccess{ display:none; font-size:var(--t3); line-height:1.5;
                    color:var(--ink); max-width:52ch; margin:var(--s5) 0; }
body.ui2 .noaccess.show{ display:block; }
body.ui2 .noaccess .na-retry{ margin-top:var(--s4); display:block;
                              width:max-content; }

/* ── stats · plain type, never boxed ─────────────────────────────── */
body.ui2 .stats{ display:flex; justify-content:center; gap:var(--s6);
                 margin:var(--s4) 0 var(--s2); }
body.ui2 .stat{ text-align:center; }
body.ui2 .stat-n{ display:block; font-size:var(--t5); line-height:1;
                  color:var(--ink); font-variant-numeric:tabular-nums; }
body.ui2 .stat-l{ display:block; font-size:var(--t2); color:var(--mid);
                  margin-top:var(--s1); }

/* ── inputs ──────────────────────────────────────────────────────────
   16px minimum, always: anything smaller and iOS zooms the page on
   focus and does not zoom back.                                      */
body.ui2 input, body.ui2 select, body.ui2 textarea{
  font-family:var(--ui-font); font-size:var(--t3); color:var(--ink);
  background:#fff; border:1px solid var(--rule); border-radius:var(--r);
  padding:var(--s3); width:100%; min-height:var(--tap); }
body.ui2 input:focus, body.ui2 textarea:focus, body.ui2 select:focus{
  outline:none; border-color:var(--accent); }

/* ── keyboard focus ──────────────────────────────────────────────────
   Nothing in the app has this today: zero pages use :focus-visible and
   six declare outline:none. A keyboard or switch user currently has no
   idea where they are.                                               */
body.ui2 :focus-visible{ outline:2px solid var(--accent); outline-offset:2px;
                         border-radius:var(--r); }

@media (prefers-reduced-motion:reduce){
  body.ui2 *{ transition:none !important; }
  body.ui2 .ptr.spin svg{ animation:none; }
}

/* keep controls off paper */
@media print{
  body.ui2 .navwrap, body.ui2 .foot, body.ui2 .dnav,
  body.ui2 .tabbar{ display:none !important; }
  body.ui2.hastabs::after{ display:none; }
}
