@import url("https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&f[]=general-sans@400,500,600,700&display=swap");

/* ============================================================
   CleanGrid — cleaner app, "Operations board" language
   ------------------------------------------------------------
   Same tokens as the admin dashboard (style.css), re-cut for a
   one-handed, single-column phone. Three ideas carry over:

     1. ELEVATION — the page is a cool slate field (#F1F4F9) and
        every card is pure white with a soft shadow, so a job row
        reads as an object you can press rather than a ruled line.
        Replaces the old flat cream-on-cream + 1.5px border look.

     2. ICON BADGES — every emoji now sits in a tinted rounded
        square, the same treatment as the admin's .icon-chip /
        .kpi-chip. Done with ::first-letter so the emoji stays
        inside the translated string and the EN/ES layer is
        untouched.

     3. GRADIENT DISCIPLINE — the indigo→sky gradient appears on
        the brand mark and on things you press to move the job
        forward (arrive, mark complete, submit). Nowhere else.
        Summary figures use the admin's white KPI-card treatment
        instead, so there is one focal point per screen.

   Touch: --tap (52px) is a hard floor for every primary control
   and is never reduced anywhere in this file.
   ============================================================ */
:root {
  /* --- Text & neutral surfaces (mirrors style.css) --- */
  --ink: #0B1220;
  --ink-2: #1E293B;
  --muted: #64748B;      /* secondary copy ON WHITE cards — 5.0:1, passes AA */
  /* Secondary copy sitting directly ON THE SLATE CANVAS. The admin app almost
     never does this (its muted text lives on white panels), but this app has
     labels, subtitles and empty states on the bare field, where #64748B is
     only 4.32:1. Slate-600 takes the same pairing to 6.8:1. */
  --muted-canvas: #475569;
  --muted-2: #94A3B8;
  --canvas: #F1F4F9;
  --surface: #FFFFFF;
  --surface-2: #F7F9FC;
  --border: #E2E8F1;
  --border-soft: #EEF2F7;

  /* --- Brand --- */
  --brand: #4F46E5;
  --brand-hover: #4338CA;
  --brand-lift: #0EA5E9;
  --brand-soft: #EEF0FF;
  --brand-grad: linear-gradient(135deg, #4F46E5 0%, #0EA5E9 100%);
  /* Same sweep, held on indigo for the first half. On the admin app's small
     pill buttons the gradient barely leaves indigo, but a 354px-wide phone
     button traverses the whole ramp — and white on the #0EA5E9 end is only
     2.8:1, which is not readable on a Brisbane roof in February. Parking the
     first stop at 52% keeps every glyph of the centred label over indigo
     (6.3:1) while the sky end still reads as the same brand gradient. */
  --brand-grad-btn: linear-gradient(135deg, #4F46E5 0%, #4F46E5 52%, #0EA5E9 100%);

  /* --- Status signals: every one of these is paired with a word
         in the markup, never used as the only cue. --- */
  --green: #15803D;  --green-soft: #DCFCE7;
  --amber: #B45309;  --amber-soft: #FEF3C7;
  --red:   #DC2626;  --red-soft:   #FEE2E2;
  /* Deeper red for error *text*: #DC2626 is 4.40:1 on the slate canvas, just
     under AA. This is 5.9:1, and matches style.css's .chip-red glyph colour. */
  --red-text: #B91C1C;

  /* --- Legacy aliases: this file and the strings cleaner.js
         builds still say --teal / --gold / --brown / --cream. --- */
  --teal: #4F46E5;
  --teal-dark: #0B1220;
  --gold: #B45309;
  --brown: #64748B;
  --cream: #F1F4F9;
  --cream-mid: #F7F9FC;

  /* --- Shape & depth (identical scale to style.css) --- */
  --radius-lg: 18px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --radius-xs: 8px;
  --radius-pill: 999px;
  --radius: 14px; /* legacy alias, = --radius-md */
  --shadow-xs: 0 1px 2px rgba(11, 18, 32, 0.05);
  --shadow-sm: 0 1px 2px rgba(11, 18, 32, 0.04), 0 2px 6px rgba(11, 18, 32, 0.05);
  --shadow-md: 0 2px 4px rgba(11, 18, 32, 0.04), 0 10px 24px rgba(11, 18, 32, 0.08);
  --shadow-lg: 0 24px 60px rgba(11, 18, 32, 0.22);
  --shadow-brand: 0 6px 18px rgba(79, 70, 229, 0.34);

  /* --- Type --- */
  --font-ui: "Satoshi", "General Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-data: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --focus: #0284C7;
  --tap: 52px; /* minimum comfortable tap target — hard floor */
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font-ui);
  background: var(--canvas);
  color: var(--ink-2);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  padding-bottom: env(safe-area-inset-bottom);
}
.hidden { display: none !important; }

/* ---------- Map tile-loading fallback overlay ---------- */
.map-tile-fallback {
  position: absolute; inset: 0; z-index: 500; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 8px; background: rgba(255,255,255,0.92); text-align: center; padding: 16px;
  font-size: 13px; color: var(--muted); pointer-events: none;
}
.map-tile-fallback button { pointer-events: auto; }

button { font-family: inherit; }
button:focus-visible, input:focus-visible, textarea:focus-visible,
summary:focus-visible, a:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* Figures that get compared line up in a column, same rule as the admin app. */
.hours-summary-value, .hours-shift-row .shift-minutes, .job-card .time-chip,
.pin-input, .count-pill {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

/* ============================================================
   ICON BADGES
   The admin app puts every glyph in a tinted rounded square
   (.icon-chip / .kpi-chip). Here the glyph is an emoji that lives
   *inside* a translated string, so the badge is drawn with
   ::first-letter — the text node is never touched, and the EN/ES
   layer keeps working exactly as before.
   ============================================================ */
.ib { display: block; }
.ib::first-letter {
  border-radius: var(--radius-xs);
  padding: 5px 6px;
  margin-right: 9px;
  font-size: 15px;
}
.ib-indigo::first-letter { background: #E0E7FF; }
.ib-blue::first-letter   { background: #DBEAFE; }
.ib-teal::first-letter   { background: #CCFBF1; }
.ib-purple::first-letter { background: #EDE9FE; }
.ib-amber::first-letter  { background: #FEF3C7; }
.ib-green::first-letter  { background: #DCFCE7; }
.ib-red::first-letter    { background: #FEE2E2; }

/* ---------- Bottom tab bar ---------- */
.tab-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -2px 6px rgba(11, 18, 32, 0.05), 0 -8px 24px rgba(11, 18, 32, 0.06);
}
.tab-btn {
  flex: 1;
  min-height: var(--tap);
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 4px 9px;
  background: none; border: none; cursor: pointer;
  color: var(--muted);
}
/* The tinted pill behind the active icon is the tab bar's version of the
   icon badge — it marks position without relying on colour alone, because
   the label underneath goes brand-coloured and bold at the same time. */
.tab-btn .tab-ic {
  width: 38px; height: 26px;
  border-radius: var(--radius-pill);
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; line-height: 1;
  background: transparent;
  transition: background 0.15s ease;
}
.tab-btn span:not(.tab-ic) {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: -0.005em;
}
.tab-btn.active { color: var(--brand); }
.tab-btn.active .tab-ic { background: var(--brand-soft); }
.tab-btn.active span:not(.tab-ic) { font-weight: 700; }
.tab-btn:active .tab-ic { transform: scale(0.94); }

/* ---------- Steps (simple view switcher) ---------- */
.step { display: none; padding: 20px 18px 40px; max-width: 480px; margin: 0 auto; }
.step.active { display: block; animation: fadeIn 0.18s ease-out; }
.step.active.has-tabs { padding-bottom: 96px; } /* clears the fixed tab bar */
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ---------- Language switcher ---------- */
.lang-switcher {
  display: flex; justify-content: center; gap: 4px;
  padding-top: max(10px, env(safe-area-inset-top));
}
.lang-btn {
  min-height: 44px;
  padding: 6px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.03em;
  cursor: pointer;
  box-shadow: var(--shadow-xs);
}
.lang-btn.active { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: var(--shadow-xs); }

/* ---------- Step 1: who ---------- */
.brand { text-align: center; padding: 24px 0 20px; }
/* Gradient here is deliberate and matches style.css: the brand mark is one
   of the three sanctioned uses, alongside primary actions. */
.brand-mark {
  width: 46px; height: 46px;
  border-radius: var(--radius-md);
  background: var(--brand-grad);
  box-shadow: var(--shadow-brand);
  display: inline-grid; grid-template-columns: 7px 7px; grid-template-rows: 7px 7px; gap: 3px;
  align-content: center; justify-content: center;
  margin-bottom: 14px;
}
.brand-mark span { width: 7px; height: 7px; background: #fff; border-radius: 2px; }
.brand-mark span:nth-child(2), .brand-mark span:nth-child(3) { opacity: 0.55; }
.brand-wordmark {
  font-size: 11px; font-weight: 700; color: var(--muted-canvas);
  letter-spacing: 0.06em; text-transform: uppercase;
  margin-top: -8px; margin-bottom: 8px;
}
.brand h1 { font-size: 24px; margin: 0 0 6px; color: var(--ink); font-weight: 700; letter-spacing: -0.02em; }
.subtitle { color: var(--muted-canvas); font-size: 15px; margin: 0; }

.cleaner-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 22px;
}
.cleaner-tile {
  min-height: 76px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  cursor: pointer;
  transition: transform 0.08s ease, border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.cleaner-tile:active { transform: scale(0.97); }
.cleaner-tile:hover { border-color: var(--brand); box-shadow: var(--shadow-md); }
/* Selection state, not a press target — solid brand, no gradient. */
.cleaner-tile.selected {
  background: var(--brand); border-color: var(--brand); color: #fff;
  box-shadow: var(--shadow-brand);
}

.link-btn {
  display: flex; align-items: center; justify-content: center;
  margin: 18px auto 0;
  min-height: var(--tap);
  background: none;
  border: none;
  color: var(--muted-canvas);
  text-decoration: underline;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  padding: 10px 12px;
}
.link-btn:hover { color: var(--brand); }
.pin-input {
  display: block;
  margin: 22px auto 0;
  width: 210px;
  min-height: var(--tap);
  padding: 16px;
  text-align: center;
  font-size: 32px;
  letter-spacing: 0.5em;
  text-indent: 0.5em; /* balances the trailing letter-spacing so digits sit centred */
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.pin-input:focus { border-color: var(--brand); box-shadow: var(--shadow-sm), 0 0 0 3px var(--brand-soft); outline: none; }
/* The confirm-PIN block sits outside .brand, so it needs the centring the
   rest of this screen already has. */
#pinConfirmRow .subtitle { text-align: center; }
#termsCheckbox { width: 24px; height: 24px; accent-color: var(--brand); }
.pin-error { color: var(--red-text); text-align: center; font-size: 13.5px; min-height: 1.2em; margin: 14px 0 0; font-weight: 600; }
.date-row { margin-top: 10px; text-align: center; }
.date-row label {
  display: block; font-size: 11px; color: var(--muted-canvas); margin-bottom: 6px;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
}
.date-row input {
  min-height: var(--tap);
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  font-family: inherit;
  font-size: 16px; /* 16px keeps iOS from zooming the viewport on focus */
  color: var(--ink);
  width: 100%;
}
.date-row input:focus { border-color: var(--brand); outline: none; }

/* ---------- Bar / header used on steps 2 & 3 ----------
   The admin app frames its work area with a white chrome bar. Here the
   sticky header does the same job full-bleed, so the cards below it read
   as sitting on the slate canvas rather than floating in the header. */
.bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: -20px -18px 18px;
  padding: max(14px, env(safe-area-inset-top)) 18px 13px;
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
}
.icon-back {
  width: 48px; height: 48px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  color: var(--ink);
  font-size: 19px;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.08s ease, border-color 0.15s ease;
}
.icon-back:hover { border-color: var(--brand); color: var(--brand); }
.icon-back:active { transform: scale(0.95); }
.bar-title { font-size: 18px; font-weight: 700; color: var(--ink); line-height: 1.2; letter-spacing: -0.02em; }
.bar-sub { font-size: 13px; color: var(--muted); margin-top: 1px; }

/* ---------- Push-notification banner ---------- */
.push-banner {
  background: var(--brand-soft); border: 1px solid var(--brand); border-radius: var(--radius-md);
  padding: 12px 14px; margin: 12px 16px 0; display: flex; align-items: center; gap: 10px;
}
.push-banner .push-banner-icon { font-size: 22px; flex-shrink: 0; }
.push-banner .push-banner-text { flex: 1; font-size: 13px; color: var(--ink-2, var(--ink)); line-height: 1.35; }
.push-banner .push-banner-text strong { display: block; font-size: 13.5px; margin-bottom: 2px; }
.push-banner button {
  flex-shrink: 0; background: var(--brand); color: #fff; border: none; border-radius: var(--radius-sm, 10px);
  padding: 8px 12px; font-size: 12.5px; font-weight: 700; font-family: inherit; cursor: pointer;
}
.push-banner button:active { transform: scale(0.97); }
.push-banner.push-banner-active { background: var(--green-soft, #DCFCE7); border-color: var(--green, #15803D); }

/* ---------- "Install the app" banner — shown before login, on the
   who's-cleaning screen, so it's the very first thing a cleaner sees when
   they scan the invite QR code. Android gets a real one-tap install button
   (beforeinstallprompt); iOS gets step-by-step Share-sheet instructions
   since Apple doesn't allow a programmatic install trigger. ---------- */
.install-banner {
  background: var(--brand-soft); border: 1px solid var(--brand); border-radius: var(--radius-md);
  padding: 14px; margin: 0 0 16px; text-align: left;
}
.install-banner .install-banner-title { font-size: 14px; font-weight: 800; color: var(--ink); display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.install-banner .install-banner-body { font-size: 13px; color: var(--ink-2, var(--ink)); line-height: 1.4; }
.install-banner .install-banner-steps { margin: 8px 0 0; padding-left: 18px; font-size: 13px; color: var(--ink-2, var(--ink)); line-height: 1.6; }
.install-banner .install-banner-steps .tap-icon { display: inline-block; font-size: 15px; }
.install-banner button.install-banner-btn {
  margin-top: 10px; width: 100%; background: var(--brand); color: #fff; border: none; border-radius: var(--radius-sm, 10px);
  padding: 11px 14px; font-size: 14px; font-weight: 700; font-family: inherit; cursor: pointer;
}
.install-banner button.install-banner-btn:active { transform: scale(0.98); }
.install-banner.install-banner-dismissed { display: none; }

/* ---------- Step 2: job list ---------- */
.jobs-list { display: flex; flex-direction: column; gap: 10px; }
/* Not scoped to .jobs-list: #leaveListLabel is a sibling of its list, so the
   old `.jobs-list .day-group-label` rule never reached it and "My requests"
   rendered unstyled. */
.day-group-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--muted-canvas); margin: 16px 0 8px; padding-top: 6px;
}
.jobs-list .day-group-label { margin-bottom: 2px; }
.jobs-list .day-group-label:first-child { margin-top: 0; }

/* ---------- My Week ---------- */
.week-map-wrap { margin: 4px 0 16px; }
.week-map {
  height: 260px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.week-map-wrap .hint { text-align: center; margin: 10px 0 0; font-size: 12.5px; color: var(--muted-canvas); }
.week-pin {
  width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  color: white; font-weight: 800; font-size: 11px;
  box-shadow: 0 2px 6px rgba(11, 18, 32, 0.35); border: 2px solid white;
  background: var(--brand);
}
.week-pin-nav {
  display: inline-block; margin-top: 4px; color: var(--brand); font-weight: 700; text-decoration: underline;
}

/* ---------- My Leave ---------- */
.leave-form-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 16px;
  margin-bottom: 18px;
}
.leave-form-row { margin-bottom: 12px; }
.leave-form-row label {
  display: block; font-size: 11px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 7px;
}
.leave-form-row input, .leave-form-row textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 13px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  color: var(--ink);
  font-size: 16px; /* no iOS zoom-on-focus */
  font-family: inherit;
  resize: vertical;
}
.leave-form-row textarea { line-height: 1.5; }
.leave-form-row input::placeholder, .leave-form-row textarea::placeholder { color: var(--muted-2); }
.leave-form-row input:focus, .leave-form-row textarea:focus {
  border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); outline: none;
}
.leave-error { color: var(--red-text); font-size: 13px; font-weight: 600; min-height: 1.2em; margin: 0 0 10px; }
.leave-request-row {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 13px 14px;
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
}
.leave-request-row .leave-dates { font-weight: 700; font-size: 14.5px; color: var(--ink); }
.leave-request-row .leave-reason { font-size: 12.5px; color: var(--muted); margin-top: 3px; line-height: 1.4; }
/* Soft-tint pairs from the token set. The word is always present — the
   colour is a second reading of it, never the only one. */
.leave-status-pill {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  border-radius: var(--radius-pill); padding: 6px 11px; flex-shrink: 0;
}
.leave-status-pill.pending  { background: var(--amber-soft); color: var(--amber); }
.leave-status-pill.approved { background: var(--green-soft); color: var(--green); }
.leave-status-pill.denied   { background: var(--red-soft);   color: var(--red-text); }
.leave-withdraw-btn {
  display: flex; align-items: center;
  min-height: 44px; padding: 8px 6px;
  background: none; border: none; color: var(--red-text); font-size: 12px; font-weight: 700;
  text-decoration: underline; cursor: pointer; flex-shrink: 0;
}

/* ---------- My Hours ----------
   Was a solid brand slab. Now the admin app's KPI-card treatment: white
   surface, tinted icon badge, uppercase label, tabular figure — which
   also keeps the gradient free for the buttons that actually do work. */
.hours-summary-card {
  position: relative; overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  color: var(--ink);
  padding: 18px 18px 20px;
  text-align: center;
  margin-bottom: 18px;
}
.hours-summary-card::before {
  content: "⏱";
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  margin: 0 auto 12px;
  border-radius: var(--radius-sm);
  background: #E0E7FF;
  font-size: 16px; line-height: 1;
}
/* No tone bar here, unlike the admin KPI card: there it encodes good/watch/bad,
   and a cleaner's hours total has no such reading. A stripe with nothing to
   say is decoration, which is the one thing colour is not allowed to be. */
.hours-summary-value { font-size: 32px; font-weight: 700; color: var(--ink); line-height: 1.05; }
.hours-summary-label {
  font-size: 10.5px; font-weight: 700; color: var(--muted); margin-top: 8px;
  text-transform: uppercase; letter-spacing: 0.07em;
}
.hours-shift-row {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 13px 14px;
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
}
.hours-shift-row .shift-name { font-weight: 700; font-size: 14.5px; color: var(--ink); }
.hours-shift-row .shift-date { font-size: 12px; color: var(--muted); margin-top: 3px; }
.hours-shift-row .shift-minutes { font-weight: 700; font-size: 15px; color: var(--brand); text-align: right; }
.hours-shift-row .shift-incomplete {
  color: var(--amber); background: var(--amber-soft);
  border-radius: var(--radius-pill); padding: 5px 10px;
  font-size: 11px; font-weight: 700; text-align: right; white-space: nowrap;
}

/* ---------- Job card ---------- */
.job-card {
  display: flex;
  align-items: center;
  gap: 13px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 12px 14px;
  min-height: var(--tap);
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: transform 0.08s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.job-card:hover { border-color: #CBD5E1; box-shadow: var(--shadow-md); }
.job-card:active { transform: scale(0.985); }
.job-card .time-chip {
  background: var(--brand-soft);
  color: var(--brand);
  font-weight: 700;
  font-size: 13px;
  border-radius: var(--radius-sm);
  padding: 9px 10px;
  min-width: 58px;
  text-align: center;
  flex-shrink: 0;
}
.job-card .job-meta { flex: 1; min-width: 0; }
.job-card .job-name { font-weight: 700; font-size: 16px; color: var(--ink); letter-spacing: -0.015em; }
.job-card .job-suburb { font-size: 13px; color: var(--muted); margin-top: 3px; }
/* Photo state carries a tick as well as a colour, so it survives greyscale
   and colour-blind viewing — the same "never colour alone" rule the admin
   app applies to its status pills. */
.job-card .status-dot {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--surface-2); border: 1px solid var(--border);
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.job-card .status-dot.has-photos { background: var(--green); border-color: var(--green); }
.job-card .status-dot.has-photos::after {
  content: "✓"; color: #fff; font-size: 11px; font-weight: 800; line-height: 1;
}
.job-card .chevron { color: var(--muted-2); font-size: 20px; flex-shrink: 0; }

/* ---------- Cash collection tab ---------- */
.cash-card { cursor: default; }
/* Solid brand, not the gradient: this button repeats once per client down the
   list, and a gradient that appears eight times on one screen has stopped
   marking "the thing to press". Solid indigo also holds 6.3:1 across the whole
   pill, where the sky end of the ramp would dip to 4.4:1 on a control this
   narrow. */
.cash-card .cash-collect-btn {
  flex-shrink: 0; padding: 10px 16px; font-size: 13px; width: auto;
  min-height: 44px; margin-top: 0;
  background: var(--brand);
  box-shadow: var(--shadow-xs);
}
.cash-collected-pill {
  flex-shrink: 0;
  background: var(--green-soft);
  color: var(--green);
  font-weight: 700;
  font-size: 12.5px;
  padding: 8px 12px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

.empty-state {
  text-align: center;
  padding: 44px 20px;
  color: var(--muted-canvas);
}
/* Empty-state glyph gets the same tinted-badge treatment as every other
   icon in the app instead of floating as bare 40px emoji. */
.empty-state .emoji {
  width: 64px; height: 64px;
  margin: 0 auto 16px;
  border-radius: var(--radius-lg);
  background: var(--brand-soft);
  display: flex; align-items: center; justify-content: center;
  font-size: 30px; line-height: 1;
}
.empty-state h2 { font-size: 17px; color: var(--ink); margin: 0 0 8px; letter-spacing: -0.015em; }
.empty-state p { font-size: 14px; margin: 0 0 16px; line-height: 1.55; }

.skeleton-card {
  height: 68px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
  background: linear-gradient(90deg, var(--surface-2) 25%, #fff 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease infinite;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* ============================================================
   Step 3: job detail
   ============================================================ */
.checkin-bar { margin-bottom: 14px; }
/* The two sanctioned gradient buttons. They never appear at the same time —
   "I've arrived" is replaced by "Mark complete" — so the screen always has
   exactly one gradient element, which is the point. */
.checkin-action-btn {
  width: 100%;
  min-height: var(--tap);
  border-radius: var(--radius-pill);
  border: none;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: transform 0.1s ease, box-shadow 0.18s ease;
}
.checkin-action-btn.arrive,
.checkin-action-btn.complete {
  background: var(--brand-grad-btn);
  color: #fff;
  box-shadow: var(--shadow-brand);
}
.checkin-action-btn:active { transform: scale(0.98); }
.checkin-action-btn:disabled { opacity: 0.6; box-shadow: none; }
.checkin-status-row {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.checkin-status-pill {
  flex: 1;
  min-width: 140px;
  background: var(--green-soft);
  border: 1px solid transparent;
  color: var(--green);
  border-radius: var(--radius-md);
  padding: 12px;
  font-size: 13px;
  font-weight: 700;
  text-align: center;
}
.checkin-status-pill.pending {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--muted);
  opacity: 1;
}

.flag-banner {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  background: var(--amber-soft);
  border: 1px solid #FCD34D;
  color: var(--ink-2);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  padding: 13px 14px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  margin-bottom: 14px;
}
/* cleaner.js writes this banner with textContent, so the badge has to be a
   pseudo-element — it survives every re-render and every language switch. */
.flag-banner::before {
  content: "⚠️";
  flex-shrink: 0;
  width: 30px; height: 30px;
  border-radius: var(--radius-xs);
  background: #FDE68A;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; line-height: 1;
}

.info-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 14px 16px 16px;
  margin-bottom: 12px;
}
.info-card-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  line-height: 28px;
  margin-bottom: 6px;
}
.info-card-body {
  font-size: 15px;
  color: var(--ink-2);
  line-height: 1.55;
  white-space: pre-line;
}
.info-card-body a { color: var(--brand); font-weight: 600; }

.nav-btn-row { display: flex; gap: 8px; margin-top: 12px; }
.nav-chip {
  flex: 1;
  min-height: 46px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 10px;
  box-shadow: var(--shadow-xs);
  transition: transform 0.08s ease, background 0.15s ease;
}
.nav-chip:active { transform: scale(0.97); }
.nav-chip.active { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: var(--shadow-xs); }

.standard-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 2px 16px;
  margin-bottom: 18px;
}
/* Block, not flex: ::first-letter only applies to block containers, and the
   badge on "✨ The FYS standard" is drawn that way. 12+28+12 = 52px = --tap. */
.standard-card summary {
  display: block;
  padding: 12px 0;
  font-weight: 700;
  font-size: 14px;
  line-height: 28px;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}
.standard-card summary::-webkit-details-marker { display: none; }
.standard-card p { font-size: 13px; color: var(--muted); margin: 0 0 10px; line-height: 1.5; }
.standard-card ul { margin: 0 0 14px; padding-left: 20px; font-size: 13.5px; color: var(--ink-2); line-height: 1.8; }

.checklist-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 14px 16px 12px;
  margin-bottom: 12px;
}
.checklist-card .info-card-label { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.checklist-items { display: flex; flex-direction: column; gap: 0; }
.checklist-item {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  padding: 8px 2px;
  font-size: 14.5px;
  color: var(--ink-2);
  border-bottom: 1px solid var(--border-soft);
  cursor: pointer;
}
.checklist-item:last-child { border-bottom: none; }
.checklist-item input[type="checkbox"] {
  width: 22px; height: 22px; accent-color: var(--brand); flex-shrink: 0;
}
.checklist-item:has(input:checked) span { color: var(--muted); text-decoration: line-through; }

.photo-section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 14px 16px 16px;
  margin-bottom: 12px;
}
.photo-section-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  line-height: 28px;
  margin-bottom: 10px;
}
.count-pill {
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 12px;
  font-weight: 700;
  border-radius: var(--radius-pill);
  min-width: 24px;
  text-align: center;
  padding: 4px 9px;
  flex-shrink: 0;
}
.count-pill.filled { background: var(--green); color: #fff; }
.thumb-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.thumb-row img {
  width: 66px; height: 66px; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
}
/* Secondary-strength: unmistakably pressable at 52px, but tinted rather than
   gradient — three of these stack on one screen and they must not outrank
   the single check-in action above them. */
.upload-btn {
  width: 100%;
  min-height: var(--tap);
  border-radius: var(--radius-pill);
  border: 1px solid var(--brand);
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: transform 0.08s ease, background 0.15s ease;
}
.upload-btn:hover { background: #E4E7FF; }
.upload-btn:active { transform: scale(0.98); }
.upload-btn.secondary { background: var(--surface); color: var(--red-text); border-color: #FCA5A5; }
.upload-btn.secondary:hover { background: var(--red-soft); }
.upload-btn:disabled { opacity: 0.55; }

/* Primary submit — PIN continue, request leave, collect cash. */
.primary-btn {
  width: 100%;
  min-height: var(--tap);
  margin-top: 18px;
  background: var(--brand-grad-btn);
  color: #fff;
  border: none;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-brand);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.18s ease;
}
.primary-btn:active { transform: scale(0.98); }
.primary-btn:disabled { opacity: 0.6; box-shadow: none; }
/* The "back to my jobs list" footer button is not a primary action, so it
   drops the gradient entirely. */
.primary-btn.secondary-btn {
  background: var(--surface);
  color: var(--brand);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
}
.primary-btn.secondary-btn:hover { border-color: var(--brand); }

/* ---------- Toast ---------- */
.toast {
  position: fixed;
  top: max(14px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%) translateY(-140%);
  background: var(--ink);
  color: white;
  padding: 13px 18px;
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 600;
  z-index: 100;
  box-shadow: var(--shadow-lg);
  /* Fades as well as slides: parked off-screen the empty pill still clipped a
     few pixels into the top of the viewport on tall phones. Opacity (not
     visibility) keeps it in the accessibility tree for aria-live. */
  opacity: 0;
  pointer-events: none;
  transition: transform 0.25s ease, opacity 0.2s ease;
  max-width: 90vw;
  text-align: center;
}
.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
.toast.error { background: var(--red-text); }
.toast.success { background: var(--green); }

/* ---------- Small screens polish ---------- */
@media (max-width: 340px) {
  .cleaner-grid { grid-template-columns: 1fr; }
  .checkin-status-pill { min-width: 100%; }
}

/* Respect the OS "reduce motion" setting — cleaners run this app all day. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
