/* ============================================================================
   CROMA MNL — Community App theme
   Same warm Japandi palette as the POS (polished concrete / oak / espresso /
   amber), re-tuned for a mobile, touch-first app: top bar + bottom tab nav,
   cards, feed, chat bubbles. Safe-area aware for notched phones.
   ========================================================================== */
:root{
  --bg:#e9e3d6; --surface:#fbf8f2; --surface2:#f1ebdd; --line:#ddd3c1;
  --text:#3a2b20; --muted:#93856f; --brand:#5b3f2c; --brand-2:#6f4e37;
  --accent:#b5895a; --accent-soft:#e8d9c3; --green:#6f8259; --red:#b0574f;
  --amber:#d99a4e; --blue:#3a6ea5;
  /* A HAIRLINE, not a full line. With a shadow underneath, a solid 1px
     border reads as a box drawn around the content rather than a surface
     above it. An rgba literal rather than color-mix(): that function has
     no precedent in this file and is unsupported on older Android
     WebViews, where the whole declaration would be dropped and the border
     would vanish entirely. */
  --hair:rgba(58,43,32,.10);
  --radius:18px; --radius-sm:13px; --shadow:0 1px 2px rgba(58,43,32,.04),0 8px 24px rgba(58,43,32,.06);
  /* A second, deeper step for anything that should read as lifted off the
     page - the balance card, a method tile. One shadow for everything is
     what made the app look flat and samey. */
  --shadow-lift:0 2px 4px rgba(58,43,32,.06),0 14px 34px rgba(58,43,32,.13);
  --shadow-lg:0 16px 40px rgba(58,43,32,.16);
  --bar-h:60px;                 /* bottom nav height */
  --safe-top:env(safe-area-inset-top,0px);
  --safe-bottom:env(safe-area-inset-bottom,0px);
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
  background:var(--bg);color:var(--text);
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  -webkit-font-smoothing:antialiased;overscroll-behavior-y:none;
}
img{max-width:100%}
a{color:var(--accent);text-decoration:none}
.muted{color:var(--muted)}
.small{font-size:12px}
.center{text-align:center}
.hidden{display:none!important}
.err{color:var(--red);font-size:13px;min-height:18px;text-align:center;margin-top:8px}
.ok-text{color:var(--green)}
.spread{display:flex;justify-content:space-between;align-items:center;gap:10px}
.row-gap{display:flex;gap:8px;align-items:center;flex-wrap:wrap}

/* ------------------------------------------------------------------ buttons */
.btn{border:1px solid var(--line);background:var(--surface);color:var(--text);
  padding:12px 18px;border-radius:12px;font-size:15px;font-weight:600;cursor:pointer;
  transition:.12s;display:inline-flex;align-items:center;justify-content:center;gap:8px}
.btn:active{transform:scale(.98)}
.btn.primary{background:var(--brand);border-color:var(--brand);color:#faf6ee}
.btn.primary:active{background:var(--brand-2)}
.btn.accent{background:var(--accent);border-color:var(--accent);color:#fff}
.btn.ghost{background:transparent}
.btn.danger{color:var(--red);border-color:#e6cdc9}
.btn.primary.danger{background:var(--red);border-color:var(--red);color:#fff}   /* destructive confirm */
.btn.block{width:100%}
.btn.big{padding:15px;font-size:16px}
.btn.sm{padding:8px 12px;font-size:13px;border-radius:10px}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.pill{border-radius:22px}

/* ------------------------------------------------------------------ inputs */
.field{width:100%;padding:13px 14px;background:var(--surface);border:1px solid var(--line);
  border-radius:12px;color:var(--text);font-size:15px;margin-bottom:12px;font-family:inherit}
.field:focus{outline:none;border-color:var(--accent)}
textarea.field{resize:vertical;min-height:88px;line-height:1.4}
label.lbl{display:block;font-size:12px;color:var(--muted);margin:6px 2px 5px;font-weight:600}
select.field{appearance:none;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='%2393856f' d='M6 8L0 0h12z'/></svg>");background-repeat:no-repeat;background-position:right 14px center}
/* iOS Safari/WebKit renders date & time inputs with a native box that ignores width and
   pushes its value/picker outside the field. Reset the appearance so they honour .field. */
input[type="date"].field,input[type="time"].field,input[type="datetime-local"].field{-webkit-appearance:none;appearance:none;min-width:0;min-height:47px;display:block}
input[type="date"].field::-webkit-date-and-time-value,input[type="time"].field::-webkit-date-and-time-value,input[type="datetime-local"].field::-webkit-date-and-time-value{text-align:left;margin:0}
input[type="date"].field::-webkit-calendar-picker-indicator{margin:0;opacity:.6}

/* ------------------------------------------------------------------ layout shell */
#app{min-height:100%}
.screen{display:none;min-height:100vh;min-height:100dvh}
.screen.active{display:block}

/* top bar */
.appbar{position:sticky;top:0;z-index:20;background:var(--surface);
  border-bottom:1px solid var(--line);padding:calc(var(--safe-top) + 10px) 16px 10px;
  display:flex;align-items:center;gap:12px;min-height:calc(54px + var(--safe-top))}
.appbar .title{font-size:17px;font-weight:700;letter-spacing:.3px}
.appbar .sub{font-size:12px;color:var(--muted)}
.appbar img.logo{height:30px;width:auto}
.appbar .back{background:none;border:none;font-size:22px;color:var(--brand);cursor:pointer;
  width:34px;height:34px;display:flex;align-items:center;justify-content:center;margin-left:-6px}
.appbar .actions{margin-left:auto;display:flex;gap:8px;align-items:center}
.iconbtn{background:var(--surface2);border:1px solid var(--line);border-radius:11px;width:38px;height:38px;
  display:flex;align-items:center;justify-content:center;font-size:17px;cursor:pointer;color:var(--brand);position:relative}
.iconbtn .dot{position:absolute;top:-3px;right:-3px;min-width:17px;height:17px;padding:0 4px;border-radius:9px;
  background:var(--red);color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;border:2px solid var(--surface)}

/* scrollable body region above the bottom nav */
.body{padding:16px;padding-bottom:calc(var(--bar-h) + var(--safe-bottom) + 20px)}
.body.no-nav{padding-bottom:calc(var(--safe-bottom) + 24px)}

/* bottom tab nav */
.tabbar{position:fixed;left:0;right:0;bottom:0;z-index:20;background:var(--surface);
  border-top:1px solid var(--line);display:flex;height:calc(var(--bar-h) + var(--safe-bottom));
  padding-bottom:var(--safe-bottom);box-shadow:0 -4px 18px rgba(58,43,32,.06)}
.tabbar button{flex:1;background:none;border:none;color:var(--muted);cursor:pointer;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;font-size:10.5px;font-weight:600;position:relative}
.tabbar button .ic{font-size:21px;line-height:1}
.tabbar button.active{color:var(--brand)}
.tabbar button.active .ic{transform:translateY(-1px)}
.tabbar button .badge-dot{position:absolute;top:6px;right:calc(50% - 16px);min-width:16px;height:16px;padding:0 4px;
  border-radius:8px;background:var(--red);color:#fff;font-size:9px;font-weight:700;display:flex;align-items:center;justify-content:center}

/* ------------------------------------------------------------------ cards */
/* The border is now a hairline at 55% opacity rather than a full line: with
   a shadow underneath, a solid 1px edge reads as a box drawn around the
   content instead of a surface sitting above it. */
.card{background:var(--surface);border:1px solid var(--hair);
  border-radius:var(--radius);padding:16px;box-shadow:var(--shadow);margin-bottom:12px}
.card.tap{cursor:pointer;transition:transform .12s,box-shadow .12s}
.card.tap:active{transform:scale(.988)}
.card.tap:hover{box-shadow:var(--shadow-lift)}
.card h3{font-size:16px;margin-bottom:4px}
/* A LABEL, NOT A HEADLINE. These were brand-brown at 13px and competed with
   the content under them; on a page with four sections that is four things
   shouting. Muted, smaller and widely tracked reads as a divider - which is
   what a section title is for. */
.section-title{font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;
  letter-spacing:1.3px;margin:22px 2px 9px;display:flex;justify-content:space-between;align-items:center}
.section-title:first-child{margin-top:4px}
.section-title a{font-size:12px;font-weight:600;text-transform:none;letter-spacing:0}
/* consistent refresh pill (↻ spins while loading) */
.refresh-btn{display:inline-flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--line);
  border-radius:999px;padding:4px 12px;font-size:12px;font-weight:600;color:var(--brand);cursor:pointer;
  text-transform:none;letter-spacing:0;transition:background .15s,border-color .15s,transform .1s}
.refresh-btn:hover{background:var(--accent-soft);border-color:var(--accent)}
.refresh-btn:active{transform:scale(.95)}
.refresh-btn .ric{font-size:14px;line-height:1;display:inline-block}
.refresh-btn .ric.spin{animation:ricspin .6s linear infinite}
@keyframes ricspin{to{transform:rotate(360deg)}}

/* hero / greeting */
.hero{background:linear-gradient(135deg,var(--brand),var(--accent));color:#fbf6ee;
  border-radius:var(--radius);padding:20px;box-shadow:var(--shadow);margin-bottom:16px}
.hero .hi{font-size:13px;opacity:.85}
.hero .name{font-size:23px;font-weight:700;margin-top:2px}
.hero .meta{margin-top:10px;font-size:13px;opacity:.9;display:flex;gap:16px;flex-wrap:wrap}

/* chips */
.chips{display:flex;gap:8px;flex-wrap:wrap}
.chip{background:var(--surface);border:1px solid var(--line);color:var(--muted);
  padding:7px 13px;border-radius:20px;font-size:13px;font-weight:600;cursor:pointer}
.chip.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.chip.soft{background:var(--accent-soft);color:var(--brand);border-color:transparent;cursor:default}
.badge{font-size:11px;padding:3px 9px;border-radius:20px;font-weight:700}
.badge.green{background:#dceadb;color:var(--green)}
.badge.amber{background:#f3e2c9;color:#a8722b}
.badge.red{background:#f0dcd9;color:var(--red)}
.badge.blue{background:#dbe6f0;color:var(--blue)}
.badge.soft{background:var(--accent-soft);color:var(--brand)}

/* avatars */
.avatar{border-radius:50%;object-fit:cover;background:var(--accent-soft);flex:none;position:relative;
  display:flex;align-items:center;justify-content:center;color:var(--brand);font-weight:700}
.avatar img{border-radius:50%}
.avatar.xl{width:96px;height:96px;font-size:34px}
.avatar.lg{width:56px;height:56px;font-size:20px}
.avatar.md{width:44px;height:44px;font-size:16px}
.avatar.sm{width:34px;height:34px;font-size:13px}
/* status emoji shown at the lower-right of the avatar (visible to everyone) */
.avatar-status{position:absolute;right:-3px;bottom:-3px;background:var(--surface,#fff);border:1.5px solid var(--surface,#fff);
  border-radius:50%;line-height:1;display:flex;align-items:center;justify-content:center;box-shadow:0 1px 3px rgba(0,0,0,.28)}
.avatar.xl .avatar-status{font-size:20px;width:30px;height:30px}
.avatar.lg .avatar-status{font-size:14px;width:21px;height:21px}
.avatar.md .avatar-status{font-size:11px;width:17px;height:17px}
.avatar.sm .avatar-status{font-size:9px;width:14px;height:14px}

/* list rows */
/* One rounded container with hairline rows, the way the credits ledger
   reads - rather than each row carrying its own border and shadow, which
   stacks into a ladder of boxes. */
.list{background:var(--surface);border:1px solid var(--hair);
  border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow)}
.list .item{display:flex;align-items:center;gap:12px;padding:13px 15px;
  border-bottom:1px solid var(--hair);cursor:pointer;transition:background .12s}
.list .item:last-child{border-bottom:none}
.list .item:active{background:var(--surface2)}
.list .item .grow{flex:1;min-width:0}
.list .item .grow .t{font-weight:600;font-size:14.5px;letter-spacing:-.1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.list .item .grow .s{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
.list .item .chev{color:var(--muted);font-size:18px}

/* Swipe-to-reveal — a row that slides aside to show what can be done to it.
   The actions sit UNDER the row rather than being drawn on drag, so they are
   already laid out when the finger starts moving and nothing reflows mid-swipe.
   touch-action:pan-y hands vertical scrolling back to the browser: without it
   the list stops scrolling the moment a row is touched. */
.swipe{position:relative;overflow:hidden;background:var(--surface)}
.list .swipe{border-bottom:1px solid var(--line)}
.list .swipe:last-child{border-bottom:none}
.list .swipe .item{border-bottom:none}
.swipe-actions{position:absolute;top:0;right:0;bottom:0;display:flex}
.swipe-actions button{border:none;color:#fff;width:76px;cursor:pointer;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  font-size:11.5px;font-weight:700;letter-spacing:.2px;padding:0}
.swipe-actions button span{font-size:18px;line-height:1}
.sa-archive{background:var(--accent)}
.sa-delete{background:#b3261e}
/* Leaving is not deleting, so it must not look like it. A group row carries
   both, and red on both would make the safe one look as final as the
   irreversible one. */
.sa-leave{background:#8a6a4a}
.swipe-face{position:relative;background:var(--surface);touch-action:pan-y;
  transition:transform .18s cubic-bezier(.25,.8,.35,1);will-change:transform}
.swipe.dragging .swipe-face{transition:none}
/* Keyboard and screen-reader route to the same actions: a gesture nobody can
   see is not a feature for anyone who cannot make it. */
.swipe-face:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* menu */
.menu-cat{font-size:13px;font-weight:700;color:var(--brand);text-transform:uppercase;letter-spacing:.6px;margin:18px 2px 8px}
.menu-item{display:flex;gap:12px;padding:12px 0;border-bottom:1px solid var(--line)}
.menu-item:last-child{border-bottom:none}
.menu-item .mi-grow{flex:1}
.menu-item .mi-name{font-weight:600;font-size:15px}
.menu-item .mi-desc{font-size:12.5px;color:var(--muted);margin-top:2px}
.menu-item .mi-price{color:var(--accent);font-weight:700;white-space:nowrap}

/* badge strip (profile — yours and other members') */
.badge-strip{display:flex;gap:9px;overflow-x:auto;padding:2px 2px 6px;-webkit-overflow-scrolling:touch}
.badge-strip::-webkit-scrollbar{display:none}
.bs-item{flex:none;width:78px;background:var(--surface);border:1px solid var(--line);border-radius:14px;
  padding:10px 6px;text-align:center;box-shadow:var(--shadow);cursor:pointer}
.bs-item:active{background:var(--surface2)}
.bs-ic{font-size:26px;line-height:1.1}
.bs-t{font-size:10.5px;font-weight:700;color:var(--brand);margin-top:5px;line-height:1.2}

/* order-ahead: POS-style category tabs + product tiles + order summary */
.ord-tabs{display:flex;gap:8px;overflow-x:auto;padding:2px 2px 10px;-webkit-overflow-scrolling:touch}
.ord-tabs::-webkit-scrollbar{display:none}
.ord-tabs button{flex:none;background:var(--surface);border:1px solid var(--line);color:var(--muted);
  padding:8px 15px;border-radius:20px;font-weight:600;font-size:13px;cursor:pointer;white-space:nowrap}
.ord-tabs button.active{background:var(--accent);color:#fff;border-color:var(--accent)}
/* 105px was too narrow the moment photos arrived — "CROMA Signature Drink
   (Caramel Cinnamon)" wrapped to four lines and every tile in that row grew to
   match. 150px fits two comfortably on the narrowest phone and three on a
   tablet, and the name is clamped so no single product can stretch a row. */
.ord-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
.ord-tile{position:relative;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-sm);
  /* Padding moved onto .ot-tx so the photo can run to the tile's edges, and
     the column now starts at the top: justify-content:flex-end was pinning
     everything to the bottom, which is what made a tile with a photo and one
     without look like two different components. */
  padding:0;overflow:hidden;box-shadow:var(--shadow);cursor:pointer;transition:.12s;min-height:172px;
  display:flex;flex-direction:column}
.ord-tile:active{background:var(--surface2);transform:scale(.97)}
.ord-tile.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft),var(--shadow)}
.ord-tile .ot-name{font-weight:600;font-size:13px;line-height:1.3;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;min-height:2.6em}
/* margin-top:auto pins the price to the bottom, so it sits on the same line
   across a row whatever the names above it did. */
.ord-tile .ot-price{color:var(--accent);font-weight:700;font-size:13px;margin-top:auto;padding-top:5px}
.ord-tile .ot-temp{position:absolute;top:7px;left:7px;z-index:1;font-size:10px;background:rgba(255,255,255,.92);
  border:1px solid var(--line);border-radius:20px;padding:0 6px;line-height:15px;color:var(--muted)}
.ord-tile .ot-qty{position:absolute;top:-6px;right:-6px;min-width:22px;height:22px;border-radius:11px;
  background:var(--accent);color:#fff;font-size:12px;font-weight:800;display:flex;align-items:center;
  justify-content:center;padding:0 5px;box-shadow:var(--shadow)}
.ord-sum-line{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--line)}
.ord-sum-line:last-child{border-bottom:none}
.ord-sum-line .osl-n{flex:1;font-size:14px;min-width:0}
.ord-step{width:28px;height:28px;flex:none;border-radius:9px;border:1px solid var(--line);
  background:var(--surface2);color:var(--brand);font-size:17px;font-weight:700;line-height:1;cursor:pointer}
.ord-step.plus{background:var(--accent);color:#fff;border-color:var(--accent)}

/* feed posts */
.post{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:15px;box-shadow:var(--shadow);margin-bottom:14px}
/* THE CAFÉ'S OWN POSTS.
   Recognisable, not loud: a warmer surface and a marked border, so the shop
   reads as the shop while the feed still reads as the members'. A pinned post
   additionally wears a ribbon and sits at the top — see feedRows for why those
   are two different things. */
.post.official{border-color:var(--brand,#7a5c3e);background:linear-gradient(180deg,rgba(122,92,62,.055),transparent 120px)}
.post.pinned{box-shadow:0 0 0 1px var(--brand,#7a5c3e),var(--shadow)}
.ppin{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;
      letter-spacing:.02em;color:var(--brand,#7a5c3e);margin:-3px 0 9px}
.pverif{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
        border-radius:50%;background:var(--brand,#7a5c3e);color:#fff;font-size:9.5px;
        font-weight:700;vertical-align:1px;margin-left:3px}
.post .phead{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.post .pname{font-weight:600;font-size:14px}
.post .ptime{font-size:11.5px;color:var(--muted)}
.post .ptext{font-size:14.5px;line-height:1.5;white-space:pre-wrap;word-break:break-word}
/* long posts collapse to ~5 lines so one essay can't push the rest of the feed off screen */
.post .ptext.clamped{display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow:hidden}
.post .ptext-more{background:none;border:none;padding:4px 0 0;color:var(--brand);font-weight:700;font-size:13px;cursor:pointer;font-family:inherit}
.post img.pimg{width:100%;border-radius:12px;margin-top:10px}
.post .pacts{display:flex;gap:18px;margin-top:12px;padding-top:10px;border-top:1px solid var(--line)}
.post .pacts button{background:none;border:none;color:var(--muted);font-weight:600;font-size:13px;cursor:pointer;display:flex;align-items:center;gap:6px}
.post .pacts button.liked{color:var(--red)}

/* composer */
.composer{display:flex;gap:10px;margin-bottom:16px}
.composer .field{margin-bottom:0}

/* chat */
/* A CONVERSATION IS A COLUMN, NOT A ROOM.
   This filled whatever width it was given, so on a laptop the input stretched
   two thousand pixels and the bubbles sat marooned at either end. Every
   messaging app caps the reading width for the same reason a newspaper does:
   past about 70 characters the eye loses the start of the next line. The cap
   does nothing on a phone, which is narrower than it anyway. */
.chat-wrap{display:flex;flex-direction:column;width:100%;max-width:760px;margin:0 auto;
  height:calc(100vh - 55px - var(--safe-top));height:calc(100dvh - 55px - var(--safe-top))}
.chat-scroll{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}
.bubble{max-width:78%;padding:10px 14px;border-radius:16px;font-size:14.5px;line-height:1.4;word-break:break-word}
.bubble.them{background:var(--surface);border:1px solid var(--line);align-self:flex-start;border-bottom-left-radius:5px}
.bubble.me{background:var(--brand);color:#faf6ee;align-self:flex-end;border-bottom-right-radius:5px}
.bubble .bt{font-size:10px;opacity:.65;margin-top:4px;text-align:right}
.chat-input{display:flex;gap:8px;align-items:center;
  padding:10px 12px calc(10px + var(--safe-bottom));
  border-top:1px solid var(--line);background:var(--surface)}
/* min-width:0 so the field can SHRINK. A flex item defaults to min-content
   width, which is why the box refused to give room to the buttons beside it and
   came out squashed on a narrow screen and stretched on a wide one. */
.chat-input .field{flex:1;min-width:0;margin-bottom:0;border-radius:22px}
/* PROPER TARGETS. These were sized by their inline padding alone, so their
   height came from the line-height of a single emoji — a 20px-tall button in a
   row 47px deep, which is both hard to hit and looks like a mistake next to a
   full-height field. 44px square is the accepted minimum for a finger and
   happens to match the input's height, so the row reads as one control. */
.chat-input .btn,.chat-input .emoji-btn{flex:none;width:44px;height:44px;padding:0;
  border-radius:50%;font-size:19px;line-height:1;
  display:inline-flex;align-items:center;justify-content:center}
.chat-input .emoji-wrap{flex:none}
/* Send is the primary action in the row and should look like it. */
.chat-input .btn.accent{font-size:17px}
.day-sep{align-self:center;font-size:11px;color:var(--muted);background:var(--surface2);padding:4px 12px;border-radius:12px;margin:6px 0}

/* ---- reactions ------------------------------------------------------------
   A message is now a bubble PLUS whatever is stuck to it, so each one is
   wrapped. The wrapper carries the left/right alignment the bubble used to
   carry itself, which keeps the reaction row hanging off the correct corner
   without any positioning maths. */
/* The ⋯ on a post. Sits in the header row opposite the name, which is where
   every feed puts it, and is deliberately low-contrast: it is there when
   wanted and not competing with the post. */
.post .phead{display:flex;align-items:flex-start;gap:10px}
.pmore{background:none;border:none;color:var(--muted);font-size:20px;line-height:1;
  padding:2px 4px;cursor:pointer;flex:none;border-radius:8px}
.pmore:hover{background:var(--surface2);color:var(--text)}

/* A published rating, read-only. Deliberately quieter than the tappable stars
   below it: this is information, not a control, and it should not invite a tap
   that does nothing. */
.mi-rate{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:700;color:var(--text)}
.mi-rate .st{color:var(--amber);letter-spacing:.5px}
.mi-rate .n{font-weight:500;color:var(--muted)}
.mi-rate.sm{font-size:11px}
.mi-rate.sm .st{letter-spacing:0}

/* Stars. Big enough to hit with a thumb without being the loudest thing on a
   receipt — 20px glyphs in a 30px target, which clears the 24px minimum while
   staying quiet until somebody looks for it. */
.rate{display:inline-flex;gap:1px;flex:none}
.rate button{background:none;border:none;cursor:pointer;font-size:20px;line-height:1;
  padding:4px 3px;min-width:30px;color:var(--line);transition:transform .12s cubic-bezier(.2,1.3,.4,1),color .12s ease}
.rate button.on{color:var(--amber)}
.rate button:active{transform:scale(1.25)}
/* Hovering a star lights the ones before it, so it is obvious the rating is
   cumulative rather than five separate buttons. */
.rate:hover button{color:var(--amber)}
.rate button:hover ~ button{color:var(--line)}

/* ---- help pages -----------------------------------------------------------
   Long-form reading, which nothing else in this app is. Everything here is
   about making a policy finishable: a privacy policy set like a chat bubble is
   one nobody gets to the end of. */
.list .item .hi{font-size:19px;width:34px;text-align:center;flex:none}

/* The index. A quiet masthead rather than a card, so the page does not open
   with a box telling you there are boxes below. */
.help-hero{padding:4px 2px 18px}
.help-hero .hh-t{font-size:23px;font-weight:800;letter-spacing:-.3px;line-height:1.2}
.help-hero .hh-s{font-size:14px;line-height:1.55;color:var(--muted);margin-top:7px;max-width:44ch}

.help-list{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;box-shadow:var(--shadow)}
.help-row{display:flex;align-items:center;gap:13px;padding:15px 15px;cursor:pointer;
  border-bottom:1px solid var(--line);transition:background .13s}
.help-row:last-child{border-bottom:none}
.help-row:active{background:var(--surface2)}
/* Icon in a tinted disc: five emoji at different optical weights on a plain
   background read as five different sizes. The disc makes them one family. */
.help-row .hr-i{flex:none;width:38px;height:38px;border-radius:50%;background:var(--accent-soft);
  display:flex;align-items:center;justify-content:center;font-size:18px}
.help-row .hr-tx{flex:1;min-width:0}
.help-row .hr-t{font-weight:700;font-size:15px;line-height:1.25}
.help-row .hr-s{font-size:12.5px;color:var(--muted);line-height:1.4;margin-top:3px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.help-row .hr-c{color:var(--muted);font-size:20px;flex:none}

/* The one row that is an ACTION, set apart from the five that are reading. */
.help-ask{display:flex;align-items:center;gap:13px;margin-top:14px;padding:15px;
  background:var(--brand);color:#faf6ee;border-radius:var(--radius);cursor:pointer;
  box-shadow:var(--shadow);transition:transform .12s}
.help-ask:active{transform:scale(.99)}
.help-ask .ha-i{flex:none;width:38px;height:38px;border-radius:50%;background:rgba(255,255,255,.14);
  display:flex;align-items:center;justify-content:center;font-size:18px}
.help-ask .ha-t{font-weight:700;font-size:15px}
.help-ask .ha-s{font-size:12.5px;opacity:.82;margin-top:2px;line-height:1.4}
.help-ask .hr-c{color:rgba(255,255,255,.7)}
.help-foot{text-align:center;font-size:11.5px;color:var(--muted);margin-top:22px;line-height:1.6}

/* A page. */
.doc-head{display:flex;align-items:center;gap:12px;margin-bottom:16px}
.doc-head .dh-i{flex:none;width:42px;height:42px;border-radius:50%;background:var(--accent-soft);
  display:flex;align-items:center;justify-content:center;font-size:20px}
.doc-head h2{font-size:22px;font-weight:800;letter-spacing:-.3px;line-height:1.2}

.prose{font-size:15px;line-height:1.68;color:var(--text)}
/* The standfirst — larger, and the thing somebody reads before deciding
   whether to read the rest. */
.prose .lead{font-size:16.5px;line-height:1.55;color:var(--text);font-weight:500;
  margin:0 0 20px;padding-bottom:18px;border-bottom:1px solid var(--line)}
/* A heading earns a rule above it rather than just space: at this length the
   eye needs somewhere to rest between sections. */
.prose h3{font-size:14px;font-weight:800;color:var(--brand);margin:26px 0 8px;
  letter-spacing:.2px;position:relative;padding-top:14px}
.prose h3::before{content:'';position:absolute;top:0;left:0;width:26px;height:2px;
  background:var(--accent);border-radius:1px}
.prose h3:first-child{margin-top:0;padding-top:0}
.prose h3:first-child::before{display:none}
.prose p{margin:0 0 14px}
.prose p:last-child{margin-bottom:0}
.prose ul{margin:0 0 14px;padding:0;list-style:none}
.prose li{position:relative;padding-left:16px;margin-bottom:7px;line-height:1.55}
.prose li::before{content:'';position:absolute;left:2px;top:.62em;width:5px;height:5px;
  border-radius:50%;background:var(--accent)}
.prose li:last-child{margin-bottom:0}
.doc-foot{margin-top:26px;padding-top:14px;border-top:1px solid var(--line);
  font-size:11.5px;color:var(--muted)}

/* An order tile with a photo. Conditional rather than a placeholder: most of
   the menu has no photo yet, and a grid of grey boxes reads as broken. */
/* The photo area is always present — see the POS note. A half-photographed
   menu with conditional images came out ragged, which reads as broken. */
.ord-tile .ot-img,.ord-tile .ot-ph{width:100%;aspect-ratio:4/3;object-fit:cover;
  display:block;flex:none;background:var(--surface2);border-bottom:1px solid var(--line)}
.ord-tile .ot-ph{display:flex;align-items:center;justify-content:center;font-size:24px;opacity:.28}
.ord-tile .ot-tx{padding:9px 11px 11px;display:flex;flex-direction:column;flex:1}

/* An avatar that leads to a profile should say so. */
.tapface{cursor:pointer;transition:opacity .12s}
.tapface:active{opacity:.7}

/* A member's profile. One card carries the whole identity — face, name, bio
   and the two numbers — rather than four stacked boxes with rules between
   them, which is what made it read as a form rather than a person. */
.pf-hero{position:relative;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);padding:24px 18px 18px;text-align:center;box-shadow:var(--shadow)}
.pf-hero .avatar{margin:0 auto}
.pf-name{font-size:20px;font-weight:800;letter-spacing:-.2px;margin-top:12px;line-height:1.2}
.pf-sub{font-size:12.5px;color:var(--muted);margin-top:4px}
.pf-bio{font-size:14px;line-height:1.55;color:var(--text);margin:12px auto 0;max-width:34ch}
/* The quiet ⋯. Top-right of the card, where every app puts the thing you only
   want occasionally — not a red button under the posts, which is both hard to
   find on purpose and easy to hit by accident. */
.pf-more{position:absolute;top:12px;right:12px;width:34px;height:34px;border-radius:50%;
  border:none;background:var(--surface2);color:var(--muted);font-size:18px;line-height:1;
  cursor:pointer;display:flex;align-items:center;justify-content:center}
.pf-more:active{background:var(--line)}
.pf-stats{display:flex;gap:10px;margin-top:18px;padding-top:16px;border-top:1px solid var(--line)}
.pf-stats .s{flex:1}
.pf-stats .s .v{font-size:20px;font-weight:800;line-height:1.1}
.pf-stats .s .l{font-size:11.5px;color:var(--muted);margin-top:3px;
  text-transform:uppercase;letter-spacing:.5px}
.pf-empty{text-align:center;color:var(--muted);font-size:13px;padding:22px 0}
.pf-ev .pf-ev-ph{width:42px;height:42px;border-radius:9px;background:var(--surface2);
  display:flex;align-items:center;justify-content:center;flex:none;font-size:18px}
.pf-post{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  padding:13px 14px;margin-bottom:10px;box-shadow:var(--shadow)}
.pf-post .t{font-size:14.5px;line-height:1.5;white-space:pre-wrap;word-break:break-word}
.pf-post img{width:100%;border-radius:10px;margin-top:9px;display:block}
.pf-post .m{font-size:11.5px;color:var(--muted);margin-top:8px}
.pf-ev{display:flex;gap:11px;align-items:center;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius-sm);padding:10px 12px;margin-bottom:8px}
.pf-ev img{width:42px;height:42px;border-radius:9px;object-fit:cover;flex:none}
.pf-ev .n{font-weight:700;font-size:14px;line-height:1.25}
.pf-ev .d{font-size:11.5px;color:var(--muted);margin-top:2px}

/* ---- inbox tabs -----------------------------------------------------------
   Scrolls sideways rather than wrapping: five tabs will not fit across a phone,
   and a strip that reflows to two rows moves the whole list down every time the
   badges change. */
/* The top padding is load-bearing: overflow-x:auto clips vertically too, and
   the count sits above the tab's top edge — with 2px here the badges were
   sliced off, which is a notification you cannot see. */
.inbox-tabs{display:flex;gap:8px;overflow-x:auto;padding:10px 2px 12px;
  -webkit-overflow-scrolling:touch;scrollbar-width:none}
.inbox-tabs::-webkit-scrollbar{display:none}
.itab{flex:none;position:relative;background:var(--surface);border:1px solid var(--line);
  color:var(--muted);border-radius:14px;padding:9px 15px 8px;cursor:pointer;
  display:flex;flex-direction:column;align-items:center;gap:3px;
  font-size:12.5px;font-weight:600;line-height:1.2;min-width:78px;
  transition:background .14s ease,border-color .14s ease,color .14s ease}
.itab .ic{font-size:17px;line-height:1}
.itab.on{background:var(--accent);border-color:var(--accent);color:#fff}
/* The count sits ON the tab, not after the word — the label stays put as the
   number changes, so the strip does not shuffle while you are reading it. */
.itab-n{position:absolute;top:-6px;right:-4px;background:var(--red,#b3261e);color:#fff;
  border-radius:999px;font-size:10px;font-weight:800;line-height:1;
  padding:3px 5px;min-width:16px;text-align:center;box-shadow:0 0 0 2px var(--bg)}
.itab.on .itab-n{box-shadow:0 0 0 2px var(--accent)}
.list .item.unread{background:var(--accent-soft)}
.badge.grey{background:var(--surface2);color:var(--muted)}

/* ---- groups ---------------------------------------------------------------
   A group has no single avatar, and inventing one — an icon, an initial — tells
   you nothing about WHICH group it is. Two or three overlapping faces do, at a
   glance, which is the entire job of a thread list. Sized to the same 44px box
   a normal avatar occupies so the rows stay aligned. */
.ga{position:relative;width:44px;height:44px;flex:none}
.ga-f{position:absolute;border-radius:50%;box-shadow:0 0 0 2px var(--surface)}
.ga-f:nth-child(1){top:0;left:0}
.ga-f:nth-child(2){bottom:0;right:0}
.ga-f:nth-child(3){bottom:0;left:0;opacity:.9}
.ga .avatar.sm{width:26px;height:26px;font-size:10px}

/* Who said it — shown only in a group, where the side of the bubble is not
   enough to tell six people apart. */
.chat-msg .who{font-size:11.5px;font-weight:700;color:var(--muted);
  margin:0 0 2px 12px;letter-spacing:.1px}

/* A system line is not something anybody said, so it is not a bubble. */
.day-sep.sys{max-width:82%;text-align:center;line-height:1.35;background:transparent;
  color:var(--muted);padding:2px 8px}

/* The reporter's own status, pinned above the conversation. Reporting
   something and then hearing nothing is its own bad experience — you cannot
   tell whether it was read, acted on, or lost. */
.chat-flag{display:flex;gap:10px;align-items:flex-start;padding:10px 14px;
  border-bottom:1px solid var(--line);background:var(--surface2)}
.chat-flag .cf-i{font-size:16px;line-height:1.3;flex:none}
.chat-flag .cf-t{font-weight:700;font-size:13px}
.chat-flag .cf-s{font-size:11.5px;color:var(--muted);margin-top:1px}
.chat-flag.amber{background:#fdf4e3;border-bottom-color:#efd9ab}
.chat-flag.amber .cf-t{color:#8a5d10}
.chat-flag.green{background:#eaf5ec;border-bottom-color:#c3e0c9}
.chat-flag.green .cf-t{color:#2f6b3b}
.chat-flag.grey .cf-t{color:var(--muted)}

/* chat-msg, NOT msg: `.msg` is already the empty-state text in core.js
   (`.empty .msg`), and a bare `.msg{display:flex}` here would have quietly
   restyled every empty state in the app. */
.chat-msg{display:flex;flex-direction:column;max-width:100%}
.chat-msg.me{align-items:flex-end}
.chat-msg.them{align-items:flex-start}
/* Pulled up into the bubble's bottom edge, the way a sticker sits on a card. */
.rx-row{display:flex;gap:4px;flex-wrap:wrap;margin-top:-7px;padding:0 6px;position:relative;z-index:1}
.rx-row:empty{display:none}
.rx{display:inline-flex;align-items:center;gap:3px;background:var(--surface);
  border:1px solid var(--line);border-radius:999px;padding:2px 7px;
  font-size:12.5px;line-height:1.35;cursor:pointer;user-select:none;
  box-shadow:0 1px 4px rgba(58,43,32,.12);
  transition:transform .13s cubic-bezier(.2,1.2,.4,1),background .13s ease,border-color .13s ease}
.rx .n{font-weight:700;font-size:11px;color:var(--muted)}
.rx.mine{background:var(--accent-soft);border-color:var(--accent)}
.rx.mine .n{color:var(--brand)}
.rx:active{transform:scale(.9)}
/* Only the chip that just changed animates. Replaying every reaction on each
   four-second poll would make a busy conversation twitch. */
@keyframes rxPop{0%{transform:scale(.3);opacity:0}55%{transform:scale(1.18)}100%{transform:scale(1);opacity:1}}
.rx.pop{animation:rxPop .28s cubic-bezier(.2,1.35,.35,1) both}

/* The picker: a floating row, placed against the bubble it belongs to. */
.rx-pick{position:fixed;z-index:130;display:flex;gap:1px;background:var(--surface);
  border:1px solid var(--line);border-radius:999px;padding:5px 7px;
  box-shadow:0 10px 30px rgba(58,43,32,.28);transform-origin:var(--rx-origin,50% 100%);
  animation:rxIn .19s cubic-bezier(.2,1.25,.35,1) both}
@keyframes rxIn{from{transform:scale(.55) translateY(8px);opacity:0}
                to{transform:scale(1) translateY(0);opacity:1}}
.rx-pick.out{animation:rxOut .13s ease both}
@keyframes rxOut{to{transform:scale(.8);opacity:0}}
.rx-pick button{border:none;background:none;font-size:25px;line-height:1;
  padding:3px 5px;cursor:pointer;border-radius:50%;
  transition:transform .13s cubic-bezier(.2,1.3,.4,1)}
.rx-pick button:hover{transform:scale(1.3)}
.rx-pick button:active{transform:scale(1.05)}
.rx-pick button.on{background:var(--accent-soft)}
/* Copy is not a reaction, so it does not sit in the same run as the emoji. */
.rx-sep{width:1px;align-self:stretch;background:var(--line);margin:2px 4px}
.rx-pick button[data-copy]{font-size:18px;color:var(--muted)}
/* LONG-PRESS MUST NOT START A SELECTION.
   -webkit-touch-callout only suppresses iOS's link/image callout. On Android
   the long press begins a text selection first, which pops the Copy / Select
   all bar over the reaction picker and takes the touch with it — so the picker
   opened underneath a toolbar and could not be tapped.

   user-select:none stops that at the source. It costs the ability to select
   message text by hand, which is why Copy is in the picker: the capability is
   moved, not removed. */
.bubble{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
/* The reaction chips are not text either. */
.rx,.rx-row{-webkit-user-select:none;user-select:none}
.bubble.pressing{transform:scale(.975);transition:transform .16s ease}
@media (prefers-reduced-motion:reduce){
  .rx.pop,.rx-pick{animation:none}
  .rx,.rx-pick button,.bubble.pressing{transition:none}
}

/* empty state */
.empty{text-align:center;color:var(--muted);padding:48px 22px}
.empty .ico{font-size:34px;margin-bottom:12px;opacity:.55}
.empty .msg{font-size:13.5px;line-height:1.5}

/* auth screens */
.auth{min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;justify-content:center;
  padding:24px;padding-top:calc(24px + var(--safe-top));
  background:radial-gradient(900px 460px at 50% -10%,#f3ede1,transparent),var(--bg)}
.auth .logo-wrap{text-align:center;margin-bottom:22px}
.auth .logo-wrap img{height:72px}
.auth .logo-wrap .tag{letter-spacing:2px;font-size:12px;color:var(--muted);font-weight:600;margin-top:12px}
.auth-card{background:var(--surface);border:1px solid var(--line);border-radius:22px;padding:24px;box-shadow:var(--shadow-lg)}
.auth-card h2{font-size:20px;margin-bottom:4px}
.auth-card .sub{color:var(--muted);font-size:13.5px;margin-bottom:18px}
.gbtn{width:100%;background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px;font-size:15px;
  font-weight:600;color:#3c4043;display:flex;align-items:center;justify-content:center;gap:10px;cursor:pointer}
.gbtn img,.gbtn svg{width:19px;height:19px}
.divider{display:flex;align-items:center;gap:12px;margin:16px 0;color:var(--muted);font-size:12px}
.divider::before,.divider::after{content:"";flex:1;height:1px;background:var(--line)}
.switch-line{text-align:center;margin-top:16px;font-size:13.5px;color:var(--muted)}

/* profile */
.profile-head{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;padding:8px 0 4px}
.profile-head .pn{font-size:21px;font-weight:700}
.avatar-edit{position:relative;cursor:pointer}
.avatar-edit .cam{position:absolute;right:-2px;bottom:-2px;width:32px;height:32px;border-radius:50%;
  background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-size:15px;border:3px solid var(--surface)}
.stat-row{display:flex;gap:10px;margin:14px 0}
.stat{flex:1;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-sm);padding:12px;text-align:center;box-shadow:var(--shadow)}
.stat .v{font-size:20px;font-weight:700}
.stat .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin-top:2px}

/* event card */
.event-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);margin-bottom:14px}
.event-card .banner{height:120px;background:linear-gradient(135deg,var(--accent),var(--brand));display:flex;align-items:flex-end;padding:12px;color:#fff}
.event-card .banner img{position:absolute}
.event-card .ebody{padding:14px}
.event-card .edate{font-size:12px;color:var(--accent);font-weight:700;text-transform:uppercase;letter-spacing:.4px}
.event-card .etitle{font-size:17px;font-weight:700;margin:3px 0 6px}
.event-card .edesc{font-size:13.5px;color:var(--muted);line-height:1.45}
.event-card .efoot{display:flex;justify-content:space-between;align-items:center;margin-top:12px}

/* modal (matches POS: closes only via ✕ / Cancel) */
.modal-backdrop{position:fixed;inset:0;background:rgba(58,43,32,.45);display:flex;align-items:flex-end;justify-content:center;z-index:100}
.modal{background:var(--surface);border-radius:20px 20px 0 0;padding:22px 20px calc(22px + var(--safe-bottom));width:100%;max-width:520px;
  max-height:92vh;overflow:auto;box-shadow:var(--shadow-lg);position:relative;animation:slideup .22s ease}
@keyframes slideup{from{transform:translateY(30px);opacity:.6}to{transform:translateY(0);opacity:1}}
@media(min-width:560px){.modal-backdrop{align-items:center}.modal{border-radius:20px}}
.modal h3{margin-bottom:14px;padding-right:26px}
.modal-x{position:absolute;top:14px;right:14px;background:none;border:none;font-size:24px;line-height:1;color:var(--muted);cursor:pointer}
.modal .grab{width:40px;height:4px;background:var(--line);border-radius:3px;margin:-8px auto 14px}
.modal-actions{display:flex;gap:10px;margin-top:18px}
.modal-actions .btn{flex:1}

/* toast + loading (from POS) */
/* HIDDEN BY OPACITY, NOT BY A DISTANCE.
   It used to park itself with translateY(120px), which only hides a toast if
   its own height plus its offset from the bottom comes to less than 120px.
   The offset alone is --bar-h (60) + 16 + the safe-area inset, so on a phone
   with a home indicator (34px) that is 110px before the toast has any height
   at all: it never left the screen, and sat over the tab bar until something
   else replaced it. A one-line toast on a phone with no inset cleared it by
   about nothing, which is why it looked intermittent rather than broken.

   Opacity cannot be out-grown by a long message or a tall safe area. The
   small slide is now decoration on top of it, and visibility is delayed to
   the end of the fade so the finished toast is really gone.

   pointer-events:none because a fixed element parked over the tab bar still
   swallows taps, visible or not - the Messages tab underneath it was
   catching some of them. */
.toast{position:fixed;bottom:calc(var(--bar-h) + var(--safe-bottom) + 16px);left:50%;
  transform:translateX(-50%) translateY(8px);opacity:0;visibility:hidden;pointer-events:none;
  background:var(--text);color:#faf6ee;padding:12px 22px;border-radius:12px;z-index:200;max-width:88vw;
  text-align:center;font-size:14px;box-shadow:var(--shadow-lg);
  transition:opacity .25s,transform .25s,visibility 0s linear .25s}
.toast.show{transform:translateX(-50%) translateY(0);opacity:1;visibility:visible;
  transition:opacity .25s,transform .25s,visibility 0s}
.loading-overlay{position:fixed;inset:0;background:rgba(233,227,214,.6);backdrop-filter:blur(2px);
  display:none;flex-direction:column;align-items:center;justify-content:center;gap:14px;z-index:300}
.loading-overlay.show{display:flex}
.spinner{width:44px;height:44px;border-radius:50%;border:4px solid var(--accent-soft);border-top-color:var(--brand);animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.skeleton{background:linear-gradient(90deg,var(--surface2) 25%,#eae2d2 50%,var(--surface2) 75%);background-size:200% 100%;animation:sk 1.2s infinite;border-radius:10px}
@keyframes sk{to{background-position:-200% 0}}

/* ---- store credits: wallet header + ledger -------------------------------------------------
   The balance is the thing you open this screen for, so it gets a card of its own rather than a
   number sitting inside a generic panel. The ledger below reads as a statement: day headings, a
   coloured direction dot, and the amount right-aligned so the column scans vertically. */
.wallet{position:relative;overflow:hidden;border-radius:var(--radius);padding:22px 20px 20px;margin-bottom:14px;
  background:linear-gradient(135deg,var(--brand) 0%,var(--brand-2) 55%,#7d5a40 100%);
  box-shadow:var(--shadow-lg);color:#f6efe2;text-align:center}
.wallet::after{content:'';position:absolute;right:-42px;top:-42px;width:140px;height:140px;border-radius:50%;
  background:rgba(255,255,255,.07)}
.wallet-label{font-size:11px;font-weight:700;letter-spacing:1px;text-transform:uppercase;opacity:.72}
.wallet-bal{font-size:42px;font-weight:300;letter-spacing:-1.5px;line-height:1.05;margin-top:5px;position:relative;font-variant-numeric:tabular-nums}
.wallet-sub{font-size:12.5px;opacity:.78;margin-top:7px;line-height:1.45;position:relative}
.led-day{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.7px;
  margin:16px 2px 7px}
.led-day:first-child{margin-top:2px}
.led{display:flex;align-items:center;gap:12px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius-sm);padding:12px 14px;margin-bottom:8px}
.led-ic{flex:none;width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:15px;font-weight:800}
.led-ic.up{background:#dceadb;color:var(--green)}
.led-ic.down{background:var(--accent-soft);color:var(--brand)}
.led-mid{flex:1;min-width:0}
.led-t{font-weight:600;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.led-s{font-size:12px;color:var(--muted);margin-top:2px}
.led-amt{font-weight:500;font-size:15px;white-space:nowrap;letter-spacing:-.2px;font-variant-numeric:tabular-nums}
.led-amt.up{color:var(--green)}

/* pull spacing helper */
.mt8{margin-top:8px}.mt16{margin-top:16px}.mb8{margin-bottom:8px}

/* ---------------------------------------------------------------- pull to refresh
   Sits above the appbar, translated down from off-screen by the drag. Fixed rather than
   in-flow so it never reflows the page it is being dragged over. */
#ptr{
  position:fixed;top:calc(var(--safe-top, 0px) + 4px);left:50%;
  transform:translateX(-50%) translateY(0);
  opacity:0;pointer-events:none;z-index:60;
}
#ptr .ptr-ring{
  width:34px;height:34px;border-radius:50%;
  background:var(--surface);border:1px solid var(--line);
  box-shadow:0 2px 10px rgba(0,0,0,.14);
  display:flex;align-items:center;justify-content:center;
  font-size:17px;color:var(--brand);
  transition:transform .18s ease,background .18s ease;
}
/* Past the trigger point: the ring turns and fills, so you can feel where the threshold is
   without having to read anything. */
#ptr.ready .ptr-ring{transform:rotate(180deg);background:var(--accent);color:#fff;border-color:var(--accent)}
#ptr.spin .ptr-ring{animation:ptr-spin .7s linear infinite;background:var(--accent);color:#fff;border-color:var(--accent)}
@keyframes ptr-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  #ptr .ptr-ring{transition:none}
  #ptr.spin .ptr-ring{animation-duration:1.6s}
}

/* ---- emoji picker ---------------------------------------------------------
   Anchored to the button it hangs off, and z-indexed above the modal it lives
   in — a picker that opens BEHIND the dialog is the same as no picker. */
.emoji-wrap{position:relative;display:inline-block}
.emoji-btn{background:none;border:1px solid var(--line);border-radius:10px;
  padding:6px 10px;font-size:17px;line-height:1;cursor:pointer;color:inherit}
.emoji-btn:hover{background:var(--surface2)}
/* Anchored to the button's RIGHT edge, opening leftwards. Anchored left, a
   296px panel hanging off a button that sits at the right end of the input ran
   straight off the screen — which is exactly what it did. Every emoji button in
   this app is at the right of its row, so right is the correct anchor for all
   of them. */
.emoji-pop{position:absolute;bottom:calc(100% + 6px);right:0;left:auto;z-index:60;
  max-width:calc(100vw - 24px);
  grid-template-columns:repeat(8,1fr);gap:2px;width:296px;max-height:196px;
  overflow-y:auto;padding:8px;border:1px solid var(--line);border-radius:12px;
  background:var(--surface);box-shadow:0 10px 28px rgba(0,0,0,.18)}
.emoji-b{background:none;border:0;font-size:19px;line-height:1;padding:5px;
  border-radius:8px;cursor:pointer}
.emoji-b:hover{background:var(--surface2)}
/* The comment box sits at the bottom of a modal, so its picker must open
   UPWARDS or it is clipped off the end of the dialog. Already does — this is
   only here to say so, since "bottom:100%" above is easy to helpfully break. */

/* ---- chat photos ----------------------------------------------------------
   Capped by VIEWPORT height as well as bubble width: a portrait photo sized
   only by width fills the whole screen and pushes the message it came with
   out of sight. */
.chat-img{display:block;max-width:100%;max-height:46vh;width:auto;border-radius:12px;
  margin-bottom:6px;cursor:pointer;background:var(--surface2)}
.bubble .chat-img:last-child{margin-bottom:2px}
/* The photo waiting to be sent, above the input. */
.chat-pending{display:flex;align-items:center;gap:10px;padding:8px 12px;
  border-top:1px solid var(--line);background:var(--surface2)}
.chat-pending img{height:52px;width:52px;object-fit:cover;border-radius:8px}
.chat-input{display:flex;gap:6px;align-items:center}

/* --- THE MENU IN TILES ----------------------------------------------------
 * This was repeat(2,1fr) inline, at every width. On a phone that is right; on a
 * laptop it produced two tiles roughly 980px wide, each showing a 980x112 slice
 * of a photograph — which is why every cup looked like an extreme crop. The
 * fixed image height was half the fault and the column count was the other.
 *
 * auto-fill decides the count from the space available. The floor is 160px so a
 * 375px phone still gets two across, and 210px above 700px so a laptop lands
 * around five or six rather than eleven postage stamps. */
.menu-tiles{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}
@media(min-width:700px){
  .menu-tiles{gap:12px;grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}
}
/* Height follows width instead of being pinned, so the crop stays the same
   shape whatever the tile is doing. 4:3 keeps a drink recognisable. */
.menu-tiles .mt-img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block}
.menu-tiles .mt-img.ph{display:flex;align-items:center;justify-content:center;
  background:var(--surface2);font-size:30px;opacity:.45}

/* --- THE MEMBERSHIP CARD --------------------------------------------------
 * The member QR used to be a plain white panel in a modal. It is now a
 * credit-card: landscape (real card ratio, 85.60x53.98mm = 1.586), front for
 * the brand and the name, QR on the back, tap to flip. Landscape on purpose —
 * the owner wants it screenshot-able and social-media worthy, and a card is a
 * shape people already photograph.
 *
 * The flip is 3D (perspective + rotateY), not a crossfade: the crossfade reads
 * as two screens, the rotation reads as one object with two sides — which is
 * the whole metaphor. Reduced-motion gets an instant flip: the STATE change is
 * the feature, the animation is decoration.
 */
.mcard-wrap{perspective:1200px;margin-top:16px}
.mcard{position:relative;width:100%;aspect-ratio:1.586/1;cursor:pointer;outline:none;
  -webkit-tap-highlight-color:transparent}
.mcard:focus-visible{outline:3px solid var(--accent);outline-offset:4px;border-radius:18px}
.mcard-in{position:absolute;inset:0;transform-style:preserve-3d;
  transition:transform .65s cubic-bezier(.3,.7,.25,1)}
.mcard.flip .mcard-in{transform:rotateY(180deg)}
@media (prefers-reduced-motion: reduce){ .mcard-in{transition:none} }

.mface{position:absolute;inset:0;border-radius:18px;overflow:hidden;color:#fff;
  backface-visibility:hidden;-webkit-backface-visibility:hidden;
  box-shadow:0 16px 36px -16px rgba(0,0,0,.5)}
.mface.back{transform:rotateY(180deg)}
/* One soft light source, top-left — what makes a gradient read as metal. */
.mface::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(120% 90% at 18% 0%,rgba(255,255,255,.28),transparent 55%),
             radial-gradient(80% 60% at 105% 110%,rgba(0,0,0,.25),transparent 60%)}

/* The three faces. Metallic pairs: dark edge, bright band, mid edge. */
/* THE THREE FACES, AND WHY THEY ARE NOT ALL METAL.
 *
 * Silver and Platinum are both grey metals — rendered literally, the top of the
 * ladder looked like the bottom of it, which is the one thing a tier card must
 * never do. So they are separated by TEMPERATURE and DEPTH rather than by hue:
 * Silver is a light, cool steel; Platinum is near-black graphite with a cold
 * sheen. Gold sits between them, warm. Held side by side the order now reads
 * light -> warm -> dark, which is legible even to someone who cannot tell the
 * two greys apart.
 *
 * Each is three stops, not two: a dark edge, a bright band off-centre, and a
 * mid shadow. That off-centre band is what the eye reads as a curved metal
 * surface catching a light — a flat two-stop gradient reads as paper. */

/* Cool, light steel — clearly the LIGHTEST of the three. */
/* Bright chrome. The lettering is dark and the WORDMARK ALONE is white - see
   the logo rule below for why that split is the only version of "white on
   silver" that actually works. */
.mcard.t-silver .mface{
  background:
    linear-gradient(135deg,#8e97a3 0%,#cfd6de 34%,#9aa3af 58%,#c3cad3 78%,#848d99 100%)}

/* Warm gold, deepened at the edges so it reads as metal rather than yellow. */
.mcard.t-gold .mface{
  background:
    linear-gradient(135deg,#7a5310 0%,#e9cd6b 33%,#c19a2c 55%,#f0da8c 76%,#8a611a 100%)}

/* Graphite. Nearly black, with a cold highlight — the darkest and the rarest. */
.mcard.t-platinum .mface{
  background:
    linear-gradient(135deg,#14171c 0%,#4a525d 30%,#8c96a4 47%,#39414c 66%,#0e1116 100%)}

/* A hairline inner edge lifts the card off the sheet — the detail that reads as
   "printed object" rather than "coloured rectangle". Inset, so it survives the
   rounded corners. */
.mface{box-shadow:0 16px 36px -16px rgba(0,0,0,.5),
                  inset 0 0 0 1px rgba(255,255,255,.16)}
.mcard.t-platinum .mface{box-shadow:0 18px 40px -16px rgba(0,0,0,.62),
                                    inset 0 0 0 1px rgba(255,255,255,.13)}

/* A GUILLOCHÉ WASH — the fine repeating line-work on banknotes and real cards.
   Almost invisible on its own and the whole difference between a gradient and
   something that looks manufactured. Two crossing sets at a shallow angle, kept
   under the highlight so it never competes with the name. */
.mface::before{
  content:"";position:absolute;inset:0;pointer-events:none;opacity:.16;
  background:
    repeating-linear-gradient(58deg, rgba(255,255,255,.5) 0 1px, transparent 1px 7px),
    repeating-linear-gradient(-58deg, rgba(0,0,0,.32) 0 1px, transparent 1px 9px)}

/* Gold carries the pattern less well — on a warm face it reads as dirt. */
.mcard.t-gold .mface::before{opacity:.1}

/* The name is embossed, not printed: a dark line under a light one is how
   pressed foil catches light on a real card. */
.mc-name{text-shadow:0 1px 0 rgba(255,255,255,.35), 0 -1px 0 rgba(0,0,0,.28),
                     0 3px 6px rgba(0,0,0,.4)}
.mcard.t-platinum .mc-name{color:#eef1f5}
/* WHITE ON BRIGHT CHROME, at the owner's direction and with the trade-off
   known: the raw contrast is 1.47:1, so the SHADOW is what separates the
   letters from the metal, not the colour difference.
   
   Three layers, each doing a different job - a tight dark edge that hugs every
   stroke so thin parts of the letterform do not dissolve, a short drop that
   gives the relief a direction, and a wide soft pool that darkens the metal
   generally behind the word. This is how foil lettering actually reads on a
   pale card: by casting a shadow, not by being darker than what is under it. */
.mcard.t-silver .mc-name{color:#fff;
  text-shadow:0 0 2px rgba(0,0,0,.55), 0 1px 2px rgba(0,0,0,.5), 0 3px 10px rgba(0,0,0,.35)}

/* GOLD TAKES DARK INK; SILVER AND PLATINUM TAKE WHITE. Measured, not guessed.
   Gold is a light metal - white on its mid tone scores 2.65, unreadable, and
   the name is the one thing on this card that has to be legible. Silver was a
   light metal too and carried dark ink for the same reason; it is now brushed
   gunmetal, so white measures 4.60 on it. Platinum has always been near-black.

   THE INK IS THE THEME'S BROWN, not a cool grey. It was #20262e, which is
   blue-grey and the one thing on this card that did not belong to the rest of
   the app.

   #5b3f2c — the brand brown — was the obvious choice and is NOT usable here:
   measured against the darkest stop of the silver gradient it scores 2.85,
   under the 3.0 that large text needs. #2a1d14 is the same family two steps
   deeper and scores 4.87 on silver, which is BETTER than the cool grey it
   replaces (4.54). The sub-labels drop their opacity trick and take a real
   value for the same reason: .9 of a light brown was quietly costing contrast
   the measurement did not show. */
.mcard.t-silver .mface{color:#fff}
/* The small labels get the same treatment, tightened: at this size a wide pool
   would smear, so the edge does almost all of the work. */
.mcard.t-silver .mc-tier,.mcard.t-silver .mc-since{color:#fff;opacity:1;
  text-shadow:0 0 2px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.45)}

/* THE WORDMARK IS WHITE; THE TEXT IS NOT. Two different things, and only one
   of them is safe to invert here.
   
   The base .mc-logo rule already whitens the PNG with brightness(0) invert(1)
   - platinum uses it. Silver was overriding it back to the printed brown; the
   override is simply gone, so the mark inherits the white.
   
   White TEXT on this metal measures 1.47:1 and is unreadable, which is why the
   name and labels keep their dark ink. A wordmark is not text being read, it
   is a shape being recognised, and white-on-silver is how foil is actually
   pressed into a card - so the drop-shadow does the separating rather than the
   contrast. drop-shadow and not text-shadow, because this is an <img>. */
.mcard.t-silver .mc-logo{
  filter:brightness(0) invert(1) drop-shadow(0 1px 2px rgba(0,0,0,.42));
  opacity:1}

/* Gold's ink was already a warm near-black; nudged onto the same brown as
   silver so the two cards are one family rather than two accidents. */
.mcard.t-gold .mface{color:#2a1d14}
.mcard.t-gold .mc-logo{filter:none;opacity:.92}
.mcard.t-gold .mc-tier,.mcard.t-gold .mc-since{color:#33251a;opacity:1}
.mcard.t-gold .mc-name{color:#241a12;
  text-shadow:0 1px 0 rgba(255,255,255,.45), 0 -1px 0 rgba(0,0,0,.2)}
/* The back's magnetic stripe and QR panel are unchanged by ink colour, but the
   code beside them follows the face so it stays readable. */
.mcard.t-gold .mc-code,.mcard.t-gold .mc-code-l,.mcard.t-gold .mc-back-hint,
.mcard.t-silver .mc-code,.mcard.t-silver .mc-code-l,.mcard.t-silver .mc-back-hint{
  color:inherit;text-shadow:none}

/* The chip reads as gold on every face — a real one is not repainted per tier. */
.mc-chip{background:linear-gradient(140deg,#f3dc9c,#c9a13f 55%,#e8c877);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.28), 0 1px 2px rgba(0,0,0,.25)}

/* FRONT */
.mface.front{padding:6.5% 7%}
.mc-logo{width:min(32%,132px);height:auto;display:block;
  filter:brightness(0) invert(1);opacity:.96}
.mc-chip{width:13.5%;aspect-ratio:1.4/1;border-radius:14%/22%;margin-top:7%;
  background:linear-gradient(140deg,#efd188,#c9a13f 60%,#e3bd63);position:relative;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.22)}
.mc-chip::before,.mc-chip::after{content:"";position:absolute;background:rgba(0,0,0,.25)}
.mc-chip::before{left:0;right:0;top:32%;height:1px;box-shadow:0 8px 0 rgba(0,0,0,.25)}
.mc-chip::after{top:0;bottom:0;left:46%;width:1px}
.mc-tier{position:absolute;top:8%;right:7%;font-size:clamp(10px,2.6vw,13px);font-weight:800;
  letter-spacing:.18em;text-shadow:0 1px 2px rgba(0,0,0,.35)}
.mc-name{position:absolute;left:7%;bottom:16%;right:7%;font-size:clamp(15px,4.6vw,22px);
  font-weight:800;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;
  text-shadow:0 1px 0 rgba(255,255,255,.28),0 2px 3px rgba(0,0,0,.4)}
.mc-since{position:absolute;left:7%;bottom:7.5%;font-size:clamp(8px,2.2vw,11px);
  letter-spacing:.14em;opacity:.85}

/* BACK */
.mc-stripe{height:17%;background:rgba(20,16,12,.62);margin-top:7.5%}
.mc-back-row{position:absolute;left:6%;right:6%;bottom:7%;top:33%;display:flex;gap:5%;
  align-items:center}
.mc-qrpanel{height:100%;aspect-ratio:1/1;background:#fff;border-radius:12px;flex:none;
  display:flex;align-items:center;justify-content:center;padding:6px;cursor:zoom-in;
  box-shadow:0 4px 14px rgba(0,0,0,.3)}
.mc-qrpanel svg{width:100%;height:100%;display:block}
.mc-back-info{flex:1;min-width:0;text-align:left}
.mc-code-l{font-size:9px;letter-spacing:.16em;opacity:.85;font-weight:700}
.mc-code{font-family:'Courier New',monospace;font-weight:700;
  font-size:clamp(11px,3vw,14px);word-break:break-all;
  text-shadow:0 1px 2px rgba(0,0,0,.35);margin-top:2px}
.mc-back-hint{font-size:clamp(9px,2.4vw,11px);line-height:1.45;opacity:.9;margin-top:8px}

/* THE ZOOM. Full-screen white with a large QR — this is the face a cashier
 * scans, so it is deliberately the plainest thing in the app: maximum contrast,
 * no card styling, nothing between the camera and the code. */
.qr-zoom{position:fixed;inset:0;z-index:9999;background:#fff;cursor:zoom-out;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;
  padding:24px}
.qr-zoom svg{width:min(78vw,58vh);height:auto;display:block}
.qz-code{font-family:'Courier New',monospace;font-weight:700;font-size:15px;color:#333}
.qz-hint{font-size:13px;color:#888}

/* The Croma Favorite mark. Sits inline beside a product name on every surface
   — till, community app, website, admin list — so it reads the same way
   wherever a customer meets it. Rounded to match the source SVG's own corner
   radius, and nudged up so it sits on the text baseline rather than under it.
   Sits BEFORE the name, so the gap belongs on its right. */
.fav-mark{display:inline-block;border-radius:4px;vertical-align:-2px;
  margin-right:7px;flex:none}
/* The favourite mark, bottom-right of the card, on its own row with the Hot or
   iced tag. IN FLOW, not absolutely positioned — pinned to the corner it would
   sooner or later land on the last line of a long description, and only on the
   products whose text happens to run that far. margin-left:auto is what holds
   it right whether or not the tag is there to sit beside it.
   No halo down here: it is on the cream card, not on a photograph, where the
   wordmark brown measures 11.7:1 and needs no help. */
.mt-foot{display:flex;align-items:center;gap:8px;margin-top:6px}
.fav-foot{margin-left:auto;margin-right:0;flex:none}


/* ---- instant top-up: the reload screen ----------------------------------
   Modelled on the Starbucks reload screen: amount large and quiet, a
   segmented bar to choose it, the payment methods as a list, one button.
   The CTA on the page is the only thing that is loud. */
.tu-cta{display:flex;align-items:center;gap:12px;width:100%;text-align:left;cursor:pointer;font-family:inherit;
  background:linear-gradient(135deg,var(--brand),var(--brand-2));color:#faf6ee;border:none;border-radius:16px;
  padding:14px 16px;box-shadow:0 8px 22px rgba(58,43,32,.18);transition:transform .12s}
.tu-cta:active{transform:scale(.985)}
.tu-cta-ic{width:38px;height:38px;border-radius:12px;background:rgba(255,255,255,.14);display:flex;align-items:center;justify-content:center;font-size:18px;flex:none}
.tu-cta-txt{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}
.tu-cta-t{font-weight:800;font-size:15px;letter-spacing:-.2px}
.tu-cta-s{font-size:11px;opacity:.78;letter-spacing:.2px}
.tu-cta-go{font-size:22px;opacity:.7;line-height:1}

.tu-hd{font-size:12px;font-weight:700;letter-spacing:1.6px;text-transform:uppercase;color:var(--brand);margin:0 0 6px}
.tu-big{font-size:44px;font-weight:300;letter-spacing:-1.5px;line-height:1.05;text-align:center;color:var(--text);padding:18px 0 22px}
.tu-sec{font-size:13px;font-weight:700;margin:14px 0 8px}

/* Segmented, full width, edge to edge. The selected segment is filled; the
   rest are plain text - the way a tab bar reads, not a row of pills. */
.tu-seg{display:flex;background:var(--surface2);border:1px solid var(--line);border-radius:12px;overflow:hidden}
.tu-chip{flex:1;border:none;background:transparent;color:var(--text);font-family:inherit;
  padding:12px 4px;font-size:14px;font-weight:600;cursor:pointer;border-radius:0}
.tu-chip.on{background:var(--brand);color:#faf6ee;font-weight:800}
.tu-chip:active{opacity:.85}
.tu-other{margin:10px 0 4px;font-size:15px}
.tu-range{margin:0 2px 4px}

.tu-grid{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:12px;overflow:hidden}
.tu-m{display:flex;align-items:center;gap:12px;text-align:left;cursor:pointer;font-family:inherit;color:var(--text);
  background:var(--surface);border:none;border-bottom:1px solid var(--line);padding:11px 12px;width:100%;border-radius:0}
.tu-m:last-child{border-bottom:none}
.tu-m.on{background:var(--surface2)}
.tu-m:disabled{cursor:default;opacity:.6}
.tu-logo{width:32px;height:32px;border-radius:9px;color:#fff;font-weight:800;font-size:12.5px;flex:none;
  display:flex;align-items:center;justify-content:center}
.tu-m-txt{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}
.tu-name{font-weight:700;font-size:13.5px}
.tu-fee{font-size:11px;color:var(--muted)}
.tu-pay{font-weight:700;font-size:13px;color:var(--muted);white-space:nowrap}
.tu-m.on .tu-pay{color:var(--brand);font-weight:800}
.tu-radio{width:18px;height:18px;border-radius:50%;border:2px solid var(--line);flex:none;position:relative}
.tu-m.on .tu-radio{border-color:var(--brand)}
.tu-m.on .tu-radio::after{content:'';position:absolute;inset:3px;border-radius:50%;background:var(--brand)}

.tu-paybtn{display:block;width:100%;margin-top:16px;padding:16px;border:none;border-radius:12px;cursor:pointer;font-family:inherit;
  background:var(--brand);color:#faf6ee;font-size:14px;font-weight:800;letter-spacing:1.4px;text-transform:uppercase}
.tu-paybtn:active{background:var(--brand-2)}
.tu-paybtn:disabled{opacity:.6;cursor:default}
.tu-foot{font-size:11.5px;color:var(--muted);margin-top:10px;line-height:1.45;text-align:center}
.tu-note{padding:14px 16px}
.tu-note-t{font-weight:700;font-size:14px;margin-bottom:4px}

/* ---- one ledger line, in full ------------------------------------------- */
.cd-amt{font-size:34px;font-weight:300;letter-spacing:-1.2px;color:var(--red);font-variant-numeric:tabular-nums}
.cd-amt.up{color:var(--green)}
.cd-sub{font-size:13.5px;color:var(--muted);margin:2px 0 14px}
.cd-list{border-top:1px solid var(--line)}
.cd-row{display:flex;gap:12px;align-items:baseline;padding:9px 0;border-bottom:1px solid var(--line)}
.cd-k{font-size:12px;color:var(--muted);width:96px;flex:none}
.cd-v{font-size:13.5px;flex:1;min-width:0;word-break:break-word}
.cd-ref{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;user-select:all}

/* ---- store credits: the card, the two actions, the grouped ledger ----------
   Numerals throughout use the light face from the reload screen: weight 300,
   tight tracking, tabular figures so columns of amounts line up. */
.cr-card{position:relative;overflow:hidden;border-radius:20px;padding:18px 20px 16px;margin-bottom:12px;color:#faf6ee;
  background:linear-gradient(135deg,var(--brand) 0%,var(--brand-2) 70%,#8a6a4e 100%);box-shadow:0 12px 30px rgba(58,43,32,.22)}
.cr-card::before{content:'';position:absolute;right:-60px;top:-70px;width:200px;height:200px;border-radius:50%;background:rgba(255,255,255,.06)}
.cr-card::after{content:'';position:absolute;left:-40px;bottom:-90px;width:180px;height:180px;border-radius:50%;background:rgba(0,0,0,.08)}
.cr-card-top,.cr-card-bot{display:flex;justify-content:space-between;position:relative;font-size:10.5px;font-weight:700;letter-spacing:1.6px;text-transform:uppercase;opacity:.82}
.cr-card-bot{font-size:11.5px;letter-spacing:.3px;text-transform:none;font-weight:600;opacity:.9}
.cr-card .wallet-bal{margin:18px 0 16px;color:#fff}

.cr-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:0 0 10px}
.cr-tile,.cr-slot>.tu-cta{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-height:104px;
  padding:14px;border-radius:16px;text-align:left;cursor:pointer;font-family:inherit;width:100%}
.cr-tile{background:var(--surface);border:1px solid var(--line);color:var(--text);box-shadow:0 4px 14px rgba(58,43,32,.06)}
.cr-tile:active{transform:scale(.985)}
.cr-tile-ic{width:36px;height:36px;border-radius:11px;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:18px}
.cr-tile-t{font-weight:800;font-size:14px;letter-spacing:-.2px;margin-top:2px}
.cr-tile-s{font-size:11px;color:var(--muted)}
.cr-slot{display:flex}
.cr-slot>.tu-cta{box-shadow:0 8px 22px rgba(58,43,32,.18)}
.cr-slot .tu-cta-ic{margin-bottom:2px}
.cr-slot .tu-cta-txt{gap:4px}
.cr-slot .tu-cta-t{font-size:14px}
.cr-slot .tu-cta-s{font-size:11px}
.cr-slot .tu-cta-go{display:none}
.cr-slot .tu-note{width:100%;min-height:104px}
.cr-note{font-size:11.5px;color:var(--muted);margin:2px 2px 16px;line-height:1.45}

#crList{background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:2px 14px}
#crList .led{background:none;border:none;border-bottom:1px solid var(--line);border-radius:0;box-shadow:none;margin:0;padding:12px 0}
#crList .led:last-child{border-bottom:none}
#crList .led-day{padding:12px 0 2px;font-size:10.5px;letter-spacing:1.2px;color:var(--muted)}
#crList .empty,#crList .card{border:none;box-shadow:none;background:none}

/* ---- pay-with-credits: QR first, digits as the fallback ------------------ */
.pc-qr{display:flex;justify-content:center;padding:4px 0 2px}
.pc-qr svg{width:190px;height:190px;background:#fff;border-radius:12px;padding:8px;box-sizing:content-box}
.pc-or{font-size:10.5px;font-weight:700;letter-spacing:1.4px;text-transform:uppercase;color:var(--muted);margin:12px 0 4px}
.pc-code{font-size:36px;font-weight:300;letter-spacing:10px;color:var(--brand);font-variant-numeric:tabular-nums;
  text-indent:10px}

/* ---- FIGURES ---------------------------------------------------------------
   Every number in the app now uses the same treatment the balance does:
   tabular so columns of amounts line up and a ticking countdown does not
   shuffle its own width, and a lighter face at display sizes - a large number
   at weight 800 reads as shouting, at 300 it reads as a figure. */
.wallet-bal,.cd-amt,.tu-amt,.tu-big,.pc-code,.stat .v,.big-num{font-variant-numeric:tabular-nums}
.led-amt,.tu-pay,.tu-fee,.money,.price{font-variant-numeric:tabular-nums}

/* ---- a tap target that answers ---------------------------------------------
   .tap existed on cards only. Rows and tiles across the app were tappable with
   no feedback at all, which on a phone reads as a dead control. */
.list .item:active{background:var(--surface2)}
.tap,.tapface{cursor:pointer}
.tap:active{transform:scale(.988)}

/* .body deliberately NOT touched here: its padding-bottom reserves room for
   the fixed tab bar (--bar-h). Overriding it hides content behind the bar on
   every tabbed screen - which is what the first version of this did. */
.chip,.badge{letter-spacing:.2px}

/* ---- home carousel ---------------------------------------------------------
   A scroll-snap strip: no library, and it stays usable with a trackpad, a
   keyboard and a screen reader. The negative margins let the cards run to the
   screen edges the way the Starbucks strip does, while the padding keeps the
   first and last card off the glass. */
.cz-strip{display:flex;gap:12px;overflow-x:auto;scroll-snap-type:x mandatory;
  -webkit-overflow-scrolling:touch;scrollbar-width:none;
  margin:0 -16px;padding:2px 16px 4px}
.cz-strip::-webkit-scrollbar{display:none}
.cz-card{scroll-snap-align:start;flex:0 0 84%;max-width:340px;cursor:pointer;
  background:var(--surface);border:1px solid var(--line);border-radius:18px;overflow:hidden;
  box-shadow:var(--shadow);transition:transform .12s,box-shadow .12s}
.cz-card:active{transform:scale(.985)}
.cz-card:hover{box-shadow:var(--shadow-lift)}
.cz-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.cz-img{position:relative;height:132px;background:linear-gradient(135deg,var(--accent),var(--brand));
  display:flex;align-items:center;justify-content:center}
.cz-ph{font-size:34px;opacity:.55}
.cz-tag{position:absolute;left:10px;bottom:10px;background:rgba(255,255,255,.93);color:var(--brand);
  font-size:11px;font-weight:800;letter-spacing:.3px;padding:4px 9px;border-radius:20px;
  max-width:calc(100% - 20px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cz-tag.ev{text-transform:uppercase;letter-spacing:.6px}
.cz-body{padding:12px 14px 14px}
.cz-title{font-weight:700;font-size:15.5px;letter-spacing:-.2px;line-height:1.3}
.cz-sub{font-size:12.5px;color:var(--muted);margin-top:4px;line-height:1.4;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.cz-dots{display:flex;gap:6px;justify-content:center;margin:10px 0 14px}
.cz-dot{width:6px;height:6px;border-radius:50%;background:var(--line);transition:.15s}
.cz-dot.on{background:var(--brand);width:18px;border-radius:3px}
.cz-sheet-img{width:100%;border-radius:14px;margin-bottom:14px;display:block}

/* ---- top-up receipt --------------------------------------------------------
   The acknowledgement for money that has actually left someone's wallet:
   a mark, a sentence, and the figures laid out so the pair that matters —
   what it was, what it is — sit together at the bottom. */
.rc-top{text-align:center;margin-bottom:18px}
.rc-check{width:56px;height:56px;border-radius:50%;background:var(--green,#2e7d5b);color:#fff;
  font-size:28px;line-height:56px;margin:0 auto 14px;font-weight:700}
.rc-head{font-size:17px;font-weight:700;line-height:1.35;letter-spacing:-.2px}
.rc-card{background:var(--surface2);border-radius:16px;padding:16px 16px 14px}
.rc-label{font-size:10.5px;font-weight:700;letter-spacing:1.4px;text-transform:uppercase;
  color:var(--muted);text-align:center}
.rc-amt{font-size:32px;font-weight:300;letter-spacing:-1.2px;text-align:center;margin:4px 0 16px;
  font-variant-numeric:tabular-nums}
.rc-row{display:flex;justify-content:space-between;gap:14px;align-items:baseline;padding:6px 0;font-size:13px}
.rc-k{color:var(--muted);flex:0 0 auto}
.rc-v{text-align:right;font-variant-numeric:tabular-nums}
.rc-v.strong{font-weight:700}
.rc-ref{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;word-break:break-all}
.rc-hr{height:1px;background:var(--line);margin:10px 0 4px}

/* ---- opening day ----------------------------------------------------------
   The cafe's own room behind an espresso scrim, with the same drifting glow
   and shine as the website's bar. A flat brown card sat in a brown app and
   disappeared; this one is meant to be the first thing the eye lands on, and
   then to remove itself a week after opening - see openingBanner() in
   views/home.js. Contrast comes from the scrim, so it stays legible before the
   photo loads and with motion turned off. */
.open-banner{
  position:relative;overflow:hidden;isolation:isolate;
  border-radius:18px;padding:16px 18px;margin-bottom:12px;color:#fbf8f2;
  background:
    linear-gradient(115deg,rgba(24,16,11,.93),rgba(24,16,11,.62) 55%,rgba(24,16,11,.88)),
    url('/assets/hero.jpg') center 60% / cover no-repeat,
    #24180f;
  box-shadow:0 10px 28px rgba(58,43,32,.28);
}
.open-banner::before{
  content:'';position:absolute;inset:-60% -25%;z-index:-1;pointer-events:none;
  background:
    radial-gradient(40% 120% at 25% 50%,rgba(245,198,116,.50),transparent 62%),
    radial-gradient(36% 120% at 75% 50%,rgba(181,137,90,.45),transparent 62%);
  animation:obGlow 11s ease-in-out infinite alternate;
}
.open-banner::after{
  content:'';position:absolute;top:-60%;bottom:-60%;width:26%;z-index:-1;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.28),transparent);
  animation:obShine 7s cubic-bezier(.5,0,.5,1) infinite;
}
@keyframes obGlow{ from{transform:translateX(-9%)} to{transform:translateX(9%)} }
@keyframes obShine{ 0%{transform:translateX(-170%) skewX(-16deg)} 55%,100%{transform:translateX(520%) skewX(-16deg)} }

.open-banner .ob-t{font-weight:800;font-size:16px;letter-spacing:-.2px;display:flex;align-items:center;gap:9px}
.open-banner .ob-t b{font-weight:800;color:#ffd489}
.open-banner.live .ob-t b{color:#b7e8b0}
.open-banner .ob-s{font-size:12.5px;opacity:.9;margin-top:4px;line-height:1.45}
.open-banner .ob-dot{
  width:9px;height:9px;border-radius:50%;flex:none;background:#f5c674;
  box-shadow:0 0 0 4px rgba(245,198,116,.22),0 0 12px rgba(245,198,116,.9);
  animation:obPulse 2.2s ease-in-out infinite;
}
.open-banner.live .ob-dot{background:#8ed08a;box-shadow:0 0 0 4px rgba(142,208,138,.22),0 0 12px rgba(142,208,138,.9)}
@keyframes obPulse{ 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.55;transform:scale(.82)} }

@media (prefers-reduced-motion:reduce){
  .open-banner::before,.open-banner::after,.open-banner .ob-dot{animation:none}
  .open-banner::after{opacity:0}
}
