/* Upkeep — deliberately plain.
 *
 * This is an app somebody opens twice a month to answer one question: is
 * anything due? So: one column, large type, few colours, and the answer to that
 * question at the top of the first screen before anything else is drawn.
 */

/* Light, warm and legible in daylight, which is where a phone actually gets
   used. The old scheme was a dark developer dashboard: fine on a desk, gloomy
   in a hand. Colour now carries meaning -- green is fine, amber is soon, red is
   overdue -- and each kind of thing has its own tint so a list scans at arm's
   length. */
:root {
  --bg:        #f6f7fb;
  --surface:   #ffffff;
  --surface-2: #eef1f7;
  --border:    #dfe4ee;
  --text:      #17202e;
  --text-2:    #47536a;
  --muted:     #7b869b;
  --accent:    #3f5efb;
  --accent-2:  #eaeeff;
  --good:      #12996b;
  --good-2:    #e3f6ee;
  --warn:      #c47a08;
  --warn-2:    #fdf1dc;
  --bad:       #d6453c;
  --bad-2:     #fdeceb;
  --home:      #f0a202;
  --auto:      #2ba3a3;
  --tabs-h:    62px;
  --shadow:    0 1px 2px rgba(23,32,46,.06), 0 6px 18px rgba(23,32,46,.06);
  color-scheme: light;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* `hidden` has to actually hide.
 *
 * The attribute only works through a user-agent rule of `display: none`, which
 * any author rule outranks. Every panel here sets its own display -- grid for
 * the gate, flex for the shell and the sheet backdrop -- so all three stayed on
 * screen while marked hidden, and the sheet's dark scrim sat over the whole app
 * looking like everything had been greyed out. */
[hidden] { display: none !important; }
html, body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
  overscroll-behavior-y: none;
}
h3 { font-size: 14px; margin: 0; font-weight: 600; }
small { color: var(--muted); }

/* ------------------------------------------------------------------- gate */
.gate { position: fixed; inset: 0; display: grid; place-items: center; padding: 26px;
        background: var(--bg); z-index: 50; }
.gate-inner { width: 100%; max-width: 360px; }
.brand { font-size: 27px; font-weight: 700; letter-spacing: -.02em; text-align: center; }
.tag { color: var(--muted); text-align: center; font-size: 14px; margin: 6px 0 26px; }
.gate-seg { margin-bottom: 18px; }
.gate input, .card input, .card select {
  width: 100%; background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text); border-radius: 12px; padding: 14px; font: inherit; font-size: 16px;
  margin-bottom: 10px;
}
.msg { font-size: 13px; margin-top: 12px; min-height: 18px; color: var(--bad); line-height: 1.5; }
.msg.ok { color: var(--good); }

/* ------------------------------------------------------------------- shell */
.app { display: flex; flex-direction: column; min-height: 100dvh; }
.top { display: flex; align-items: center; gap: 12px;
       padding: calc(env(safe-area-inset-top) + 16px) 16px 8px; }
.top > div { flex: 1; min-width: 0; }
.hi { font-size: 19px; font-weight: 650; }
.sub { font-size: 12.5px; color: var(--muted); }
.icon { width: 38px; height: 38px; border-radius: 50%; flex: none; cursor: pointer;
        background: var(--surface-2); border: 1px solid var(--border); color: var(--text-2);
        font-size: 17px; }

.views { flex: 1; padding: 4px 16px calc(var(--tabs-h) + 28px); }
.view { display: none; }
.view.on { display: block; }
.head { display: flex; align-items: baseline; justify-content: space-between;
        margin: 24px 2px 10px; }
.link { background: none; border: 0; color: var(--accent); font: inherit; font-size: 13.5px;
        cursor: pointer; padding: 0; }
.back { margin: 8px 0 4px; }

/* -------------------------------------------------------------------- bits */
.card { background: var(--surface); border: 1px solid var(--border);
        border-radius: 16px; padding: 16px; box-shadow: var(--shadow); }
.list { display: flex; flex-direction: column; gap: 9px; }
.row { display: flex; align-items: center; gap: 13px; cursor: pointer;
       background: var(--surface); border: 1px solid var(--border);
       border-radius: 14px; padding: 14px; box-shadow: var(--shadow); text-align: left; width: 100%;
       font: inherit; color: var(--text); }
.row:active { transform: scale(.99); }
.row .ico { width: 38px; height: 38px; border-radius: 11px; flex: none; font-size: 17px;
            display: grid; place-items: center; background: var(--surface-2); }
.row .body { flex: 1; min-width: 0; }
.row .t { font-size: 15px; font-weight: 570; }
.row .s { font-size: 12.5px; color: var(--muted); margin-top: 1px; }
.row .r { text-align: right; flex: none; font-size: 12.5px; }

.pill { display: inline-block; font-size: 11px; padding: 3px 9px; border-radius: 999px;
        border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
.pill.due  { color: var(--warn); border-color: rgba(224,163,58,.45); }
.pill.over { color: var(--bad);  border-color: rgba(224,92,82,.45); }
.pill.ok   { color: var(--good); border-color: rgba(63,180,127,.4); }

/* The life bar: how far through its expected life a thing is, against the
   range rather than a single number, because the source gives a range. */
.life { margin-top: 9px; }
.life-track { height: 9px; border-radius: 5px; background: var(--surface-2);
              position: relative; overflow: hidden; }
/* The fill is how old the thing is. The line is the top of its typical range,
   so a fill running past the line has outlived what it should have. */
.life-mark { position: absolute; top: -2px; bottom: -2px; width: 2px; z-index: 5;
             background: var(--text-2); border-radius: 1px; }
.life-fill { height: 100%; border-radius: 5px; background: var(--good);
             position: relative; z-index: 2; opacity: .9; }
.life-fill.mid  { background: var(--warn); }
.life-fill.late { background: var(--bad); }

/* The typical range, bracketed underneath with its two figures.
   It was a tint laid over the track before, which the fill covered for exactly
   the items where it mattered -- and where it did show, green over orange came
   out olive and looked like a third state nobody had defined. Underneath it is
   legible either way, and the numbers save the reader measuring pixels to work
   out where "6" fell. */
.life-axis { position: relative; height: 17px; margin-top: 4px; }
.life-axis .rng { position: absolute; top: 0; height: 3px; border-radius: 2px;
                  background: rgba(18,153,107,.55); }
.life-axis span { position: absolute; top: 5px; transform: translateX(-50%);
                  font-size: 10.5px; font-weight: 620; color: var(--text-2);
                  white-space: nowrap; }
.life-cap { display: flex; justify-content: space-between; gap: 10px; font-size: 11.5px;
            color: var(--muted); margin-top: 6px; }
.life-cap b { color: var(--text-2); font-weight: 620; }
.life-cap .over { color: var(--bad); font-weight: 570; }

.banner { border-radius: 14px; padding: 15px; margin-top: 6px; font-size: 14px;
          border: 1px solid; }
.banner.over { background: rgba(224,92,82,.10); border-color: rgba(224,92,82,.35); }
.banner.soon { background: rgba(224,163,58,.10); border-color: rgba(224,163,58,.35); }
.banner.calm { background: rgba(63,180,127,.08); border-color: rgba(63,180,127,.3); }

.note { font-size: 12px; color: var(--muted); line-height: 1.6; margin-top: 12px; }
.note.center { text-align: center; }
.kv { display: flex; justify-content: space-between; gap: 12px; padding: 9px 0;
      border-bottom: 1px solid var(--border); font-size: 14px; }
.kv:last-child { border-bottom: 0; }
.kv span { color: var(--muted); }
.skel { height: 66px; border-radius: 14px; background: var(--surface);
        animation: pulse 1.2s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: .55; } }

.btn { border-radius: 12px; padding: 14px 18px; font: inherit; font-size: 15px;
       font-weight: 560; cursor: pointer; border: 1px solid var(--border);
       background: var(--surface-2); color: var(--text); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.ghost { background: transparent; color: var(--text-2); }
/* `block` has to actually be block. A <label class="btn block"> — used where
   the button opens a file picker — is inline by default, so width:100% measured
   against nothing and the control came out shrink-wrapped. */
.btn.block { display: block; width: 100%; margin-top: 12px; }
.btn.sm { padding: 9px 13px; font-size: 13px; }
.btn:active { transform: scale(.99); }
.btn[disabled] { opacity: .5; }

.seg { display: flex; gap: 4px; background: var(--surface-2); border-radius: 11px; padding: 3px; }
.seg button { flex: 1; padding: 9px 4px; border: 0; border-radius: 9px; cursor: pointer;
              background: transparent; color: var(--muted); font: inherit; font-size: 13.5px; }
.seg button.on { background: var(--surface); color: var(--text); font-weight: 600; }

/* Block, not a column flexbox. As a flexbox the label's own text and the
   "optional" hint next to it are two flex children and stack vertically, so
   every field read as three lines instead of two. */
.field { display: block; font-size: 13px; color: var(--muted); margin-bottom: 12px; }
.field > input, .field > select, .field > textarea { display: block; margin-top: 6px; }
.switch { display: flex; align-items: center; gap: 14px; padding: 4px 0 14px; }
.switch span { flex: 1; display: flex; flex-direction: column; gap: 2px; font-size: 14px;
               color: var(--text); }
.switch input { appearance: none; width: 50px; height: 30px; flex: none; cursor: pointer;
                border-radius: 999px; background: var(--surface-2);
                border: 1px solid var(--border); position: relative; }
.switch input::after { content: ''; position: absolute; top: 3px; left: 3px; width: 22px;
                       height: 22px; border-radius: 50%; background: var(--muted);
                       transition: transform .18s, background .18s; }
.switch input:checked { background: rgba(61,139,253,.3); border-color: var(--accent); }
.switch input:checked::after { transform: translateX(20px); background: var(--accent); }

/* ------------------------------------------------------------------- tabs */
.tabs { position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
        display: grid; grid-template-columns: repeat(4, 1fr);
        background: rgba(16,20,24,.94); backdrop-filter: blur(12px);
        border-top: 1px solid var(--border); padding-bottom: env(safe-area-inset-bottom); }
.tab { background: none; border: 0; cursor: pointer; color: var(--muted); font: inherit;
       display: flex; flex-direction: column; align-items: center; gap: 3px;
       padding: 9px 0 7px; font-size: 10.5px; height: var(--tabs-h); }
.tab i { font-style: normal; font-size: 18px; }
.tab.on { color: var(--accent); }

.toast { position: fixed; left: 50%; bottom: calc(var(--tabs-h) + 20px); z-index: 80;
         transform: translate(-50%, 16px); background: var(--surface-2);
         border: 1px solid var(--border); border-radius: 11px; padding: 12px 18px;
         font-size: 13.5px; opacity: 0; pointer-events: none;
         transition: opacity .2s, transform .2s; max-width: calc(100vw - 36px); }
.toast.on { opacity: 1; transform: translate(-50%, 0); }

@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

/* ------------------------------------------------- full-screen flows */
/* Anything that asks a question takes the whole screen. A phone has one
   thing's worth of room, and a form squeezed into a sheet at the bottom is
   a desktop dialog wearing a disguise. */
.screen { position: fixed; inset: 0; z-index: 70; background: var(--bg);
          display: flex; flex-direction: column; animation: slidein .18s ease; }
@keyframes slidein { from { transform: translateX(14px); opacity: .6; } }
.screen-top { display: flex; align-items: center; gap: 10px;
              padding: calc(env(safe-area-inset-top) + 14px) 14px 10px;
              border-bottom: 1px solid var(--border); }
.screen-title { flex: 1; font-size: 16px; font-weight: 620; text-align: center;
                padding-right: 38px; }
.screen-top .link { padding: 0 6px; }
.screen-body { flex: 1; overflow-y: auto; padding: 18px 16px 10px; }
.screen-foot { padding: 12px 16px calc(env(safe-area-inset-bottom) + 16px);
               border-top: 1px solid var(--border); background: var(--bg); }
.screen-body h2 { font-size: 22px; margin: 0 0 6px; letter-spacing: -.01em; }
.screen-body .lede { color: var(--muted); font-size: 14px; margin: 0 0 20px; }
/* Text fields fill the width; a checkbox is not a text field. Without the
   exclusion a toggle inside a screen stretches to the full width and shoves its
   own label into a one-word-per-line column. */
.screen-body input:not([type=checkbox]):not([type=file]), .screen-body select {
  width: 100%; background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text); border-radius: 12px; padding: 15px; font: inherit;
  font-size: 16px; margin-bottom: 11px;
}
.screen-body label.field { margin-bottom: 16px; }
.screen-body label.field input, .screen-body label.field select { margin-bottom: 0; }

/* Two big targets rather than a dropdown. This is the first thing a new
   account sees, so it should look like a decision, not a form field. */
.choices { display: grid; gap: 12px; margin-top: 8px; }
.choice { display: flex; align-items: center; gap: 15px; cursor: pointer;
          background: var(--surface); border: 1px solid var(--border);
          border-radius: 18px; padding: 22px 20px; text-align: left; width: 100%;
          font: inherit; color: var(--text); }
.choice:active { transform: scale(.99); }
.choice .big { width: 52px; height: 52px; border-radius: 15px; flex: none;
               display: grid; place-items: center; font-size: 25px;
               background: var(--surface-2); }
.choice .t { font-size: 17px; font-weight: 620; }
.choice .s { font-size: 13px; color: var(--muted); margin-top: 2px; }

/* One question at a time, with a sense of how far through you are. */
.steps { display: flex; gap: 5px; margin-bottom: 20px; }
.steps i { height: 3px; flex: 1; border-radius: 2px; background: var(--surface-2); }
.steps i.on { background: var(--accent); }

/* Pick-lists: options as rows, and the chosen one obviously chosen. */
.opts { display: flex; flex-direction: column; gap: 8px; }
.opt { display: flex; align-items: center; gap: 12px; cursor: pointer; width: 100%;
       background: var(--surface); border: 1px solid var(--border); border-radius: 13px;
       padding: 15px; text-align: left; font: inherit; color: var(--text); }
.opt.on { border-color: var(--accent); background: rgba(61,139,253,.10); }
.opt .t { font-size: 15px; font-weight: 550; }
.opt .s { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.opt .body { flex: 1; min-width: 0; }
.opt .tick { color: var(--accent); font-size: 17px; }
.grp { font-size: 11px; color: var(--muted); text-transform: uppercase;
       letter-spacing: .06em; margin: 20px 2px 8px; }
.grp:first-child { margin-top: 4px; }

/* A thing's history, oldest at the bottom. */
.tl { position: relative; padding-left: 22px; margin-top: 6px; }
.tl::before { content: ''; position: absolute; left: 5px; top: 6px; bottom: 6px;
              width: 2px; background: var(--border); }
.tl-item { position: relative; padding: 11px 0; }
.tl-item::before { content: ''; position: absolute; left: -21px; top: 17px; width: 10px;
                   height: 10px; border-radius: 50%; background: var(--surface-2);
                   border: 2px solid var(--border); }
.tl-item.install::before { background: var(--accent); border-color: var(--accent); }
.tl-item.replace::before { background: var(--warn); border-color: var(--warn); }
.tl-when { font-size: 12px; color: var(--muted); }
.tl-what { font-size: 14.5px; font-weight: 550; margin-top: 1px; }

/* ------------------------------------------------- colour and emoji ---- */
/* The tab bar and header sit on the page colour rather than a dark slab. */
.tabs { background: rgba(255,255,255,.94) !important; }
.top { background: transparent; }

/* An emoji big enough to be the thing you recognise, not decoration. */
.emo { font-size: 26px; line-height: 1; }
.row .ico.home { background: #fdf0d5; }
.row .ico.auto { background: #ddf3f3; }
.choice .big.home { background: #fdf0d5; }
.choice .big.auto { background: #ddf3f3; }

.pill.due  { background: var(--warn-2); }
.pill.over { background: var(--bad-2); }
.pill.ok   { background: var(--good-2); }
.pill      { background: var(--surface-2); }

.banner.over { background: var(--bad-2);  border-color: rgba(214,69,60,.3);  color: #8f2b25; }
.banner.soon { background: var(--warn-2); border-color: rgba(196,122,8,.3);  color: #7a4c04; }
.banner.calm { background: var(--good-2); border-color: rgba(18,153,107,.28); color: #0b6b4b; }

/* Costs split two ways: what you bought, and what it costs to keep. */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.money-card { border-radius: 16px; padding: 16px; border: 1px solid var(--border);
              box-shadow: var(--shadow); }
.money-card.equip { background: linear-gradient(155deg, #dfe4ff, #f4f6ff);
                    border-color: #c9d2ff; }
.money-card.serv  { background: linear-gradient(155deg, #d3f2e5, #f0fbf6);
                    border-color: #b7e6d2; }
.money-card.equip .amt { color: #2a3ec9; }
.money-card.serv  .amt { color: #0d7a56; }
.money-card .lbl { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em;
                   color: var(--muted); }
.money-card .amt { font-size: 25px; font-weight: 690; margin-top: 4px; letter-spacing: -.02em; }
.money-card .sub { font-size: 11.5px; color: var(--muted); margin-top: 2px; }

/* Photo strip. Square thumbnails, tap to enlarge. */
.shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 10px; }
.shot { position: relative; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
        border: 1px solid var(--border); background: var(--surface-2); cursor: pointer; }
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shot.add { display: grid; place-items: center; font-size: 24px; color: var(--muted);
            border-style: dashed; }
.lightbox { position: fixed; inset: 0; z-index: 90; background: rgba(10,14,22,.92);
            display: grid; place-items: center; padding: 20px; }
.lightbox img { max-width: 100%; max-height: 78dvh; border-radius: 12px; }
.lightbox .bar { position: absolute; bottom: calc(env(safe-area-inset-bottom) + 20px);
                 display: flex; gap: 10px; }

/* Emoji picker: a grid of taps, nothing more. */
.emoji-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; margin-top: 10px; }
.emoji-grid button { aspect-ratio: 1; font-size: 24px; border-radius: 12px; cursor: pointer;
                     background: var(--surface); border: 1px solid var(--border); }
.emoji-grid button.on { border-color: var(--accent); background: var(--accent-2); }

/* A year with nothing on one side reads "$0 + $504", which is two numbers where
   there is one fact. Show the side that happened. */
.yr { display: flex; align-items: baseline; justify-content: space-between;
      padding: 11px 0; border-bottom: 1px solid var(--border); }
.yr:last-of-type { border-bottom: 0; }
.yr .y { color: var(--muted); font-size: 15px; }
.yr .n { display: flex; gap: 12px; align-items: baseline; }
.yr .n b { font-size: 15px; }
.yr .eq b { color: #2a3ec9; }
.yr .sv b { color: #0d7a56; }
.yr .n small { color: var(--muted); font-size: 11px; margin-left: 3px; }
/* ------------------------------------------------------- title and tabs --- */
.hi-btn { flex: 1; min-width: 0; text-align: left; background: none; border: 0;
          padding: 0; cursor: pointer; color: inherit; font: inherit; }
.hi-btn:active { opacity: .6; }
.brand .lite { font-weight: 400; color: var(--muted); }
/* Five tabs at 390px is 78px each, which still fits a word and an icon. */
.tabs { grid-template-columns: repeat(5, 1fr); }
.tab span { font-size: 9.8px; }

/* --------------------------------------------------------------- chart --- */
.chart { display: grid; grid-template-columns: repeat(12, 1fr); gap: 5px;
         align-items: end; height: 132px; margin: 14px 0 4px; }
.col { display: flex; flex-direction: column; height: 100%; }
.col-wrap { flex: 1; display: flex; align-items: flex-end; }
.col-bar { width: 100%; border-radius: 5px 5px 2px 2px; min-height: 0;
           background: var(--good); display: flex; flex-direction: column;
           justify-content: flex-start; overflow: hidden; transition: height .18s; }
.col-bar.equipment { background: #4a63e8; }
.col-bar.servicing { background: #16a173; }
/* In the combined view the bar is stacked: equipment on top of upkeep, so the
   shape of a year with one big purchase in it is obvious at a glance. */
.col-bar.all { background: #16a173; }
.col-eq { width: 100%; background: #4a63e8; }
.col-lbl { text-align: center; font-size: 10px; color: var(--muted); padding-top: 5px; }
.chart-seg { margin-bottom: 2px; }
.chart-foot { display: flex; justify-content: space-between; align-items: baseline;
              font-size: 12.5px; color: var(--muted); padding-top: 10px;
              border-top: 1px solid var(--border); margin-top: 6px; }
.chart-foot b { color: var(--text); font-size: 15px; }
.yr-pick { background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
           border-radius: 9px; padding: 5px 9px; font: inherit; font-size: 12.5px; }

/* What the next year is likely to cost. A button, because it goes somewhere. */
.money-card.soon { display: block; width: 100%; text-align: left; margin-top: 11px;
                   cursor: pointer; font: inherit; color: var(--text);
                   background: linear-gradient(155deg, #fdeed2, #fffaf1);
                   border-color: #f2ddb4; }
.money-card.soon .amt { color: #9a5f04; }

/* ------------------------------------------------- timeline, now tappable - */
.tl-item { display: block; width: 100%; text-align: left; font: inherit; color: var(--text);
           cursor: pointer; position: relative; background: none; border: 0;
           padding: 11px 0; }
.tl-item:active { opacity: .6; }
.tl-item .tl-go { position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
                  color: var(--muted); font-size: 17px; }
.tl-who { font-size: 12.5px; color: var(--accent); margin-top: 2px; }
.tl-note { font-size: 12.5px; color: var(--muted); margin-top: 2px; padding-right: 18px; }

/* ------------------------------------------------------------ contacts --- */
.row .ico.shared { background: var(--accent-2); }
.kv.contact { text-decoration: none; color: inherit; }
.kv.contact b { color: var(--accent); }
/* The tinted icon squares are not only used inside list rows. */
.ico.home, .choice .big.home { background: #fdf0d5; }
.ico.auto, .choice .big.auto { background: #ddf3f3; }
.ico.shared { background: var(--accent-2); }
/* ------------------------------------- a photo instead of an emoji -------- */
/* The icon square holds either. A photo fills it; an emoji sits centred. */
.ico { overflow: hidden; }
.ico img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ico.big { width: 56px; height: 56px; border-radius: 14px; }
.ico.big .emo { font-size: 30px; }
/* A contact's face sits outside a .row and has no home/auto colour. */
.ico.big, .ico.huge { display: grid; place-items: center; flex: none; }
.ico.big:not(.home):not(.auto), .ico.huge:not(.home):not(.auto) { background: var(--surface-2); }
.ico.huge { width: 108px; height: 108px; border-radius: 22px; display: grid;
            place-items: center; }
.ico.huge .emo { font-size: 58px; }

.face { position: relative; background: none; border: 0; padding: 0; cursor: pointer;
        flex: none; line-height: 0; }
.face .ico { display: grid; place-items: center; }
.face-edit { position: absolute; right: -4px; bottom: -4px; width: 21px; height: 21px;
             border-radius: 50%; background: var(--accent); color: #fff; font-size: 11px;
             display: grid; place-items: center; line-height: 1;
             box-shadow: 0 0 0 2px var(--bg); }
.face-now { display: grid; place-items: center; margin: 4px 0 18px; }

/* ------------------------------------------------- captions on photos ----- */
.shot-cap { position: absolute; left: 0; right: 0; bottom: 0; padding: 12px 5px 4px;
            font-size: 9.5px; line-height: 1.25; color: #fff; text-align: left;
            background: linear-gradient(transparent, rgba(10,14,22,.82));
            overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2;
            -webkit-box-orient: vertical; }
.lb-cap { width: 100%; max-width: 460px; margin-top: 14px; }
.lb-cap input { width: 100%; background: rgba(255,255,255,.1); color: #fff;
                border: 1px solid rgba(255,255,255,.25); border-radius: 11px;
                padding: 12px 14px; font: inherit; font-size: 15px; }
.lb-cap input::placeholder { color: rgba(255,255,255,.5); }
/* Some things have no business on a phone. The admin panel is a wide table
   of controls that would need rebuilding to be usable on a small screen,
   and it is a desk job anyway. Hidden rather than shrunk. */
@media (max-width: 720px) { .desktop-only { display: none !important; } }

/* The push diagnostic. Monospace and selectable, because the point of it is
   being able to read it out or copy it. */
#pushDiag { white-space: pre-wrap; word-break: break-word; user-select: text;
            font: 11.5px/1.55 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
            background: var(--surface-2); border: 1px solid var(--border);
            border-radius: 12px; padding: 12px; margin: 10px 0 0; color: var(--text-2); }

/* "A new version is ready." Sits above the tab bar, out of the way of the
   thumb, and never reloads without being asked. */
.update-bar { position: fixed; left: 12px; right: 12px;
              bottom: calc(var(--tabs-h) + env(safe-area-inset-bottom) + 12px);
              z-index: 120; display: flex; align-items: center; gap: 9px;
              background: var(--surface); border: 1px solid var(--border);
              border-radius: 14px; padding: 11px 12px; box-shadow: var(--shadow);
              font-size: 14px; animation: rise .25s ease-out; }
.update-bar span { flex: 1; }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }

/* A message box that looks like the other fields. */
.screen-body textarea { width: 100%; background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text); border-radius: 12px;
  padding: 14px; font: inherit; font-size: 16px; resize: vertical;
  display: block; margin-top: 6px; }
.screen-body textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px;
  background: var(--surface); }

/* A list you can rearrange by holding a row down.
   touch-action matters: without it the browser claims the gesture for
   scrolling and the drag never gets a single move event. */
.list.sorting { touch-action: none; user-select: none; }
/* Holding a row to pick it up must not start selecting its text or pop the
   long-press menu first. */
.list[data-sortable] .row { -webkit-user-select: none; user-select: none;
                            -webkit-touch-callout: none; }
.list.sorting .row { transition: transform .12s ease; }
/* `.list.sorting .row.dragging`, not `.row.dragging`: the rule above outranks
   the shorter one, and the dragged row then eased after the finger instead of
   sticking to it. */
.list.sorting .row.dragging, .row.dragging { position: relative; z-index: 5; transition: none;
  box-shadow: 0 10px 24px rgba(14,15,17,.18); opacity: .97;
  transform-origin: center; }

/* Two ways in, side by side. */
.seg { display: flex; gap: 6px; background: var(--surface-2); padding: 4px;
  border-radius: 12px; margin: 4px 0 16px; }
.seg button { flex: 1; border: 0; background: none; border-radius: 9px;
  padding: 10px 8px; font: inherit; font-size: 14.5px; font-weight: 560;
  color: var(--muted); cursor: pointer; }
.seg button.on { background: var(--surface); color: var(--text);
  box-shadow: var(--shadow); }
.screen-body select { width: 100%; background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text); border-radius: 12px;
  padding: 13px 10px; font: inherit; font-size: 16px; display: block;
  margin-top: 6px; }

/* Searching the Upcoming list. Only rendered once there is enough in the
   list to justify it -- see SEARCH_FROM. */
#upcomingFind { margin-bottom: 10px; }
#upcomingFind input { width: 100%; background: var(--surface);
  border: 1px solid var(--border); color: var(--text); border-radius: 12px;
  padding: 12px 14px; font: inherit; font-size: 16px; }
#upcomingFind input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* A date, and a way to take it back out. Android's picker has no clear
   control of its own, and the field cannot be selected and deleted either, so
   without this a mistyped date is permanent. */
.date-row { display: flex; gap: 8px; align-items: center; }
.date-row input[type=date] { flex: 1; min-width: 0; }
.date-clear { flex: none; background: none; border: 0; padding: 8px 4px;
  color: var(--accent); font: inherit; font-size: 13.5px; cursor: pointer; }
.date-clear[hidden] { display: none; }

/* Dates are typed, with the calendar one tap away. The real <input type=date>
   still holds the value -- every form reads it -- but it sits invisible under
   the calendar button, because on Android tapping it was the only way in. */
.date-typed { position: relative; display: flex; gap: 8px; align-items: center;
              flex: 1; min-width: 0; margin-top: 6px; }
/* Prefixed with .screen-body to outrank the general screen input rule above. */
.screen-body .date-typed input.date-text { flex: 1; min-width: 0; margin: 0; }
.screen-body .date-typed input.date-text.bad { border-color: var(--bad); }
.screen-body .date-typed input.date-native, .date-typed input.date-native { position: absolute; right: 0; bottom: 0; width: 1px;
              height: 1px; padding: 0; border: 0; opacity: 0; pointer-events: none; }
.date-cal { flex: none; width: 50px; height: 50px; border-radius: 12px; cursor: pointer;
            background: var(--surface-2); border: 1px solid var(--border);
            font-size: 20px; line-height: 1; }
.screen-body .date-row .date-typed { margin-top: 0; }

/* Only on test.proonthewaylite.com -- see the top of app.js. Above everything,
   and click-through, so it labels the screen without getting in the way. */
.env-strip { position: fixed; top: env(safe-area-inset-top); left: 50%; z-index: 200;
             transform: translateX(-50%); pointer-events: none;
             background: #e8590c; color: #fff; font-size: 11px; font-weight: 700;
             letter-spacing: .08em; padding: 3px 12px; border-radius: 0 0 9px 9px; }

/* The "are you sure" box. */
.ask-veil { position: fixed; inset: 0; z-index: 90; background: rgba(0,0,0,.55);
            display: grid; place-items: center; padding: 24px; }
.ask { width: 100%; max-width: 360px; background: var(--surface); color: var(--text);
       border: 1px solid var(--border); border-radius: 18px; padding: 22px 20px 18px; }
.ask h3 { margin: 0 0 8px; font-size: 17px; line-height: 1.35; overflow-wrap: anywhere; }
.ask p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; }
.ask-btns { display: flex; gap: 10px; margin-top: 20px; }
.ask-btns .btn { flex: 1; }

/* An invitation waiting on a yes or a no. */
.card.invite { border-color: var(--accent); margin-bottom: 12px; }
.card.invite .t { font-size: 15.5px; font-weight: 600; }
.invite-acts { display: flex; gap: 9px; margin-top: 14px; }
.invite-acts .btn { flex: 1; margin-top: 0; }

/* Destructive actions look destructive. */
.btn.danger { color: var(--bad); border-color: rgba(214,69,60,.35); background: var(--bad-2); }
.btn.danger:active { opacity: .8; }
.note a { color: var(--accent); text-decoration: none; }

/* ------------------------------------------------------------- the tour --- */
/* The dim is four panels around a hole rather than one overlay with a
   transparent window, because a real gap lets taps through: somebody who would
   rather try the step than read about it can just do it. */
.tour { position: fixed; inset: 0; z-index: 200; pointer-events: none; }
.tour-hole { position: fixed; border-radius: 14px; pointer-events: none;
             box-shadow: 0 0 0 9999px rgba(12,16,24,.72),
                         0 0 0 2px var(--accent) inset;
             transition: left .28s, top .28s, width .28s, height .28s; }
/* Centred, and never wider than the screen. The old rule stacked a
   translateX, a margin-left and a translate on top of each other and landed
   off-centre on a narrow phone. */
.tour-card { position: fixed; left: 50%; transform: translateX(-50%);
             width: min(360px, calc(100vw - 28px));
             background: var(--surface); border-radius: 18px; padding: 20px;
             box-shadow: 0 24px 60px rgba(12,16,24,.35); pointer-events: auto;
             transition: top .2s; }
.tour-step { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
             color: var(--accent); font-weight: 650; }
.tour-card h3 { font-size: 18px; margin: 7px 0 7px; letter-spacing: -.01em; }
.tour-card p { font-size: 14.5px; line-height: 1.6; color: var(--text-2); margin: 0; }
.tour-dots { display: flex; gap: 6px; margin: 16px 0 14px; }
.tour-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--border); }
.tour-dots i.on { background: var(--accent); width: 20px; border-radius: 4px; }
.tour-btns { display: flex; gap: 9px; }
.tour-btns .btn { flex: 1; padding: 12px 14px; font-size: 14.5px; }
.tour-btns .btn.ghost { flex: 0 0 auto; }

/* A pencil that sits on the same line as the name it edits. */
.edit-pencil { font-size: 15px; vertical-align: middle; margin-left: 6px;
               opacity: .55; line-height: 1; }
.edit-pencil:hover { opacity: 1; }

/* A field that belongs to the option above it, tucked in rather than parked at
   the bottom of the form. */
.opt-extra { padding: 0 4px 4px; margin-top: -4px; }
.opt-extra input { width: 100%; }

/* Fuel. Its own colour because it is its own question: what a car costs to
   drive, as against what it costs to keep. */
.money-card.fuel { background: rgba(63,94,251,.07); border-color: rgba(63,94,251,.28); }
.money-card.fuel .lbl { color: #3f5efb; }