/* ---- theme ---------------------------------------------------------------
   Three states, not two: light, dark, and "whatever the phone says". The
   third is the default and is what most people want — the app should go dark
   at night with everything else on the handset.

   The OS rule is scoped :not([data-theme]) so an explicit choice always wins;
   without that, `@media (prefers-color-scheme: dark)` and `[data-theme=light]`
   sit at different specificities on different properties and the result
   depends on which line came last.

   Every colour that differs between themes is a variable, including the
   weekend and holiday tints further down. A hard-coded dark colour behind a
   media query cannot follow an explicit choice, which is how a "light" toggle
   ends up with a dark calendar. */
:root {
  --brand: #1e63d0;
  --bg: #f4f6f9;
  --card: #ffffff;
  --text: #1f2937;
  --muted: #6b7280;
  --line: #e5e7eb;
  --danger: #dc2626;
  --ok: #15803d;
  /* Amber dark enough to read on white; the dark themes lighten it. */
  --warn: #b45309;
  --radius: 16px;

  --tint-sun: #fdeef3;
  --tint-sat: #eef0f3;
  --tint-nat: #fdece0;
  --tint-mass: #f5eafb;
  --fg-sun: #c2185b;
  --fg-sat: #8a93a2;
  --fg-nat: #c2410c;
  --fg-mass: #7b1fa2;

  /* Status tints for the leave summary. Separate from the calendar tints
     above because these mean remaining / in flight / entitlement, not
     weekend / holiday, and one day the two palettes will need to differ. */
  --tint-ok: #ecfdf5;    --tint-ok-line: #a7f3d0;
  --tint-wait: #fffbeb;  --tint-wait-line: #fde68a;
  --tint-brand: #eff6ff; --tint-brand-line: #bfdbfe;
  --tint-none: #f3f4f6;

  /* Verdict colours for a punch. ONE rule each, deliberately:
       ok    — met or beat the schedule (early/on-time in, on-time/late out)
       short — fell short of it (late in OR early out — same meaning, one colour)
       over  — approved overtime; a different CATEGORY, not a deviation.
     Amber, red and slate are NOT reused here: they already mean incomplete
     record, no record, and rostered day off on the day dots. */
  --verdict-ok: #16a34a;
  --verdict-short: #2563eb;
  --verdict-over: #7c3aed;

  /* Record status — a different question from the verdict above. These answer
     "is there a record at all", not "was it on time". Named as variables so the
     day dots and the donut rings cannot drift apart: they carry the same
     meaning and the shared legend describes both. */
  --st-present: #16a34a;
  --st-partial: #e0a800;
  --st-none: var(--danger);
  --st-holiday: #64748b;
}

/* Dark values, written once and applied by either trigger. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --ok: #4ade80;
    --warn: #fbbf24;
    --bg: #0f1115;
    --card: #171a21;
    --text: #e6e8ec;
    --muted: #9aa3af;
    --line: #262b34;

    --tint-sun: rgba(194, 24, 91, .16);
    --tint-sat: rgba(138, 147, 162, .15);
    --tint-nat: rgba(196, 65, 12, .17);
    --tint-mass: rgba(123, 31, 162, .18);
    --fg-sun: #f48fb1;
    --fg-nat: #ffab7a;
    --fg-mass: #ce93d8;

    --tint-ok: rgba(74, 222, 128, .12);   --tint-ok-line: rgba(74, 222, 128, .30);
    --tint-wait: rgba(251, 191, 36, .12); --tint-wait-line: rgba(251, 191, 36, .30);
    --tint-brand: rgba(96, 165, 250, .13);--tint-brand-line: rgba(96, 165, 250, .32);
    --tint-none: rgba(154, 163, 175, .10);
    /* #1e63d0 is unreadable on #171a21; the brand blue lightens for dark. */
    --brand: #6ea8fe;

    --verdict-ok: #4ade80;
    --verdict-short: #60a5fa;
    --verdict-over: #c4b5fd;
  }
}

:root[data-theme="dark"] {
  --ok: #4ade80;
  --warn: #fbbf24;
  --bg: #0f1115;
  --card: #171a21;
  --text: #e6e8ec;
  --muted: #9aa3af;
  --line: #262b34;

  --tint-sun: rgba(194, 24, 91, .16);
  --tint-sat: rgba(138, 147, 162, .15);
  --tint-nat: rgba(196, 65, 12, .17);
  --tint-mass: rgba(123, 31, 162, .18);
  --fg-sun: #f48fb1;
  --fg-nat: #ffab7a;
  --fg-mass: #ce93d8;

  --tint-ok: rgba(74, 222, 128, .12);   --tint-ok-line: rgba(74, 222, 128, .30);
  --tint-wait: rgba(251, 191, 36, .12); --tint-wait-line: rgba(251, 191, 36, .30);
  --tint-brand: rgba(96, 165, 250, .13);--tint-brand-line: rgba(96, 165, 250, .32);
  --tint-none: rgba(154, 163, 175, .10);
  --brand: #6ea8fe;

  --verdict-ok: #4ade80;
  --verdict-short: #60a5fa;
  --verdict-over: #c4b5fd;
}

* { box-sizing: border-box; }

/* The UA rule for [hidden] is `[hidden] { display: none }` — one attribute
   selector, which ANY id or class rule that sets `display` will outrank. That
   is how #shell (display:flex) stayed on screen underneath the sign-in page
   while still carrying the hidden attribute. Make hidden mean hidden. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  /* Respect the iPhone notch and home indicator once installed. */
  padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom);
  -webkit-text-size-adjust: 100%;
}

/* Body is a column so the footer can sit at the bottom on short screens
   without floating over the content on long ones. */
body { min-height: 100vh; display: flex; flex-direction: column; }

.view { max-width: 460px; margin: 0 auto; padding: 24px 18px 40px; width: 100%; flex: 1; }

/* --- splash ------------------------------------------------------------- */
.splash {
  position: fixed; inset: 0; z-index: 20;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; background: var(--bg);
}
.splash-name { font-size: 14px; color: var(--muted); margin: 16px 0 0; letter-spacing: .02em; }

/* The IndoHRM mark is a wide wordmark, so it is sized by width and left to
   find its own height. Fixed height would squash it on narrow phones. */
.wordmark { width: 200px; max-width: 62vw; height: auto; display: block; }
.splash .wordmark { width: 220px; }

/* A tenant's own logo is usually square-ish, so it is sized by height and
   centred instead of stretched to the wordmark's width. */
.company-mark { height: 72px; width: auto; max-width: 62vw; margin: 0 auto; display: block; border-radius: 12px; }

.tagline {
  font-size: 13px; color: var(--muted); margin: 2px 0 10px;
  letter-spacing: .02em;
}

.brand { text-align: center; margin: 24px 0 20px; }
.brand picture { display: block; }
.brand .wordmark { margin: 0 auto; }
.brand h1 { font-size: 17px; font-weight: 600; color: var(--muted); margin: 12px 0 4px; }
/* The wordmark, the product name and the instruction were stacked at even
   spacing, so they read as one three-line paragraph. The gap separates what
   the app IS from what it is asking you to do. */
.brand h1 + .muted { margin-top: 14px; }
.brand .tagline { margin: 10px 0 18px; letter-spacing: .02em; }
.brand .tagline + h1 { margin-top: 0; }

.topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
.topbar h1 { font-size: 18px; margin: 2px 0 0; }

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 16px;
}

label { display: block; font-size: 13px; font-weight: 600; margin: 12px 0 6px; }

input {
  width: 100%;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  /* 16px keeps iOS Safari from zooming the whole page on focus. */
  font-size: 16px;
}

input:focus { outline: 2px solid var(--brand); outline-offset: 1px; }

button {
  font: inherit;
  cursor: pointer;
  border-radius: 10px;
  border: 1px solid transparent;
  /* Comfortably above the 44px touch target minimum. */
  min-height: 48px;
  width: 100%;
  margin-top: 14px;
}

button:disabled { opacity: .5; cursor: not-allowed; }

.primary { background: var(--brand); color: #fff; font-weight: 600; }
.secondary { background: transparent; border-color: var(--line); color: var(--text); }
.link { background: none; color: var(--muted); min-height: 40px; text-decoration: underline; }
.icon { width: 44px; min-height: 44px; margin: 0; background: none; border: 1px solid var(--line); color: var(--muted); font-size: 18px; }

.muted { color: var(--muted); }
.small { font-size: 12px; }
.error { color: var(--danger); font-size: 13px; margin: 12px 0 0; }

/* The clock card is padded tighter than a normal .card: on an iPhone the whole
   page is only ~670px tall and this card has to end above the fold. */
.clock { text-align: center; padding: 14px; }

/* Scheduled times on the top row, what actually happened underneath. Reading
   DOWN a column answers "was I on time?" without any arithmetic. All four are
   buttons — a blank "--:--" is exactly the moment someone wonders what to do
   about it, so that is where the answer has to live. */
.clockgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.cg {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  background: var(--bg); border: 1px solid transparent; border-radius: 10px;
  padding: 8px 6px; margin: 0; min-height: 0; width: 100%;
  color: inherit; text-align: center;
}
.cg:active { border-color: var(--line); }
.cg-val { font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* Only the ACTUAL figures carry a verdict colour. A schedule cannot be late. */
.cg-val.ok    { color: var(--verdict-ok); }
.cg-val.short { color: var(--verdict-short); }
.cg-val.over  { color: var(--verdict-over); }

.location { margin: 14px 0 0; }

.section { font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 26px 0 10px; }

.logs { list-style: none; padding: 0; margin: 0; }
.logs li {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 14px 16px; margin-bottom: 10px;
}

video { width: 100%; border-radius: 12px; background: #000; margin-top: 12px; }

.offline-bar {
  background: #b45309; color: #fff; text-align: center;
  padding: 10px 16px; font-size: 13px;
  position: sticky; top: 0; z-index: 10;
}

.footnote { margin-top: 26px; text-align: center; }

/* --- document reader ---------------------------------------------------- */
.doc { line-height: 1.6; font-size: 14px; }
.doc h1, .doc h2, .doc h3 { font-size: 15px; margin: 18px 0 6px; }
.doc p { margin: 0 0 12px; }
.doc ul, .doc ol { padding-left: 20px; margin: 0 0 12px; }
.doc a { color: var(--brand); }
/* Author-supplied HTML can carry wide tables or long URLs; keep the page
   itself from scrolling sideways. */
.doc img, .doc table { max-width: 100%; }
.doc table { display: block; overflow-x: auto; }
.doc { overflow-wrap: anywhere; }

.topbar h1 { flex: 1; }

/* --- footer ------------------------------------------------------------- */
.site-footer {
  border-top: 1px solid var(--line);
  padding: 18px 18px calc(18px + env(safe-area-inset-bottom));
  text-align: center;
  font-size: 12px;
  color: var(--muted);
}
.site-footer .version {
  font-weight: 600;
  color: var(--text);
  margin: 0 0 6px;
  letter-spacing: .02em;
}
.site-footer nav {
  display: flex; flex-wrap: wrap; gap: 8px;
  align-items: center; justify-content: center;
  margin-bottom: 6px;
}
.site-footer a { color: var(--brand); text-decoration: none; }
.site-footer a:hover { text-decoration: underline; }
.site-footer p { margin: 0; }

/* ====================================================================
   APP SHELL — app bar, drawer, tabs, bottom nav
   ==================================================================== */

/* The shell owns the scrolling area between a fixed app bar and a fixed
   bottom nav, so tab content scrolls under neither. */
#shell { display: flex; flex-direction: column; min-height: 100vh; }

.appbar {
  position: sticky; top: 0; z-index: 12;
  display: flex; align-items: center; gap: 8px;
  padding: 8px 8px calc(8px);
  background: var(--card);
  border-bottom: 1px solid var(--line);
}
.appbar h1 {
  flex: 1; text-align: center; margin: 0;
  font-size: 16px; font-weight: 600;
  /* Three more buttons landed on the right (notif, mail, refresh) than sit on
     the left (menu alone), so the centred title now floats in a narrower,
     off-centre space. Wrapping a long one to a second line inside a sticky
     bar would resize the bar on every tab change; clipping it is the
     option that keeps the bar's height constant. */
  min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.appbar-btn {
  width: 44px; min-height: 44px; margin: 0;
  background: none; border: none; color: var(--text);
  font-size: 19px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  position: relative; /* anchors .badge-dot */
}

/* Small enough to sit on a 44px icon button without covering it, capped at
   two characters ("9+") so a real count never forces the circle oval. */
.badge-dot {
  position: absolute; top: 4px; right: 4px;
  min-width: 16px; height: 16px; padding: 0 3px;
  border-radius: 999px; background: var(--danger); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
  font-variant-numeric: tabular-nums;
  border: 2px solid var(--card); /* separates the dot from the icon underneath */
}

/* Three bars drawn rather than shipped as an icon font. */
.burger, .burger::before, .burger::after {
  display: block; width: 19px; height: 2px;
  background: var(--text); border-radius: 2px;
}
.burger { position: relative; }
.burger::before, .burger::after { content: ''; position: absolute; left: 0; }
.burger::before { top: -6px; }
.burger::after { top: 6px; }

/* --- drawer ------------------------------------------------------------- */
.scrim {
  position: fixed; inset: 0; z-index: 14;
  background: rgba(17, 24, 39, .45);
}

.drawer {
  position: fixed; top: 0; bottom: 0; left: 0; z-index: 15;
  width: 82vw; max-width: 320px;
  background: var(--card);
  border-right: 1px solid var(--line);
  padding: calc(18px + env(safe-area-inset-top)) 0 calc(18px + env(safe-area-inset-bottom));
  overflow-y: auto;
  transform: translateX(-102%);
  transition: transform .22s ease;
}
.drawer.open { transform: translateX(0); }

/* A drawer that animates in from nowhere on a slow phone looks broken, so it
   is always mounted and only slid out of view. */
@media (prefers-reduced-motion: reduce) { .drawer { transition: none; } }

.drawer-head {
  display: flex; align-items: center; gap: 12px;
  padding: 0 18px 16px; margin-bottom: 8px;
  border-bottom: 1px solid var(--line);
}
.drawer-id { min-width: 0; }
.drawer-id strong { display: block; font-size: 15px; overflow-wrap: anywhere; }

.drawer-section {
  margin: 14px 18px 6px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.drawer-nav ul { list-style: none; margin: 0; padding: 0; }
.drawer-nav li { margin: 0; }
.drawer-nav a,
.drawer-nav button {
  display: block; width: 100%; text-align: left;
  padding: 12px 18px; min-height: 44px; margin: 0;
  background: none; border: none; border-radius: 0;
  color: var(--text); font-size: 14px; text-decoration: none;
}
.drawer-nav a:hover, .drawer-nav button:hover { background: var(--bg); }
.drawer-nav .danger { color: var(--danger); }

/* The entry whose screen is currently open. The bar on the left carries the
   meaning for anyone who cannot separate the tint from the plain rows. */
.drawer-nav .active {
  background: var(--bg); color: var(--brand); font-weight: 600;
  box-shadow: inset 3px 0 0 var(--brand);
}
.drawer-nav .active .bi { color: var(--brand); }

/* Planned entries are shown, not hidden: a menu that quietly lacks half the
   product is more confusing than one that says what is coming. */
.drawer-nav li.soon button {
  color: var(--muted); cursor: default;
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
}
.drawer-nav li.soon button:hover { background: none; }
.soon-tag {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  border: 1px solid var(--line); border-radius: 999px; padding: 2px 7px;
  color: var(--muted); white-space: nowrap;
}

.drawer-foot { padding: 16px 18px 0; margin: 8px 0 0; border-top: 1px solid var(--line); }

/* --- tabs --------------------------------------------------------------- */
#tabs { flex: 1; }
.tab {
  max-width: 460px; margin: 0 auto; width: 100%;
  padding: 14px 18px calc(90px + env(safe-area-inset-bottom));
}

.avatar {
  width: 42px; height: 42px; border-radius: 50%;
  background: var(--brand); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 15px; flex: none;
  position: relative; overflow: hidden;
}
.avatar-lg { width: 52px; height: 52px; font-size: 17px; }

/* The photo covers the initials rather than replacing them, so a picture that
   fails to load leaves the initials showing instead of an empty circle. */
.avatar img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .18s ease;
}
.avatar.has-photo img { opacity: 1; }

/* Still used by the Akun tab, which has a whole screen to spend and no punch
   button to protect. Only the HOME greeting became a bare row. */
.greeting { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.greeting h2 { margin: 2px 0; font-size: 18px; }

/* One row, no card. See the comment in index.html for why the card had to go.
   The whole row is the button — people tap their own face expecting Profil,
   and the name next to it is not a separate thought. */
.hello {
  display: flex; align-items: center; gap: 10px;
  width: 100%; margin: 0 0 12px; padding: 2px;
  background: none; border: none; color: inherit; text-align: left;
  min-height: 0;
}
.hello-text { flex: 1; min-width: 0; }
.hello-name { font-size: 15px; font-weight: 600; }
/* Ellipsis, never wrap. A name that wrapped to two lines is precisely what
   pushed the punch button off the bottom of the screen. */
.hello-name, .hello-text > .small { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hello-chev { width: 13px; height: 13px; color: var(--muted); flex: none; }

/* A group of label/value lines is ONE card with hairline separators, not one
   card per line. Eight settings as eight floating boxes reads as eight
   unrelated things and wastes a third of the screen on gaps; grouped, the
   heading above the card is what says where one section ends. */
.rows {
  list-style: none; padding: 0; margin: 0 0 18px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; overflow: hidden;
}
/* An empty list still has a border, which shows as a stray hairline box while
   the values are still loading. */
.rows:empty { display: none; }
.rows li {
  position: relative;
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 13px 16px;
  font-size: 14px;
}
/* Inset separator — it starts where the label starts, so the eye follows one
   column instead of being cut across. */
.rows li + li::before {
  content: ''; position: absolute; top: 0; left: 16px; right: 0;
  height: 1px; background: var(--line);
}
.rows li span { color: var(--muted); }
.rows li strong { text-align: right; overflow-wrap: anywhere; }

/* --- home widget preferences -------------------------------------------- */
#widget-list li span { color: var(--text); display: flex; flex-direction: column; gap: 2px; }
.wfixed { color: var(--muted); }
.wtoggle {
  width: 20px; height: 20px; flex: none; margin: 0;
  accent-color: var(--brand);
}
/* Locked, not broken. The default disabled styling reads as "this control has
   failed"; half opacity plus the words beside it read as "already decided". */
.wtoggle:disabled { opacity: .55; cursor: default; }

.empty { text-align: center; padding: 34px 20px; }
.empty h2 { margin: 0 0 8px; font-size: 18px; }

/* --- sync marker --------------------------------------------------------
   Italic = recorded on the handset, not yet accepted by the server. Normal =
   the server holds it. Applied by markSync() in sync-utils.js wherever a punch
   time is shown: the home grid, the week strip, Rincian Hari and the Absensi
   list — one rule, four surfaces.

   Typography rather than colour, and nothing else — no badge, no second line.
   A punch that has not uploaded yet is still a real punch, and dressing it as a
   warning would say something we do not mean. Deliberately global: a marker
   scoped per screen is a marker that ends up meaning something different on
   each of them.

   The PWA is online-only, so in practice nothing is italic here today. It is
   wired anyway because the Flutter app has the local queue and the port copies
   this. What guards it from rotting meanwhile is test/sync-utils.test.js. */
.unsynced { font-style: italic; }

/* --- seven-day strip ---------------------------------------------------- */
.weekstrip {
  display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px;
  margin-bottom: 16px;
  /* Snap so a half-visible day never looks like the current one. */
  scroll-snap-type: x proximity;
}
/* Each day is a button — tapping opens what was actually recorded. Wider than
   it was because it now carries a donut: the strip used to say only WHEN
   someone arrived, which is the least interesting half of a working day. */
.day {
  flex: 0 0 auto; width: 78px; scroll-snap-align: start;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 8px 5px 7px; text-align: center;
  margin: 0; min-height: 0; color: inherit; display: block;
}
.day:active { background: var(--bg); }
.day.today { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand) inset; }
.day .dow { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0; }
.day .num { font-size: 15px; font-weight: 700; margin: 1px 0 0; }
.day .daytime {
  font-size: 10px; color: var(--muted); margin: 2px 0 0;
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The arrival time carries the VERDICT; the ring carries the record status.
   Two questions, two channels, so neither has to be read through the other. */
.day .daytime.ok    { color: var(--verdict-ok); }
.day .daytime.short { color: var(--verdict-short); }

/* Ring length = how much of the rostered day was actually covered.
   Ring colour = the same record status the dots and the legend already use. */
.ring { width: 42px; height: 42px; display: block; margin: 3px auto 0; }
.ring .track { fill: none; stroke: var(--line); stroke-width: 3.5; }
/* Status without a duration: the ring is tinted rather than filled, because a
   filled fraction would be a measurement we do not have. */
.ring .track.present { stroke: var(--st-present); opacity: .38; }
.ring .track.partial { stroke: var(--st-partial); opacity: .38; }
.ring .track.none    { stroke: var(--st-none);    opacity: .38; }
.ring .track.holiday { stroke: var(--st-holiday); opacity: .38; }
.ring .arc {
  fill: none; stroke-width: 3.5; stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 50% 50%;
  stroke: var(--line);
}
.ring .arc.present { stroke: var(--st-present); }
.ring .arc.partial { stroke: var(--st-partial); }
.ring .arc.none    { stroke: var(--st-none); }
.ring .arc.holiday { stroke: var(--st-holiday); }
.ring .lbl {
  font-size: 9.5px; font-weight: 700; fill: var(--text);
  font-variant-numeric: tabular-nums;
}

/* The MORE affordance at the end of the scroll. A horizontal strip hides its
   own end, so the way out has to be the thing you reach by scrolling. */
.day-more {
  flex: 0 0 auto; width: 78px; scroll-snap-align: start;
  background: none; border: 1px dashed var(--line); border-radius: 12px;
  margin: 0; min-height: 0; padding: 8px 5px; color: var(--muted);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  font-size: 11px;
}
.day-more:active { background: var(--bg); }
.day-more .bi { width: 15px; height: 15px; }

.dot { width: 8px; height: 8px; border-radius: 50%; margin: 0 auto; background: var(--line); display: block; }
.dot.present { background: var(--st-present); }
.dot.partial { background: var(--st-partial); }
.dot.none    { background: var(--st-none); }
/* Slate, not orange. A rostered day off is not a problem, and next to the red
   "no record" dot an orange one was near-indistinguishable anyway. */
.dot.holiday { background: var(--st-holiday); }
/* A day that has not happened yet is not an absence. */
.dot.future  { background: transparent; }

/* ---- weekend and holiday tones -----------------------------------------
   Applied to day cells in the strip, the Absensi list and the calendar, so
   one rule set keeps all three consistent. A holiday wins over the weekend
   colours: a public holiday on a Tuesday must not look like a working day.

   Each selector is doubled up (.day.sunday, not .sunday) on purpose. The
   plain class alone loses to `.day { background: var(--card) }` and
   `.calcell { background: var(--card) }` — same specificity, and those come
   later in the file, so source order silently wiped every tint. */
.day.sunday, .calcell.sunday, .daylist li.sunday           { background: var(--tint-sun); }
.day.saturday, .calcell.saturday, .daylist li.saturday     { background: var(--tint-sat); }
.day.nationalday, .calcell.nationalday, .daylist li.nationalday { background: var(--tint-nat); }
.day.massleave, .calcell.massleave, .daylist li.massleave  { background: var(--tint-mass); }

.sunday .num, .sunday .caldate, .sunday .dayrow-date strong { color: var(--fg-sun); }
.saturday .num, .saturday .caldate, .saturday .dayrow-date strong,
.saturday .dow, .saturday .dayrow-date span { color: var(--fg-sat); }
/* Holidays come from the tenant's own Holiday table — national days and
   cuti bersama are set there and are deliberately distinguishable. */
.nationalday .num, .nationalday .caldate, .nationalday .dayrow-date strong { color: var(--fg-nat); }
.massleave .num, .massleave .caldate, .massleave .dayrow-date strong { color: var(--fg-mass); }

/* Four colours with no key is a puzzle, not a summary. */
.legend {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  margin: -8px 0 18px; font-size: 11px; color: var(--muted);
}
.legend span { display: flex; align-items: center; gap: 5px; }
.legend .dot { margin: 0; flex: none; }

/* Label/value rows inside the day-detail sheet. */
.actions li.kv {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  font-size: 14px; padding: 12px 15px;
}

/* --- avatar as a link into Profil ---------------------------------------- */
.avatar.tappable { cursor: pointer; }
.avatar.tappable:active { filter: brightness(.92); }
.avatar.tappable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.avatar-xl { width: 76px; height: 76px; font-size: 24px; }

.profile-head { text-align: center; }
.profile-head .avatar { margin: 2px auto 12px; }
.profile-head h2 { margin: 0 0 2px; font-size: 18px; }

/* --- device registration row --------------------------------------------- */
.devreg {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; text-align: left; margin: 0 0 4px; min-height: 0; color: inherit;
  border-left: 3px solid var(--line);
}
.devreg > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.devreg strong { font-size: 15px; }
.devreg .bi { flex: none; color: var(--muted); }
.devreg:disabled { opacity: .75; }
.devreg.ok   { border-left-color: #16a34a; }
.devreg.wait { border-left-color: #e0a800; }
.devreg.bad  { border-left-color: var(--danger); }
/* why this browser cannot keep its device id — shown only when it applies */
.dev-risk {
  margin: 8px 0 0; padding: 10px 12px; font-size: 13px; line-height: 1.45;
  color: var(--warn); border-left: 3px solid var(--warn); background: rgba(251, 191, 36, .10);
  border-radius: 0 8px 8px 0;
}

/* A confirmation, not a failure — same slot, different meaning. */
.error.as-note { color: var(--muted); }

.danger-solid { background: var(--danger); color: #fff; }

/* --- section headers with a trailing link -------------------------------- */
.section-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
.section-row .section { flex: 1; }
.see-more {
  display: inline-flex; align-items: center; gap: 3px;
  background: none; border: 0; padding: 0; margin: 0; min-height: 0; width: auto;
  color: var(--brand); font-size: 12px; font-weight: 600; cursor: pointer;
  white-space: nowrap;
}
.see-more .bi { width: 12px; height: 12px; }

/* --- collapsible sections ------------------------------------------------ */
.fold { margin: 0; }
.fold > summary {
  display: flex; align-items: baseline; gap: 10px;
  list-style: none; cursor: pointer;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary .section { flex: 1; margin: 26px 0 10px; }
.fold > summary .see-more { align-self: center; }
.fold > summary .chev {
  width: 13px; height: 13px; color: var(--muted); flex: none;
  transform: rotate(90deg); transition: transform .18s ease; align-self: center;
}
.fold[open] > summary .chev { transform: rotate(-90deg); }
.fold > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* --- month navigator ----------------------------------------------------- */
.monthnav {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 14px;
}
.monthnav h2 { margin: 0; font-size: 16px; text-align: center; flex: 1; }
.bi.flip { transform: rotate(180deg); }

/* --- Absensi recap ------------------------------------------------------- */
.recap {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
  margin-bottom: 16px;
}
.recap > div {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 10px 4px; text-align: center;
}
.recap .dot { margin: 0 auto 5px; }
.recap .n { font-size: 19px; font-weight: 700; margin: 0; }
.recap p.small { margin: 1px 0 0; font-size: 10px; line-height: 1.25; }

/* --- Absensi day list ---------------------------------------------------- */
.daylist { list-style: none; padding: 0; margin: 0 0 18px; }
.daylist li {
  border: 1px solid var(--line); border-radius: 12px;
  margin-bottom: 8px; overflow: hidden; background: var(--card);
}
/* The tone classes paint the row, so the button itself stays transparent. */
.daylist li.sunday, .daylist li.saturday,
.daylist li.nationalday, .daylist li.massleave { background: inherit; }
.daylist li > button {
  display: flex; align-items: center; gap: 12px; width: 100%;
  background: none; border: 0; margin: 0; padding: 11px 14px;
  min-height: 0; color: inherit; text-align: left;
}
.daylist li > button:active { background: rgba(127, 127, 127, .08); }
.dayrow-date {
  display: flex; flex-direction: column; align-items: center;
  width: 34px; flex: none; line-height: 1.15;
}
.dayrow-date strong { font-size: 17px; font-variant-numeric: tabular-nums; }
.dayrow-date span { color: var(--muted); font-size: 10px; text-transform: uppercase; }
.dayrow-body { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.dayrow-body strong { font-size: 14px; }
.dayrow-body span { margin-top: 1px; }
.daylist .dot { flex: none; }

/* --- calendar grid ------------------------------------------------------- */
.calhead, .calgrid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
}
.calhead {
  margin-bottom: 6px; font-size: 10px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); text-align: center;
}
.calhead .sun { color: #c2185b; }
.calhead .sat { color: #8a93a2; }

.calgrid { margin-bottom: 14px; }
.calcell {
  aspect-ratio: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px;
  background: var(--card); border: 1px solid var(--line); border-radius: 9px;
  margin: 0; padding: 0; min-height: 0; color: inherit;
}
.calcell.blank { background: none; border-color: transparent; }
.calcell.today { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand) inset; }
.calcell:active { filter: brightness(.95); }
.caldate { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.calcell .dot { width: 6px; height: 6px; }

/* --- leave balance -------------------------------------------------------
   One row, four columns, two lines each. It used to be a 2x2 grid with a
   period caption above it, which took a third of the first screen to say four
   numbers. The period moved into the fold's own summary line, where it costs
   nothing. */
button.leave {
  display: block; width: 100%; text-align: inherit; font: inherit;
  color: inherit; cursor: pointer; padding: 14px 10px;
}
button.leave:active { filter: brightness(.97); }
button.leave:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.leave-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
               align-items: start; }
.leave-grid > span { display: block; text-align: center; min-width: 0; }
.leave-grid .n {
  display: block; font-size: 20px; font-weight: 700; line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
/* Four labels across a 360px screen leaves ~80px each: they wrap rather than
   ellipsize, because "Sedang" tells nobody which number it belongs to. */
.leave-grid .l {
  display: block; font-size: 11px; line-height: 1.25; color: var(--muted);
  margin-top: 2px; overflow-wrap: anywhere;
}
.leave-grid .n-ok   { color: var(--ok); }
.leave-grid .n-wait { color: var(--warn); }
.leave-grid .n-zero { color: var(--muted); }

/* The annual period, shown next to the heading instead of above the numbers. */
.fold > summary .fold-note {
  align-self: center; font-size: 11px; color: var(--muted);
  margin-right: 8px; white-space: nowrap;
}

/* --- leave summary on the request page ---------------------------------- */
.leave-sum { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.leave-sum > div {
  text-align: center; padding: 12px 6px; border-radius: 12px;
  border: 1px solid var(--line);
}
.leave-sum .n {
  display: block; font-size: 26px; font-weight: 700; line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.leave-sum .l { display: block; font-size: 12px; margin-top: 4px; }
.leave-sum .u { display: block; font-size: 11px; color: var(--muted); margin-top: 2px; }

/* Tints, not fills: the number carries the colour and the panel only hints at
   it, so three coloured blocks in a row stay readable in both themes. */
.sum-left  { background: var(--tint-ok);   border-color: var(--tint-ok-line); }
.sum-left .n  { color: var(--ok); }
.sum-wait  { background: var(--tint-wait); border-color: var(--tint-wait-line); }
.sum-wait .n  { color: var(--warn); }
.sum-total { background: var(--tint-brand); border-color: var(--tint-brand-line); }
.sum-total .n { color: var(--brand); }

.sum-left.is-zero { background: var(--tint-none); border-color: var(--line); }
.sum-left.is-zero .n { color: var(--muted); }

.leave-sum-note { margin: 10px 0 0; }
.leave-sum-note.bad { color: var(--danger); }

/* --- announcements ------------------------------------------------------ */
.ann { background: var(--card); border: 1px solid var(--line);
       border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; }
.ann h3 { margin: 0 0 4px; font-size: 14px; }
.ann p { margin: 0; font-size: 13px; color: var(--muted); }

/* --- news --------------------------------------------------------------- */
/* A news card is a <button>: the whole card opens the article, and a real
   button gets keyboard and screen-reader behaviour without any aria plumbing. */
.newscard {
  display: block; width: 100%; text-align: left;
  min-height: 0; margin-top: 0; color: inherit;
}
.newscard:active { background: var(--bg); }
.newscard h3 { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Unread is a left edge, not a dot floating in the corner: it survives a long
   title wrapping to three lines, which a corner marker does not. */
.newscard.unread { border-left: 3px solid var(--brand); }
.newsexcerpt {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; margin-top: 6px !important;
}
.chip {
  font-size: 10px; font-weight: 600; letter-spacing: .03em;
  padding: 2px 7px; border-radius: 999px;
  background: var(--tint-brand); color: var(--brand);
  border: 1px solid var(--tint-brand-line);
}
.newsitem h2 { margin: 0 0 6px; font-size: 18px; }
.newsitem img { width: 100%; height: auto; border-radius: 10px; margin: 12px 0; }

/* --- bottom nav --------------------------------------------------------- */
.bottomnav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 12;
  display: flex;
  background: var(--card);
  border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
}
.bottomnav button {
  flex: 1; margin: 0; border: none; border-radius: 0;
  background: none; color: var(--muted);
  min-height: 58px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  font-size: 11px;
}
.bottomnav button .ico { font-size: 18px; line-height: 1; }
.bottomnav button.active { color: var(--brand); font-weight: 600; }

.build-stamp { text-align: center; margin-top: 18px; }

/* --- Bootstrap Icons ----------------------------------------------------- */
.bi { width: 20px; height: 20px; fill: currentColor; display: block; }
.bi-lg { width: 24px; height: 24px; }
.bottomnav .bi { width: 21px; height: 21px; }
.drawer-nav .bi { width: 18px; height: 18px; flex: none; }
.drawer-nav a, .drawer-nav button { display: flex; align-items: center; gap: 12px; }
.drawer-nav li.soon button { justify-content: flex-start; }
.drawer-nav li.soon .soon-tag { margin-left: auto; }

/* --- device status ------------------------------------------------------- */
.status-card { text-align: center; padding: 26px 20px; }
.status-card h2 { margin: 12px 0 4px; font-size: 19px; }
.status-dot {
  width: 56px; height: 56px; border-radius: 50%; margin: 0 auto;
  background: var(--brand);
  /* A ring rather than a flat disc: it reads as a live indicator without
     needing a colour the user has to decode. */
  box-shadow: 0 0 0 8px rgba(30, 99, 208, .15);
}
.status-dot.off { background: #94a3b8; box-shadow: 0 0 0 8px rgba(148, 163, 184, .18); }

/* --- location control + map sheet ---------------------------------------- */
.location-btn {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: auto; margin: 14px auto 0; min-height: 36px;
  background: none; border: none; color: var(--muted); font-size: 12px;
}
.location-btn.ready { color: var(--brand); }
.location-btn .bi { width: 15px; height: 15px; flex: none; }

/* One line with an icon, not three lines of prose. The sentence explaining
   that the camera opens on tap cost 50px to say something the user discovers
   half a second later anyway. */
.photo-hint {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 8px 0 0;
}
.photo-hint .bi { width: 13px; height: 13px; flex: none; }

/* The wall clock lives in the button now, so it costs no row of its own. */
.punch { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 52px; }
.punch-time { font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; }
.punch-sep { width: 1px; height: 17px; background: currentColor; opacity: .4; flex: none; }
.punch .btn-label { font-size: 15px; }

/* Mode absen 2 — Masuk, Istirahat Keluar, Istirahat Masuk, Pulang sebagai
   tombol terpisah. Dua kolom, karena empat tombol dalam satu baris di layar
   360px menyisakan label yang tidak terbaca, dan satu kolom mendorong Pulang
   ke bawah lipatan — persis keluhan yang membuat beranda ini dirombak. */
.clocksteps {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px;
}
.clockstep {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 48px; padding: 6px 8px;
  border-radius: 12px; border: 1px solid var(--line);
  background: var(--card); color: var(--fg);
  font-size: 14px; font-weight: 600; text-align: center;
}
/* Langkah berikutnya yang wajar diberi bobot tombol utama; sisanya tidak.
   Pada mode 2 lebih dari satu bisa aktif (Istirahat dan Pulang sama-sama boleh
   ditekan siang hari), jadi ini bukan penanda "satu-satunya pilihan" — hanya
   penanda "boleh ditekan sekarang". */
.clockstep.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* Yang sudah terjadi TIDAK disembunyikan. Tombol yang lenyap membuat orang
   mengira ia salah lihat; yang berubah jadi jam yang tercatat menjawab
   pertanyaannya sekaligus. */
.clockstep.done {
  background: transparent; color: var(--muted); border-style: dashed;
}
.clockstep[disabled] { opacity: 1; }
.clockstep .step-time {
  font-variant-numeric: tabular-nums; font-weight: 700;
}

/* A recorded punch is GREEN and bold. It used to render through the red
   .error box because the client misread the tenant's 202 as a failure, so
   "Success" arrived in the colour of a problem. */
.punch-result {
  margin: 12px 0 0; font-size: 13px; color: var(--ok); font-weight: 700;
}

.see-all {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  width: auto; margin: 2px auto 0; min-height: 40px;
  background: none; border: none; color: var(--brand); font-size: 13px; font-weight: 600;
}
.see-all .bi { width: 14px; height: 14px; }

.sheet {
  position: fixed; inset: 0; z-index: 16;
  background: var(--bg);
  display: flex; flex-direction: column;
  padding-top: env(safe-area-inset-top);
}
.sheet-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 8px 10px 18px; border-bottom: 1px solid var(--line);
  background: var(--card);
}
.sheet-head h2 { margin: 0; font-size: 16px; }
.sheet-body { flex: 1; display: flex; flex-direction: column; padding: 14px 18px 24px; gap: 10px; }
.sheet-body iframe { flex: 1; width: 100%; min-height: 260px; border: 1px solid var(--line); border-radius: 12px; }
.map-open {
  display: block; text-align: center; text-decoration: none;
  padding: 13px; border: 1px solid var(--line); border-radius: 10px; color: var(--text);
}

/* --- punch sheet (camera) ------------------------------------------------ */
.punch-body { gap: 12px; }

/* Fixed 3:4 portrait box so the layout does not jump when the stream starts
   and the video finally reports its real dimensions. */
.camwrap {
  position: relative; width: 100%; aspect-ratio: 3 / 4; max-height: 56vh;
  border-radius: 14px; overflow: hidden; background: #000;
  display: flex; align-items: center; justify-content: center;
}
/* STACKED, not side by side. This is the fix for the long-standing "foto
   terpotong" report: the preview and the captured still are two children of a
   flex container, and the video is hidden with `visibility: hidden` — which
   keeps it IN the layout. So the dead video kept its half of the flex row and
   squeezed the photo into the other half, producing a black band down the left
   of every confirmation screen.
   Taking both out of flow makes them occupy the same box, which is what
   "preview, then the still that replaces it" actually means. `visibility`
   then hides the video for real.
   The photo that was sent to HRD was never affected — that comes off the
   canvas, which is sized from the video's own dimensions. Only this preview
   ever looked broken. */
.camwrap video, .camwrap img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Selfie cameras are mirrored on every other app; an unmirrored preview reads
   as broken. The captured frame is mirrored to match, so what you saw is what
   is sent. */
.camwrap video { transform: scaleX(-1); }
.camwrap.is-off video { visibility: hidden; }
.cam-hint {
  position: absolute; margin: 0; padding: 0 18px; text-align: center;
  font-size: 13px; color: #e6e8ec;
}
.camwrap.is-live .cam-hint, .camwrap.is-shot .cam-hint { display: none; }

.punch-line { margin: 0; font-size: 13px; color: var(--muted); }
.punch-line.ready { color: var(--ok); }
.punch-line.warn  { color: var(--warn); }

.punch-geo-hint {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 12px; color: var(--warn);
}
.punch-geo-hint .link {
  width: auto; min-height: 28px; margin: 0; padding: 0; flex: none;
  font-size: 12px; font-weight: 600;
}

/* Small, non-interactive preview — the iframe is decorative (pointer-events
   none) so the surrounding element is the one clickable target, opening the
   full map sheet on tap. */
.punch-map {
  position: relative; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--line); cursor: pointer; background: var(--tint-none, #eee);
  flex: none;
}
/* `min-height` and `flex` are RESET here on purpose.
   `.sheet-body iframe` sets `flex: 1; min-height: 260px` for the full-screen
   map sheet, and it caught this mini-map too — same specificity, and min-height
   beats height, so this 110px strip was really rendering at 260px. Those extra
   150px pushed the shutter row clean off the bottom of an iPhone, and
   .sheet-body had no overflow, so it could not even be scrolled to. People
   opened the camera and found no way to take the photo. */
.punch-map iframe {
  width: 100%; height: 110px; min-height: 0; flex: none;
  display: block; border: 0; pointer-events: none;
}
.punch-map .coords {
  position: absolute; left: 8px; bottom: 8px; padding: 3px 8px;
  border-radius: 6px; background: rgba(0, 0, 0, .6); color: #fff; font-size: 11px;
}

/* What to do about a refused permission. Long by necessity — "izinkan kamera"
   is not actionable once the browser has already been told no, because the
   prompt never appears again. */
.punch-help {
  background: var(--tint-wait); border: 1px solid var(--tint-wait-line);
  border-radius: 12px; padding: 12px 14px; font-size: 12.5px; line-height: 1.5;
}
.punch-help h3 { margin: 0 0 6px; font-size: 13px; }
.punch-help ol, .punch-help ul { margin: 6px 0 0; padding-left: 18px; }
.punch-help li { margin-bottom: 4px; }
.punch-help p { margin: 6px 0 0; }

/* Pinned to the bottom of the sheet, not merely pushed there by `margin-top:
   auto`. Belt and braces after the min-height bug above: whatever ends up in
   this sheet in future — a taller map, an extra warning, a second hint line —
   the shutter stays on screen. A camera you cannot fire is a broken app, so
   this row is the one thing that must never depend on the height of the rest. */
.punch-actions {
  margin-top: auto; padding-top: 8px;
  position: sticky; bottom: 0; z-index: 2;
  background: var(--bg);
}
.punch-actions button { margin-top: 10px; }
.punch-actions button:first-child { margin-top: 0; }

/* The safety net under the safety net: if content still exceeds the sheet, it
   scrolls instead of being silently unreachable. */
.sheet-body { overflow-y: auto; }

/* Tapping the preview fires the shutter, the way every camera app behaves.
   Only while the stream is live — on a still, the tap would mean nothing. */
.camwrap.is-live { cursor: pointer; }

.map-toggle {
  align-self: flex-start; width: auto; min-height: 0; margin: 0;
  padding: 4px 0; background: none; border: none;
  color: var(--brand); font-size: 12px;
  display: flex; align-items: center; gap: 6px;
}
.map-toggle .bi { width: 13px; height: 13px; }

/* --- punch log on Rincian Hari ------------------------------------------- */
.punchlog { list-style: none; margin: 0 0 16px; padding: 0; }
.punchlog li {
  display: flex; gap: 12px; align-items: center;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 10px 12px; margin-bottom: 8px;
}
.punchlog .thumb {
  width: 54px; height: 54px; flex: 0 0 54px; border-radius: 10px;
  overflow: hidden; background: var(--bg); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center; padding: 0;
  margin: 0; min-height: 0;
}
.punchlog .thumb img { width: 100%; height: 100%; object-fit: cover; }
.punchlog .thumb .bi { width: 20px; height: 20px; color: var(--muted); }
.punchlog .body { flex: 1; min-width: 0; }
.punchlog .body strong { display: block; font-size: 14px; }
.punchlog .body .addr {
  display: block; font-size: 12px; color: var(--muted); margin-top: 2px;
  overflow-wrap: anywhere;
}
.punchlog .pin {
  width: 40px; flex: 0 0 40px; min-height: 40px; margin: 0; padding: 0;
  background: none; border: 1px solid var(--line); border-radius: 10px;
  color: var(--brand); display: flex; align-items: center; justify-content: center;
}
.punchlog .pin:disabled { color: var(--muted); }
.punchlog .pin .bi { width: 18px; height: 18px; }

.pic-full {
  width: 100%; max-height: 72vh; object-fit: contain;
  border-radius: 12px; background: var(--bg); border: 1px solid var(--line);
}

/* --- PIN ----------------------------------------------------------------- */
.pin-avatar { margin: 0 auto 12px; }

.pindots {
  display: flex; gap: 14px; justify-content: center; margin: 22px 0 4px;
}
.pindots i {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--line); background: transparent;
  transition: background .12s ease, border-color .12s ease;
}
.pindots i.on { background: var(--brand); border-color: var(--brand); }
.pin-error { text-align: center; }

/* An on-screen keypad rather than a text input: iOS shows a full keyboard for
   numeric inputs in some locales, which covers the dots the user is watching. */
.keypad {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  max-width: 280px; margin: 22px auto 0;
}
.keypad button {
  margin: 0; min-height: 62px;
  background: var(--card); border: 1px solid var(--line);
  color: var(--text); font-size: 22px; font-weight: 600;
}
.keypad button.wide { font-size: 15px; font-weight: 500; }
.keypad button:active { background: var(--bg); }
.keypad button.blank { visibility: hidden; }

.pin-setup { align-items: stretch; }

/* Two escapes from the lock screen, side by side. Neither is more correct than
   the other: one is for the person who knows their password, the other for the
   person who does not yet know that is all they need. */
.pin-links {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: 4px;
}
/* width:auto — the base button rule stretches to full width, which would push
   the two links to opposite edges and strand the separator in between. */
.pin-links .link { width: auto; min-height: 44px; padding: 0 8px; margin: 0; }
.link-sep { color: var(--muted); font-size: 12px; line-height: 1; }

#pin-ident, #pin-company { margin: 2px 0 0; text-align: center; }
#pin-company { margin-bottom: 4px; }

/* --- forgot PIN ---------------------------------------------------------- */
.steps { list-style: none; padding: 0; margin: 0 0 8px; }
.step { text-align: left; margin-bottom: 12px; }
.step h2 { margin: 0 0 6px; font-size: 15px; }
.step p { margin: 0 0 8px; line-height: 1.55; }
.step .primary, .step .secondary { margin-top: 4px; }

.bullets { padding-left: 20px; margin: 0 0 10px; line-height: 1.6; }
.bullets li { margin-bottom: 3px; }

/* Not an error, not a success — a boundary of what this build can do. It reads
   as neither red nor green so it does not look like something went wrong. */
.note {
  background: var(--bg); border: 1px dashed var(--line);
  border-radius: 10px; padding: 10px 12px; margin: 4px 0 0;
  color: var(--muted); line-height: 1.5;
}

/* --- suggested actions --------------------------------------------------- */
.action-lead { margin: 0; line-height: 1.6; font-size: 14px; }

.actions { list-style: none; padding: 0; margin: 6px 0 0; }
.actions li {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 13px 15px; margin-bottom: 9px;
}
.actions li.primary-action { border-color: var(--brand); }
.action-head { display: flex; align-items: center; gap: 9px; }
.action-head strong { font-size: 14px; }
.action-head .bi { width: 16px; height: 16px; flex: none; color: var(--muted); }
.actions li.primary-action .action-head .bi { color: var(--brand); }
.action-head .soon-tag { margin-left: auto; }
.actions li p { margin: 6px 0 0; line-height: 1.5; }

/* Baris yang bisa ditekan versus baris yang sengaja mati. Keduanya tetap
   terlihat: yang mati membawa alasannya di paragrafnya sendiri, karena
   menyembunyikannya membuat orang mencari tombol yang mereka ingat ada. */
.actions li.tappable { cursor: pointer; }
.actions li.tappable:hover { border-color: var(--brand); }
.actions li.tappable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.actions li.is-off { opacity: .55; }
.actions li.is-off .action-head strong { text-decoration: line-through; }

/* --- tanggal yang sudah terisi -------------------------------------------
   Menggantikan pemilih tanggal ketika formulir dibuka dari hari tertentu.
   Warnanya memakai tint yang sama dengan kalender dan strip mingguan, supaya
   "merah berarti libur" berarti hal yang sama di seluruh aplikasi. */
.datechip {
  display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--line); border-radius: 12px;
  padding: 10px 13px; background: var(--card);
}
.datechip.sunday     { background: var(--tint-sun);  border-color: var(--tint-sun); }
.datechip.saturday   { background: var(--tint-sat);  border-color: var(--tint-sat); }
.datechip.nationalday { background: var(--tint-nat); border-color: var(--tint-nat); }
.datechip.massleave  { background: var(--tint-mass); border-color: var(--tint-mass); }
.datechip-num { font-size: 30px; font-weight: 700; line-height: 1; }
.datechip.sunday .datechip-num      { color: var(--fg-sun); }
.datechip.saturday .datechip-num    { color: var(--fg-sat); }
.datechip.nationalday .datechip-num { color: var(--fg-nat); }
.datechip.massleave .datechip-num   { color: var(--fg-mass); }
.datechip-meta { display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }
.datechip-meta strong { font-size: 14px; }
.datechip-meta span { font-size: 12px; color: var(--muted); }
.datechip-meta em { font-size: 11px; font-style: normal; color: var(--fg-nat); }
.datechip-change {
  margin-left: auto; border: 0; background: none; padding: 4px;
  color: var(--brand); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}

/* --- pre-auth chrome ------------------------------------------------------
   A language switch at the top and, at the foot, the theme choice and the
   legal links. Both are one element shown across every pre-auth screen rather
   than copies inside each one — three language switches is three places for
   them to disagree about which language is selected. */
.auth-top {
  max-width: 460px; width: 100%; margin: 0 auto;
  padding: 10px 18px 0;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.lang-note { margin: 0; text-align: right; line-height: 1.45; }

.auth-bottom {
  max-width: 460px; width: 100%; margin: 0 auto;
  padding: 8px 18px calc(14px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  /* Sticky, not fixed. A fixed bar sits on top of the last field the moment
     the on-screen keyboard opens, which is exactly when the field matters. */
  position: sticky; bottom: 0;
  background: var(--bg);
  border-top: 1px solid var(--line);
}

/* Segmented control, used for both language and theme. */
.segmented {
  display: inline-flex; border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); overflow: hidden;
}
.segmented button {
  background: none; border: 0; margin: 0; width: auto;
  min-height: 32px; padding: 0 14px;
  font-size: 12px; font-weight: 600; color: var(--muted);
  cursor: pointer; white-space: nowrap;
}
.segmented button + button { border-left: 1px solid var(--line); }
.segmented button[aria-pressed="true"] { background: var(--brand); color: #fff; }
.segmented button:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

.themeswitch button { min-width: 74px; }

.legal-links {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 2px 4px;
}
.legal-links .link {
  width: auto; min-height: 32px; padding: 0 4px; margin: 0;
  font-size: 12px; display: inline-flex; align-items: center;
}

/* Two links under a form button, side by side. */
.link-row {
  display: flex; align-items: center; justify-content: center; gap: 2px;
  flex-wrap: wrap;
}
.link-row .link { width: auto; min-height: 44px; padding: 0 8px; margin: 0; }

/* Explanation of a field, inside the card and under the button — where
   someone who cannot answer the field is already looking. */
.field-help {
  margin-top: 18px; padding-top: 16px;
  border-top: 1px solid var(--line);
}
.field-help h2 { font-size: 13px; margin: 0 0 6px; }
.field-help p { margin: 0 0 8px; }
.field-help p:last-child { margin-bottom: 0; }

/* --- profile photo ------------------------------------------------------- */
.avatar-edit { position: relative; display: inline-block; margin: 0 auto; }
.avatar-pencil {
  position: absolute; right: -2px; bottom: -2px;
  width: 32px; height: 32px; min-height: 32px; margin: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--brand); color: #fff;
  border: 2px solid var(--card);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
.avatar-pencil .bi { width: 14px; height: 14px; }
.avatar-pencil:disabled { opacity: .6; }

/* A change that has been asked for but not granted. Neither an error nor a
   success — saying so plainly stops people submitting the same photo twice. */
.pending-note {
  margin: 10px 0 0; color: var(--muted);
  padding: 8px 12px; border: 1px dashed var(--line); border-radius: 10px;
}

/* The success box doubles as the failure box for one-line outcomes. */
.punch-result.as-error { color: var(--danger); }

/* Standing notice, not an error: something the company has switched on that
   this person needs to know about before they act. Amber rather than red —
   nothing has gone wrong yet. */
.notice {
  margin: 14px 0 0; padding: 12px 14px;
  border: 1px solid rgba(185, 119, 14, .35);
  border-radius: 12px;
  background: rgba(185, 119, 14, .08);
  color: var(--text);
  font-size: 13px; line-height: 1.5; text-align: left;
  cursor: pointer;
}

/* The request form reuses the input styling; textarea needs the same. */
textarea {
  width: 100%; padding: 13px 14px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg); color: var(--text);
  font: inherit; font-size: 16px;
  resize: vertical;
}
textarea:focus { outline: 2px solid var(--brand); outline-offset: 1px; }

/* ---------------- Pengajuan (cuti, lembur, izin, …) ----------------
   One stylesheet block serves every request type; see REQUEST_TYPES in
   app.js. Nothing here is leave-specific. */

select {
  width: 100%; padding: 13px 14px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg); color: var(--text);
  font: inherit; font-size: 16px;
}
select:focus { outline: 2px solid var(--brand); outline-offset: 1px; }

/* The label wrapper carries the app's bold form-label weight. The control and
   the hint inside it must opt back out, or a select's options and a hint read
   as headings. */
.field { display: block; margin: 0 0 4px; font-weight: 400; }
.field-label { display: block; font-size: 13px; font-weight: 600; margin: 12px 0 6px; }
.field-hint { display: block; font-size: 12px; font-weight: 400; color: var(--muted); margin: 6px 0 0; }
.field input, .field select, .field textarea { font-weight: 400; }

.svc-filter { margin: 0 0 12px; }
.svc-budget { margin: 2px 0 4px; font-size: 22px; font-weight: 700; color: var(--brand); }

.svc-list { list-style: none; margin: 0; padding: 0; }
.svc-item {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px; margin: 0 0 10px;
}
.svc-head { display: flex; align-items: center; gap: 8px; justify-content: space-between; }
.svc-head strong { font-size: 15px; }
.svc-detail { margin: 6px 0 0; font-size: 13px; color: var(--text); }
/* Withdrawing destroys the request and its history, so it reads as
   destructive rather than as one more grey link. */
.svc-item .link { width: auto; min-height: 32px; margin: 6px 0 0; padding: 0; text-align: left; }
.svc-item .link.danger { color: var(--danger); }

/* Status chips. Colour alone never carries the meaning — the word is always
   there too, for anyone who cannot separate these hues. */
.chip {
  flex: none; font-size: 11px; font-weight: 700; line-height: 1;
  padding: 5px 9px; border-radius: 999px; white-space: nowrap;
}
.chip-ok   { background: rgba(21, 128, 61, .12);  color: var(--ok); }
.chip-wait { background: rgba(30, 99, 208, .12);  color: var(--brand); }
.chip-bad  { background: rgba(220, 38, 38, .12);  color: var(--danger); }
.chip-off  { background: var(--bg);               color: var(--muted); }

.svc-actions { display: flex; gap: 16px; margin: 6px 0 0; }
.svc-actions .link { width: auto; min-height: 32px; margin: 0; padding: 0; text-align: left; }

/* --- Pengajuan: ringkasan periode ------------------------------------------
   Count + the type's own metric (jam/hari), then a status breakdown — the
   same shape for every request type, so switching between Izin Keluar and
   Lembur reads as the same screen with different numbers, not two screens
   that happen to be adjacent in the drawer. */
#svc-period-summary { padding: 12px 14px; }
.svcp-head { display: flex; align-items: baseline; justify-content: space-between; }
.svcp-count { font-size: 15px; font-weight: 700; }
.svcp-total { font-size: 15px; font-weight: 700; color: var(--brand); }
.svcp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0; }

/* --- Pengajuan: detail ------------------------------------------------------ */
.svcd-row { display: flex; justify-content: space-between; gap: 12px; margin: 10px 0 0; font-size: 13px; }
.svcd-row strong { text-align: right; }

.svcd-swap {
  display: flex; align-items: center; gap: 10px; margin: 14px 0 0;
  padding: 12px; border-radius: 12px; background: var(--bg);
}
.svcd-swap-side { flex: 1; min-width: 0; text-align: center; }
.svcd-swap-name { margin: 2px 0 0; font-size: 13px; font-weight: 700; overflow-wrap: break-word; }
.svcd-swap-arrow { flex: none; color: var(--muted); }
.svcd-swap-arrow .bi { width: 18px; height: 18px; }

.svcd-approvals { margin: 4px 0 0; }

.svcd-files { display: flex; flex-direction: column; gap: 6px; margin: 14px 0 0; }
.svcd-files a {
  display: flex; align-items: center; gap: 6px; font-size: 13px;
  color: var(--brand); text-decoration: none;
}
.svcd-files a .bi { width: 16px; height: 16px; }

/* --- notifications --------------------------------------------------------
   One list shared by the bell sheet; unread rows carry a left accent bar and
   a bold title, read ones fall back to plain text. Deleting is a swipe away
   in the mobile app; here it is a small button that only appears once you
   have actually looked at the row, so it cannot be brushed against by
   accident while scrolling. */
.notif-toolbar { display: flex; justify-content: flex-end; margin: 0 0 4px; }
.notif-toolbar .link { width: auto; min-height: 32px; margin: 0; padding: 0; }
.notif-toolbar .link .bi { width: 14px; height: 14px; margin-right: 4px; vertical-align: -2px; }

.notif-list { list-style: none; margin: 0; padding: 0; }
.notif-item {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--card); border: 1px solid var(--line);
  border-left: 3px solid transparent;
  border-radius: 12px; padding: 12px 14px; margin: 0 0 8px;
}
.notif-item.is-unread { border-left-color: var(--brand); }
.notif-item .dot {
  flex: none; width: 8px; height: 8px; margin-top: 6px;
  border-radius: 50%; background: var(--brand);
}
.notif-item .body { flex: 1; min-width: 0; }
.notif-item .body strong { display: block; font-size: 14px; }
.notif-item.is-unread .body strong { font-weight: 700; }
.notif-item:not(.is-unread) .body strong { font-weight: 500; color: var(--muted); }
.notif-item .body p { margin: 3px 0 0; font-size: 13px; color: var(--text); }
.notif-item .body time { display: block; margin-top: 4px; font-size: 11px; color: var(--muted); }
.notif-item .del {
  flex: none; width: 32px; min-height: 32px; margin: 0; padding: 0;
  background: none; border: none; color: var(--muted);
}
.notif-item .del .bi { width: 15px; height: 15px; }

/* --- Rincian Hari --------------------------------------------------------
   The day-kind accent (badge text and border) is whatever the API sends, so a
   date is never one colour on the calendar and another here. The BACKGROUND
   is deliberately not: those tints already exist as theme variables and follow
   light/dark, whereas a hard-coded #EC407A would stay pink on a dark screen. */
.day-head-row { display: flex; align-items: center; gap: 10px; }
.day-head-row strong { flex: 1; font-size: 15px; }

.day-badge {
  flex: none; font-size: 11px; font-weight: 700; line-height: 1;
  padding: 5px 9px; border-radius: 999px; white-space: nowrap;
  border: 1px solid currentColor;
}

.day-head.day-off    { background: var(--tint-sun); }
.day-head.day-nat    { background: var(--tint-nat); }
.day-head.day-mass   { background: var(--tint-mass); }
.day-head.day-change { background: var(--tint-sat); }

/* Green for a duration that matches the roster, red for a late arrival. The
   word beside it always says the same thing — see .chip. */
.day-ok  { color: var(--ok); }
.day-bad { color: var(--danger); }

/* A sentence about the day, not another row — .actions li would otherwise give
   it the same bordered box as Masuk/Pulang and it reads as missing data.
   The selector matches `.actions li` element-for-element on purpose: written
   as a bare `.day-note` it loses on specificity and the box comes back. */
.actions li.day-note {
  background: none; border: 0; border-radius: 0;
  padding: 4px 3px 0; margin: 0;
}

.day-item-head { display: flex; align-items: center; gap: 10px; }
.day-item-head strong { flex: 1; }

.day-reason {
  font-size: 13px; line-height: 1.5; margin: 0 0 10px;
  padding: 10px 12px; border-radius: 10px;
  background: var(--bg); color: var(--muted);
}
.day-reason.warn {
  background: rgba(220, 38, 38, .10); color: var(--danger); font-weight: 500;
}

#day-act button + button { margin-top: 8px; }

/* Three buttons across a narrow phone. At the inherited size the labels wrap
   and the row breaks, so the type is stepped down and the padding tightened
   rather than shortening the words — "Kemarin" and "Besok" say what the
   arrows alone do not. */
.day-nav { display: flex; gap: 8px; margin-top: 26px; }
.day-nav button {
  flex: 1; margin: 0; gap: 4px;
  font-size: 13px; padding: 0 8px; white-space: nowrap;
}
.day-nav button .bi { width: 14px; height: 14px; flex: none; }

/* --- change-request form -------------------------------------------------
   Seventeen boxes in one column give no clue that four of them describe one
   address. The heading is the grouping; no extra card chrome, which would push
   the fields further apart than the relationship between them warrants. */
.field-group {
  font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); font-weight: 700;
  margin: 22px 0 10px; padding-top: 16px;
  border-top: 1px solid var(--line);
}
.field-group:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* A box holding a value that has been asked for but not yet granted. */
input.is-pending, textarea.is-pending { border-color: var(--warn); }
.field-hint.pending { color: var(--warn); }

/* Waiting vs granted. The word is always there too — see .chip. */
.state-wait { color: var(--warn); }
.state-ok   { color: var(--ok); }

/* Visible to screen readers, not on screen. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- pengingat kata sandi ------------------------------------------------ */
.pwrem-later-label { margin-top: 6px; }
.pwrem-later { display: flex; flex-wrap: wrap; gap: 8px; }
.pwrem-later button {
  flex: 1 1 calc(50% - 8px);
  padding: 11px 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: transparent;
  color: var(--text);
  font: inherit;
}
.pwrem-later button:disabled { opacity: .5; }

/* --- Chat --- */
.chat-tile {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--line);
  cursor: pointer; transition: background 0.15s;
}
.chat-tile:hover { background: var(--bg-alt); }
.chat-tile.unread { background: #e8f4fd; }
.chat-avatar {
  width: 40px; height: 40px; border-radius: 50%;
  background: #1A6DAE; color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: bold; font-size: 16px; flex-shrink: 0;
}
.chat-info { flex: 1; min-width: 0; }
.chat-title-row { display: flex; justify-content: space-between; }
.chat-title { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-time { font-size: 11px; color: var(--muted); flex-shrink: 0; }
.chat-preview-row { display: flex; justify-content: space-between; margin-top: 2px; }
.chat-preview { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-badge {
  background: #dc3545; color: #fff; border-radius: 10px;
  padding: 1px 6px; font-size: 10px; flex-shrink: 0;
}

/* Chat Room */
#chat-messages { max-height: 60vh; overflow-y: auto; padding: 8px; }
.chat-msg { margin: 4px 0; max-width: 80%; }
.chat-msg.mine { margin-left: auto; text-align: right; }
.chat-msg.theirs { margin-right: auto; }
.chat-msg-sender { font-size: 10px; color: var(--muted); margin-bottom: 2px; }
.chat-msg-body {
  padding: 8px 12px; border-radius: 12px; font-size: 13px;
  display: inline-block; word-break: break-word;
}
.chat-msg.mine .chat-msg-body { background: #1A6DAE; color: #fff; }
.chat-msg.theirs .chat-msg-body { background: #e9ecef; color: #333; }
.chat-msg-time { font-size: 9px; color: var(--muted); margin-top: 2px; }
.chat-msg.typing .dot { animation: blink 1.4s infinite; display: inline-block; }
.chat-msg.typing .dot:nth-child(2) { animation-delay: 0.2s; }
.chat-msg.typing .dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes blink { 0%, 60%, 100% { opacity: 0.3; } 30% { opacity: 1; } }

.chat-input-row { display: flex; gap: 6px; padding: 8px; border-top: 1px solid var(--line); }
.chat-input-row input { flex: 1; padding: 8px 12px; border: 1px solid var(--line); border-radius: 20px; }
.chat-input-row button { padding: 8px 16px; border-radius: 20px; }

/* Assistant */
#assistant-panel { display: flex; flex-direction: column; height: 70vh; }
#assistant-messages { flex: 1; overflow-y: auto; padding: 8px; }
.assistant-quick { display: flex; gap: 6px; padding: 8px; flex-wrap: wrap; }
.assistant-quick button {
  padding: 4px 10px; border: 1px solid #1A6DAE; border-radius: 12px;
  background: transparent; color: #1A6DAE; font-size: 11px; cursor: pointer;
}

/* Task */
.task-tile {
  padding: 10px 12px; border-bottom: 1px solid var(--line);
  cursor: pointer; border-left: 3px solid transparent;
}
.task-tile:hover { background: var(--bg-alt); }
.task-tile.priority-urgent { border-left-color: #dc3545; }
.task-tile.priority-high { border-left-color: #fd7e14; }
.task-tile.priority-medium { border-left-color: #ffc107; }
.task-tile.priority-low { border-left-color: #28a745; }
.task-header { display: flex; justify-content: space-between; align-items: center; }
.task-title { font-weight: 600; font-size: 13px; }
.task-priority { font-size: 9px; padding: 2px 6px; border-radius: 8px; color: #fff; }
.task-meta { display: flex; gap: 8px; margin-top: 4px; font-size: 11px; color: var(--muted); }
.task-status { padding: 1px 6px; border-radius: 4px; font-size: 10px; }
.status-open { background: #e8f4fd; color: #1A6DAE; }
.status-in_progress { background: #fff3cd; color: #856404; }
.status-completed { background: #d4edda; color: #155724; }
.status-cancelled { background: #f8d7da; color: #721c24; }
.task-assignee { font-size: 11px; color: var(--muted); margin-top: 4px; }

/* Sync Status Bar */
#sync-status-bar { font-size: 12px; }
#sync-now-btn { background: rgba(0,0,0,0.1); border: 1px solid rgba(0,0,0,0.2); border-radius: 4px; cursor: pointer; }
body.is-offline #sync-status-bar { display: block !important; background: #dc3545; color: #fff; }

/* Face Detection Hint */
.face-detect-hint {
  position: absolute; bottom: 8px; left: 8px; right: 8px;
  text-align: center; font-size: 11px; font-weight: 600;
  padding: 4px 8px; border-radius: 6px;
  background: rgba(0,0,0,0.5); color: #fff;
  pointer-events: none; z-index: 5;
}
.camwrap { position: relative; }

/* Lapor Kendala — kode kejadian besar supaya gampang dibaca/difoto */
.incident-code { font: 700 1.4rem/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .04em; text-align: center; padding: 12px; border: 1px dashed currentColor; border-radius: 10px; margin: 8px 0 16px; }
#incident-form select, #incident-form textarea { width: 100%; box-sizing: border-box; }
#incident-form label.check { display: flex; gap: 8px; align-items: flex-start; font-weight: 400; margin: 10px 0; }
