/* ============================================================
   BPP. One design, every page.

   THE ONE THING THIS LOOKS LIKE
   A photographer's contact sheet on white paper. The pictures are
   the loudest thing on any screen. Type is quiet and well drawn and
   gets out of their way. One yellow control per view, on the thing
   that acts.

   TYPE   Schibsted Grotesk, one family, everywhere. Weight and size
          do the work that four families were doing before. Figures
          hold their columns by alignment, never with tabular numerals
          (Schibsted's tabular digits read like a typewriter).
   COLOUR White, because photographs are judged against neutral and
          Brian works in daylight. Yellow #FCD508 on the control that
          acts and nowhere else. Red, amber and green mean state only.
   SPACE  4px base. Tight inside a card, open between shelves.

   Structure, which Brian pointed at twice in Spotify and in
   Show & Tour: a rail on the left that never moves, search pinned
   at the top, and the body as rows of picture cards under a
   heading. Shelves, not sections stacked like a document.

   Pages keep their own component CSS below this file. Nothing here
   knows about one page's furniture.

   THE SCALES (Sep 9 2026)
   Seven type steps, nothing under 13px, weights 400/600/700 only,
   four line-heights (1.15 headings, 1.3 titles, 1.45 body, 1.55 long
   text). Spacing on the 4px grid, --s0 to --s8. --scrim and --onphoto
   are for anything sitting on a photograph (a chip is never plain
   paper on a picture): a wash close to paper's own colour and the
   shadow that lifts it off the image under it, both flipped for dark.
   ============================================================ */

:root{
  /* surfaces */
  --paper:#ffffff;
  --lift:#f4f3f1;
  --rail:#faf9f7;
  /* ink, three depths, and that is enough */
  --ink:#14171a;
  --ink2:#4a5158;
  --ink3:#676d74;
  /* lines */
  --rule:#e5e3de;
  --rule2:#cbc7c0;
  /* the one accent, and the four states */
  --act:#FCD508;
  --act-ink:#14171a;
  --late:#a8261a;
  --wait:#7a5200;
  --done:#1a6440;
  --focus:#0f62d6;
  /* type */
  --sans:'Schibsted Grotesk','Helvetica Neue',Arial,sans-serif;
  --display:'Schibsted Grotesk','Helvetica Neue',Arial,sans-serif;
  /* the type scale: seven steps, nothing under 13px */
  --t-meta:13px; --t-fact:15px; --t-body:17px; --t-sub:20px;
  --t-head:24px; --t-page:32px; --t-lead:44px;
  /* on a photograph: a chip or a control never sits on plain paper
     over a picture, it sits on this wash, lifted off the image by
     this shadow. Dark theme gets its own dark wash below. */
  --scrim:rgba(255,255,255,.94);
  --onphoto:0 1px 3px rgba(0,0,0,.26);
  /* space */
  --s0:2px; --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px; --s8:64px;
  --radius:8px; --radius-lg:16px; --pill:999px;
  /* the phone's bottom tab bar, so a page's own fixed footer (a save
     bar, an upload bar) can sit above it instead of underneath it */
  --tabbar-h:62px;
  color-scheme:light;
}

@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    color-scheme:dark;
    --paper:#101214; --lift:#1e2225; --rail:#16191c;
    --ink:#f2f3f4; --ink2:#b3b9bd; --ink3:#8e959a;
    --rule:#262a2e; --rule2:#3a4045;
    --late:#ff8a7a; --wait:#e8b74a; --done:#5fcf9a; --focus:#8ab4ff;
    --scrim:rgba(24,27,30,.94);
    --onphoto:0 1px 3px rgba(0,0,0,.6);
  }
}
:root[data-theme="dark"]{
    color-scheme:dark;
  --paper:#101214; --lift:#1e2225; --rail:#16191c;
  --ink:#f2f3f4; --ink2:#b3b9bd; --ink3:#8e959a;
  --rule:#262a2e; --rule2:#3a4045;
  --late:#ff8a7a; --wait:#e8b74a; --done:#5fcf9a; --focus:#8ab4ff;
  --scrim:rgba(24,27,30,.94);
  --onphoto:0 1px 3px rgba(0,0,0,.6);
}

/* ---------- the parts nobody looks at ---------- */
*{margin:0;padding:0;box-sizing:border-box}
::selection{background:var(--act);color:#14171a}
:root{accent-color:var(--act);caret-color:var(--ink)}
:where(a,button,input,textarea,select,summary,[tabindex]):focus-visible{
  outline:2px solid var(--focus);outline-offset:2px;border-radius:4px;
}
:where(button,input,textarea,select){font:inherit;color:inherit}
:where(button){cursor:pointer}
:where(button)[disabled]{cursor:not-allowed}
a{color:inherit}
img{max-width:100%}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
}
*{scrollbar-color:var(--rule2) transparent}
::-webkit-scrollbar{width:10px;height:8px}
::-webkit-scrollbar-thumb{background:var(--rule2);border-radius:var(--pill)}
::-webkit-scrollbar-track{background:transparent}

body{
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:var(--t-body);
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}

/* ---------- type, one family, weight doing the work ---------- */
h1,h2,h3,h4{font-family:var(--display);font-weight:700;letter-spacing:-.025em;line-height:1.15}
/* One flat page heading. The largest thing on a view is a figure you can
   act on, never a string that only reports (the date used to render at
   60px on Home). Audit finding 15, Sep 8 2026. */
h1{font-size:var(--t-page);letter-spacing:-.035em}
h2{font-size:var(--t-head)}
h3{font-size:var(--t-sub);letter-spacing:-.02em}
h4{font-size:var(--t-body);letter-spacing:-.01em}
.quiet{color:var(--ink2)}
.faint{color:var(--ink3)}

/* ---------- controls ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  font-family:var(--sans);font-size:var(--t-body);font-weight:600;
  border:1px solid var(--rule2);border-radius:var(--pill);
  background:var(--paper);color:var(--ink);
  padding:var(--s3) var(--s4);min-height:44px;
}
.btn:hover{border-color:var(--ink3)}
.btn-act{background:var(--act);color:var(--act-ink);border-color:transparent}
.btn-act:hover{filter:brightness(.94);border-color:transparent}
.btn[disabled],.btn-act[disabled]{background:var(--rule);color:var(--ink2);border-color:transparent;filter:none}
.field{
  font-family:var(--sans);font-size:var(--t-body);color:var(--ink);
  background:var(--lift);border:1px solid transparent;border-radius:var(--pill);
  padding:0 var(--s4);min-height:46px;
}
.field:focus{background:var(--paper);border-color:var(--rule2)}
.field::placeholder{color:var(--ink3)}

/* ---------- the rail, which never moves ---------- */
.app{display:grid;grid-template-columns:236px minmax(0,1fr);min-height:100vh}
.app>*{min-width:0}
.rail{
  background:var(--rail);border-right:1px solid var(--rule);
  padding:var(--s5) var(--s4) var(--s5);display:flex;flex-direction:column;
  position:sticky;top:0;height:100vh;
}
/* the wordmark: a brand mark and the way back to the board */
.rail .mark{font-family:var(--display);font-size:var(--t-page);font-weight:700;letter-spacing:-.03em;line-height:1.3;padding:var(--s1) var(--s2) var(--s5);display:flex;align-items:center;gap:var(--s2);text-decoration:none;color:var(--ink)}
.rail .mark img{width:30px;height:auto}
.rail .new{
  display:flex;align-items:center;gap:var(--s2);width:100%;
  font-family:var(--sans);font-size:var(--t-body);font-weight:600;color:var(--act-ink);
  background:var(--act);border:0;border-radius:var(--pill);
  padding:var(--s3) var(--s4);min-height:46px;margin-bottom:var(--s4);
}
.rail .new{text-decoration:none}
.rail .new:hover{filter:brightness(.94)}
.rail .new .plus{font-size:var(--t-sub);line-height:1.3}
/* Justin's key carries scope "shooter": booking a shoot and blocking
   time are Brian's calls, not his. The board stamps body[data-scope] the
   moment it knows who is holding the key (the cached board at boot, or
   the fresh read behind it), so this needs no script of its own. */
body[data-scope="shooter"] .rail .new{display:none}
.rail nav{display:flex;flex-direction:column;gap:1px}
.rail nav a,.rail nav button{
  display:flex;align-items:center;text-align:left;text-decoration:none;
  color:var(--ink2);background:none;border:0;
  font-family:var(--sans);font-size:var(--t-body);
  padding:var(--s3);min-height:44px;border-radius:var(--radius);width:100%;
}
/* The same icon the tab bar draws, at the size the tab bar draws it, so
   a section looks like itself on both. Audit P3, Sep 8 2026. */
.rail nav a svg,.rail nav button svg{
  width:22px;height:22px;flex:none;margin-right:10px;
  fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;
}
.rail nav a:hover,.rail nav button:hover{color:var(--ink);background:var(--paper)}
.rail nav [aria-current]{color:var(--ink);font-weight:600;background:var(--paper)}
/* The foot of the nav: whatever the page puts in the slot (the board
   puts the day and its two numbers there) and Sign out. nav.js keeps it
   OUT of the rail, because the rail is not on screen at all on a phone
   and signing out has to stay reachable there. On a desktop it is
   pinned to the foot of the rail column, exactly where it has always
   been; on a phone it sits at the end of the page, above the tab bar. */
.navfoot{padding:0 var(--s3) var(--s5);font-size:var(--t-meta);color:var(--ink3)}
.navfoot b{display:block;font-size:var(--t-fact);font-weight:600;color:var(--ink2)}
.navfoot .signout{display:block;margin-top:var(--s3);font:600 var(--t-fact) var(--sans);color:var(--ink2);
  background:none;border:0;padding:var(--s3) 0;min-height:44px;text-align:left}
.navfoot .signout:hover{color:var(--ink);text-decoration:underline}
.navfoot .signout.armed{color:var(--late)}
@media (min-width:861px){
  .navfoot{position:fixed;left:0;bottom:0;width:236px;padding:0 var(--s6) var(--s5);z-index:21}
}
@media (max-width:860px){
  .navfoot{margin-top:var(--s6);border-top:1px solid var(--rule);
    padding:var(--s4) var(--s4) calc(var(--s4) + var(--tabbar-h))}
}

/* ---------- search, pinned ---------- */
.bar{
  position:sticky;top:0;z-index:5;background:var(--paper);
  display:flex;align-items:center;gap:var(--s4);
  padding:var(--s4) var(--s6);border-bottom:1px solid var(--rule);
}
.bar .find{
  flex:1;max-width:460px;display:flex;align-items:center;gap:var(--s3);
  background:var(--lift);border-radius:var(--pill);padding:0 var(--s4);min-height:46px;
}
.bar .find input{flex:1;font-family:var(--sans);font-size:var(--t-body);color:var(--ink);background:none;border:0;min-height:46px}
.bar .find input::placeholder{color:var(--ink3)}
.bar .grow{flex:1}
.bar .who{color:var(--ink2);font-size:var(--t-fact)}
.key{border:1px solid var(--rule2);border-radius:4px;padding:var(--s0) var(--s2);font-size:var(--t-meta);color:var(--ink3)}

/* ---------- a shelf ----------
   Groups read as groups: 48px between one shelf and the next, 24px
   between cards inside one, 16px from a shelf's head to its cards. The
   old 26 and 20 were a 1.3 to 1 ratio, so the eye read one field of
   evenly spaced cards instead of five shelves. Audit finding 21. */
.shelf{padding-top:var(--s7)}
.shelf .head{display:flex;align-items:baseline;gap:var(--s4);padding-bottom:var(--s4)}
.shelf h2{font-family:var(--display);font-size:var(--t-head);font-weight:700;letter-spacing:-.025em}
.shelf .n{color:var(--ink3);font-size:var(--t-meta)}
.shelf .all{
  margin-left:auto;font-family:var(--sans);font-size:var(--t-fact);font-weight:600;color:var(--ink2);
  background:none;border:0;padding:var(--s3) var(--s0);min-height:44px;
}
.shelf .all:hover{color:var(--ink);text-decoration:underline}
/* the same word inside a sentence: "13 shoots have no price. Show them" */
.all.inline{
  display:inline-flex;align-items:center;margin:0;padding:0 var(--s0);min-height:44px;background:none;border:0;
  font-family:var(--sans);font-weight:600;font-size:inherit;color:var(--ink2);text-decoration:underline;text-underline-offset:3px;
}
.all.inline:hover{color:var(--ink)}
/* A wide screen grows the cards instead of multiplying them: the column
   is capped (main, board.html) and each card takes a share of it.
   Audit finding 1. */
.row{
  display:grid;grid-auto-flow:column;grid-auto-columns:clamp(200px,13vw,224px);gap:var(--s5);
  overflow-x:auto;scroll-snap-type:x proximity;padding-bottom:var(--s2);
}
/* A list of single lines, a name at one end and its number at the other.
   Past 760px the two stop being one glance. Audit finding 1. */
.lines,.money .clients,.money .months,.pkv{max-width:760px}
.shelf.open .row{grid-auto-flow:row;grid-template-columns:repeat(auto-fill,minmax(224px,1fr));overflow:visible}
/* A shelf that is folded down to one line until it is asked for. The
   Delivered shelf wears it: finished work is a reference, not something
   to scroll past on the way to the work that is not finished. */
.archline{display:block;width:100%;text-align:left;background:none;border:0;
  border-top:1px solid var(--rule);padding:var(--s4) var(--s0);min-height:44px;
  font:600 var(--t-body) var(--sans);color:var(--ink2)}
.archline:hover{color:var(--ink)}

/* ---------- a picture card ---------- */
.card{scroll-snap-align:start;display:flex;flex-direction:column;min-width:0}
.shot{position:relative;display:block;border-radius:var(--radius);overflow:hidden;aspect-ratio:4/3;background:var(--lift);text-decoration:none;container-type:inline-size}
.shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* The no-photo card, one rule for every shot with nothing loaded yet
   (a booked address, a gallery placeholder, a client's initials): no
   border, so it never reads as an empty form field, and the glyph
   scales to the box it sits in instead of a fixed size that is too
   small on a wide card and cramped on a narrow one. */
.shot .none{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  text-align:center;padding:0 var(--s4);
  font-family:var(--display);font-weight:700;letter-spacing:.02em;
  font-size:clamp(28px,22cqw,64px);color:var(--ink3);
}
/* The chip says the shoot's one state word. On a 158px card some of
   those words are longer than the picture, so it ends in an ellipsis
   rather than being sliced through a letter at the edge of the card.
   It sits on a photograph, so it wears the scrim and the on-photo
   shadow, never plain paper: paper-on-photo is a near-black pill in
   dark mode.
   Round two, Sep 12 2026: the width it gave up was room for the three
   dot menu in the other corner, and that menu went on Sep 12. The chip
   keeps the same 8px gutter it sits on, and the word it lost back. */
.chip{
  position:absolute;top:var(--s2);left:var(--s2);background:var(--scrim);border-radius:var(--pill);
  padding:var(--s1) var(--s3);font-size:var(--t-meta);font-weight:600;line-height:1.3;white-space:nowrap;
  max-width:calc(100% - var(--s4));overflow:hidden;text-overflow:ellipsis;
  box-shadow:var(--onphoto);color:var(--ink);
}
.chip.late{color:var(--late)}
.chip.wait{color:var(--wait)}
.chip.run{color:var(--ink2)}
.chip.done{color:var(--done)}
.chip.ai::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--act);margin-right:var(--s2);vertical-align:1px}
/* the control arrives on the picture when the pointer does, and is always
   there on a touch screen, where there is no hover to arrive with */
/* Quiet by default. Yellow is the acting control, singular: on a screen
   with nine yellow bars the eye has nowhere to land, so a card action is
   a plain pill on the photograph and exactly one of them per view wears
   .go.lead. Audit finding 8, Sep 8 2026. */
.go{
  font-family:var(--sans);font-size:var(--t-fact);font-weight:600;color:var(--ink);
  background:var(--scrim);border:0;border-radius:var(--pill);padding:var(--s2) var(--s4);min-height:44px;
  box-shadow:var(--onphoto);
}
/* Sitting on a photograph, and arriving with the pointer, is what a .go
   does ON A CARD. This used to be the blanket rule, so every .go
   anywhere else in the app was absolutely positioned and invisible until
   something that was never coming hovered a card: it is what hid the
   upload confirm bar's own button, and what makes .rows .addrgo and its
   two neighbours put themselves back in the flow by hand. Off a card a
   .go is now a plain, visible button. Round two, Sep 12 2026. */
.card .go{
  position:absolute;right:var(--s2);bottom:var(--s2);
  opacity:0;transform:translateY(6px);transition:opacity .16s,transform .16s;
}
.card:hover .go,.card:focus-within .go{opacity:1;transform:none}
/* The one yellow control on a view is not allowed to be invisible. On
   Home the lead control is the today card's own pill, and it sat at
   opacity 0 until a mouse happened to land on that one card, so a
   desktop showed no acting control at all. Round one, Sep 12 2026
   (audit 2, P2 19). */
.card .go.lead{opacity:1;transform:none}
/* No pointer means no hover for the control to arrive with, so it is
   always there. At 158px a pill in the corner sits on the middle of the
   photograph, so on touch it becomes a bar across the foot of it. */
@media (hover:none){
  .card .go{opacity:1;transform:none;left:var(--s2);right:var(--s2);text-align:center;padding:var(--s2) var(--s3)}
}
/* A quiet control on a photograph: same scrim and on-photo shadow as
   the chip, never plain paper. */
.go.quiet{background:var(--scrim);color:var(--ink);box-shadow:var(--onphoto)}
/* The one acting control on the view: the first card action on the page,
   marked by leadAction() in board.html as the body is drawn. */
.go.lead{background:var(--act);color:var(--act-ink);box-shadow:none}
/* The one card action is sometimes a link (Navigate, on the Today
   shelf), and a link has no button's centring of its own. */
a.go{display:inline-flex;align-items:center;justify-content:center;text-decoration:none}
/* The whole card is the door to the gallery, which a mouse can use and a
   keyboard cannot. This is that same door as a real link: out of the way
   until it is tabbed to, and then a plain pill on the picture, wearing
   the same scrim and shadow as everything else that lands on a photo. */
.glink{position:absolute;left:var(--s2);top:var(--s2);z-index:3;
  font:600 var(--t-fact) var(--sans);color:var(--ink);background:var(--scrim);
  border-radius:var(--pill);text-decoration:none;
  width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%)}
.glink:focus,.glink:focus-visible{width:auto;height:auto;min-height:44px;padding:var(--s2) var(--s4);
  overflow:visible;clip-path:none;display:inline-flex;align-items:center;
  box-shadow:var(--onphoto)}
.meter{position:absolute;left:0;right:0;bottom:0;height:4px;background:rgba(255,255,255,.4)}
.meter i{display:block;height:100%;background:#fff;transform-origin:left;transform:scaleX(var(--done-frac,0));transition:transform .6s ease}
@media (prefers-reduced-motion:reduce){.meter i{transition:none}}
.card .name{display:block;font-size:var(--t-body);font-weight:600;letter-spacing:-.01em;line-height:1.3;padding-top:var(--s2);text-decoration:none;min-height:44px}
.card a.name:hover{text-decoration:underline}
.card .facts{font-size:var(--t-fact);color:var(--ink2)}
.card .byai{font-size:var(--t-fact);color:var(--ink3)}
.card .byai b{font-weight:600;color:var(--ink2)}

/* ---------- the client chip ---------- */
/* Who a shoot belongs to, said the same way everywhere: her initials in
   a small circle and her name beside it. It sits in a Home tile's meta
   line, a Jobs card's facts line and the shoot page's own line, and a
   tap on it opens her page (board.html's one click handler, off
   data-client). Brian, Sep 15 2026: "each shoot should have an icon or
   something that says who the client is, and if it's clicked, it shows
   the client's page". Inline, so it reads as part of the sentence it is
   in; the circle is the only new mark. */
.cchip{
  display:inline-flex;align-items:center;gap:6px;vertical-align:middle;
  max-width:100%;min-width:0;padding:0;border:0;background:none;
  font:inherit;color:inherit;text-align:left;
}
.cchip .av{
  flex:0 0 auto;width:22px;height:22px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--ink);color:var(--paper);
  font:700 11px/1 var(--sans);letter-spacing:.02em;
}
.cchip .cn{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cchip:hover .cn,.cchip:focus-visible .cn{text-decoration:underline;text-underline-offset:2px}

/* ---------- what an empty shelf says ---------- */
.nothing{color:var(--ink2);font-size:var(--t-fact);padding:var(--s0) 0 var(--s2)}

/* ---------- phones ---------- */
@media (max-width:860px){
  /* The bar is a search box, three filters and seven icons. On a phone
     they get two rows instead of one long one that drags the page wide. */
  .bar{flex-wrap:wrap;row-gap:var(--s3)}
  .bar .find{flex:1 0 100%;max-width:none;order:-1}
  .bar .grow{display:none}
  .app{grid-template-columns:minmax(0,1fr)}
  .rail{
    flex-direction:row;align-items:center;gap:var(--s3);overflow-x:auto;height:auto;
    border-right:0;border-bottom:1px solid var(--rule);padding:var(--s3) var(--s4);
  }
  .rail .mark{font-size:var(--t-sub);padding:0 var(--s2) 0 0;flex:none}
  .rail .mark img{width:24px}
  .rail .new{width:auto;margin:0;padding:var(--s2) var(--s4);white-space:nowrap}
  .rail nav{flex-direction:row;gap:var(--s0)}
  .rail nav a,.rail nav button{padding:var(--s3);white-space:nowrap;width:auto}
  .bar{padding:var(--s3) var(--s4);gap:var(--s3)}
  .bar .who,.bar .key{display:none}
  /* WP6 (audit finding 20): the row used to end flush with the
     container's own 16px padding, so the third card showed a 14px
     sliver that read as a card cut off rather than "there is more".
     Bleeding to the screen edge and giving the peek room to show about
     42px reads as a shelf, the way Spotify's do. FIX-D, Sep 9 2026:
     148px, not 160px, is what actually leaves that peek at this
     column width; kept the same reasoning, changed the number. */
  .row{grid-auto-columns:148px;gap:var(--s3);margin-right:calc(-1 * var(--s4));padding-right:var(--s4)}
  /* an opened shelf is two cards across on a phone, the way a music app
     lays out a library, never one tall card after another */
  .shelf.open .row{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* a phone card is narrow, so the chip takes the whole width it has,
     less its own gutter, and says its whole word ("Wed 9 · 1:42 PM") */
  .chip{padding:var(--s1) var(--s2);font-size:var(--t-meta);max-width:calc(100% - var(--s4))}
  /* the shelf heading steps down on a phone, the appendix's own note */
  .shelf h2{font-size:var(--t-sub)}
}

/* ---------- the shared nav, drawn by /style/nav.js on every admin page
   but the board (which keeps its own copy of this same rail) ----------
   The rail above is board.html's own; #bpp-rail is nav.js's copy of it,
   pinned to the left edge so a page that is not a two-column grid still
   gets a fixed sidebar instead of a rewrite of its layout. body's own
   padding makes room for it, the same way board.html's grid column does. */
#bpp-rail{position:fixed;left:0;top:0;width:236px;height:100vh;z-index:20}
@media (min-width:861px){
  body.bpp-nav-mounted{padding-left:236px}
}
@media (max-width:860px){
  #bpp-rail{display:none}
}

/* the bottom tab bar nav.js draws on a phone, lifted out of texts.html so
   every admin page wears the same one instead of its own copy. Current
   item reads var(--ink) at full weight, never the yellow accent text on
   text this small: the audit found the Texts page's own copy at 10px and
   under contrast (var(--acc-ink) on its chrome), so this one is sized and
   colored to pass by construction, not by luck. */
.tabbar{position:fixed;left:0;right:0;bottom:0;z-index:30;display:flex;
  background:var(--rail);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  border-top:1px solid var(--rule);padding:var(--s2) 0 calc(var(--s2) + env(safe-area-inset-bottom))}
.tabbar a,.tabbar .more{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s1);
  min-height:44px;text-decoration:none;color:var(--ink3);font:600 var(--t-meta) var(--sans);letter-spacing:.02em;
  background:none;border:0;padding:0;cursor:pointer}
.tabbar a.here,.tabbar .more.here{color:var(--ink);font-weight:700}
.tabbar svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* The four sections behind More, a short list that sits on the bar. */
.morelist{position:fixed;left:0;right:0;bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom));z-index:31;
  background:var(--rail);border-top:1px solid var(--rule);padding:var(--s2) var(--s2) var(--s2);max-height:70vh;overflow-y:auto}
/* The four-across grid of little squares stood here. Round two, Sep 12
   2026: the four sections are rows in .morerows now, the same shape as
   everything else in the sheet, so nothing was left for these rules to
   style. */
.morelist svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* WP6 (audit findings 5 and 19): New shoot, then the who filter and the
   icon buttons the phone's one-row bar no longer carries, as rows above
   the grid. nav.js draws the New shoot row; board.html moves its own
   who-filter and icon elements into #bpp-morerows once nav mounts, so
   they are the same elements the bar always had, never a second copy. */
.morerows{display:flex;flex-direction:column;gap:2px;padding-bottom:6px;margin-bottom:6px;border-bottom:1px solid var(--rule)}
.morerows:empty{display:none;border-bottom:0;margin-bottom:0}
.morerows .newrow{display:flex;align-items:center;gap:9px;min-height:46px;
  font-family:var(--sans);font-size:15px;font-weight:600;color:var(--act-ink);
  background:var(--act);border:0;border-radius:var(--radius);padding:10px 14px;
  text-decoration:none;margin-bottom:4px}
.morerows .newrow .plus{font-size:18px;line-height:1}
body[data-scope="shooter"] .morerows .newrow{display:none}
.morerows .whochips{display:flex}
.morerows .whochips button{flex:1}
.morerows .iconbtn{display:flex;align-items:center;justify-content:flex-start;gap:10px;
  width:100%;height:auto;min-height:44px;border-radius:var(--radius);
  padding:10px 12px;font-family:var(--sans);font-size:15px;font-weight:600;color:var(--ink2)}
.morerows .iconbtn svg{width:20px;height:20px;flex:none}
/* The section you are on, so the sheet says where you are instead of
   offering four rows that all look alike. The same treatment the rail
   gives its own current row. Round two, Sep 12 2026. */
.morerows .iconbtn[aria-current]{color:var(--ink);font-weight:700;background:var(--paper)}
/* qtoggle and bell carry no visible words of their own (a round icon
   button reads fine on the bar); as a row in More they need one, so
   board.html gives each a .morelabel span. Hidden everywhere by
   default, shown only inside .morerows; the bar keeps the icon alone.
   The bell's span carries the unseen count after the word, kept in
   step with its pip by board.html. Audit finding 19. */
.morelabel{display:none}
.morerows .morelabel{display:inline}
@media (min-width:861px){
  .tabbar,.morelist{display:none}
}

/* WP6 (audit finding 5): the floating New shoot control. Hidden by
   default; a phone viewing Home or Calendar is the only place it draws,
   because board.html is the only page that ever sets data-view, and
   only those two views are where a shoot gets booked from a driveway. */
.fab-new{display:none}
@media (max-width:860px){
  body[data-view="home"] .fab-new,body[data-view="calendar"] .fab-new{
    display:flex;align-items:center;justify-content:center;
    position:fixed;right:16px;z-index:29;
    bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 16px);
    width:56px;height:56px;border-radius:50%;padding:0;
    background:var(--act);color:var(--act-ink);border:0;
    font-family:var(--sans);font-size:28px;font-weight:600;line-height:1;
    box-shadow:0 4px 14px rgba(0,0,0,.28);
  }
  body[data-scope="shooter"] .fab-new{display:none}
}

/* WP6 (audit finding 35): one shared toast, above the tab bar and above
   the rail foot on a desktop (window.bppToast(), in board.html). Ink on
   paper through the tokens, so it reads right in dark mode without a
   second rule. */
.toast{position:fixed;left:50%;bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 14px);
  transform:translate(-50%,8px);z-index:40;max-width:calc(100% - 32px);
  background:var(--ink);color:var(--paper);
  font-family:var(--sans);font-size:14px;font-weight:600;
  padding:11px 20px;border-radius:var(--pill);box-shadow:0 6px 20px rgba(0,0,0,.28);
  opacity:0;pointer-events:none;transition:opacity .18s,transform .18s}
.toast.show{opacity:1;transform:translate(-50%,0)}
@media (min-width:861px){
  /* the rail column, above the foot (the day, the two numbers, Sign
     out) rather than the middle of the page a phone centres it in */
  .toast{left:0;right:auto;width:236px;max-width:236px;bottom:150px;
    text-align:center;white-space:normal;transform:translateY(8px)}
  .toast.show{transform:translateY(0)}
}

/* ---------- the one question a drop asks ----------
   A folder dropped on a shoot that is already delivered, invoiced, paid
   or closed does not upload: this says so where the toast says
   everything else, and the button beside it is the only way to start
   the run. It lives eight seconds. (audit 3, P0 2) */
.askpill{position:fixed;left:50%;bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 14px);
  transform:translateX(-50%);z-index:41;max-width:calc(100% - 32px);
  display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  background:var(--ink);color:var(--paper);
  font-family:var(--sans);font-size:14px;line-height:1.4;
  padding:10px 12px 10px 20px;border-radius:20px;box-shadow:0 6px 20px rgba(0,0,0,.28)}
.askpill button.retry{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 var(--s4);border-radius:var(--pill);
  /* not the yellow: the one acting control on the screen behind this
     still owns it. */
  border:0;background:var(--paper);color:var(--ink);
  font:600 14px var(--sans);cursor:pointer}
@media (min-width:861px){
  .askpill{left:0;right:auto;width:236px;max-width:236px;bottom:150px;transform:none}
}

/* ---------- a read that did not land ----------
   One line, and the one button that asks for it again. The shoot page's
   failed photo read already looks like this (job.css, button.retry);
   these are the board's own three. (audit 3, P2 11) */
.readfail{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap}
.readfail button.retry{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 var(--s4);border-radius:var(--pill);
  border:1px solid var(--rule);background:var(--paper);color:var(--ink2);
  font:600 var(--t-fact) var(--sans);cursor:pointer}
.readfail button.retry:hover{background:var(--lift);color:var(--ink)}

/* ---------- the sign-in gate, the same words on every admin page ---------- */
.gate{max-width:340px;margin:15vh auto 0;padding:var(--s5);text-align:center}
/* h1 on a page with no other heading (texts.html), h2 on a page that
   already has its own page h1 (upload, import, edits, editor): either
   way the gate's own heading reads the same size. */
.gate h1,.gate h2{font-family:var(--display);font-size:var(--t-head);letter-spacing:-.01em;margin-bottom:var(--s2)}
.gate p{color:var(--ink2);font-size:var(--t-fact);line-height:1.55}
.gate input{width:100%;background:var(--lift);border:1px solid var(--rule2);color:var(--ink);
  padding:var(--s3) var(--s4);border-radius:var(--radius);font:inherit;font-size:var(--t-body);margin:var(--s4) 0;min-height:44px}
.gate input:focus{border-color:var(--focus)}
.gate button{width:100%;background:var(--act);color:var(--act-ink);border:0;font:700 var(--t-fact) var(--sans);
  padding:var(--s3);border-radius:var(--radius);min-height:44px}
.gate .err{color:var(--late);font-size:var(--t-meta);margin-top:var(--s3)}
.connhint{color:var(--ink2);font-size:var(--t-meta);line-height:1.45;padding:var(--s2) 0}
