/* STARLUX brand palette. Core accent/text colors sampled directly from the
   official logo's Pantone swatches (878C copper, 871C bronze/olive, 10393C
   dark slate) — kept exact, not re-tinted. Background/card/border/muted
   tones went through two passes 2026-07-31: first warmed up to match cabin
   reference photos (cream seat fabric, camel leather), then the user said
   light mode had ended up too warm overall — dialed back to a near-neutral
   light grey-white (barely any cream tint) while keeping the accent/text
   colors as the actual source of "brand warmth" instead of tinting every
   surface. prefers-color-scheme is the default signal; :root[data-theme]
   overrides it when the user picks a mode manually via the header toggle
   (see app.js) — the attribute must win in both directions, so these rules
   come after the media query. */
:root {
  --bg: #f6f5f1;
  --card-bg: #ffffff;
  --text: #3f4b4e;
  --muted: #6b6a63;
  --border: #e2e0d8;
  --accent: #8f664d;
  --accent-secondary: #7f724d;
  --accent-contrast: #ffffff;
  --accent-soft: #e4d9cd;
  --danger: #b3382c;
  --warning: #c17817;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #131110;
    --card-bg: #1e1b17;
    --text: #ece5d6;
    --muted: #a89985;
    --border: #332c22;
    --accent: #bd9780;
    --accent-secondary: #b3a67f;
    --accent-contrast: #1a1712;
    --accent-soft: #3a2f24;
    --danger: #e2685c;
    --warning: #f2b84d;
  }
}

:root[data-theme="light"] {
  --bg: #f6f5f1;
  --card-bg: #ffffff;
  --text: #3f4b4e;
  --muted: #6b6a63;
  --border: #e2e0d8;
  --accent: #8f664d;
  --accent-secondary: #7f724d;
  --accent-contrast: #ffffff;
  --accent-soft: #e4d9cd;
  --danger: #b3382c;
  --warning: #c17817;
}

:root[data-theme="dark"] {
  --bg: #131110;
  --card-bg: #1e1b17;
  --card-bg-translucent: rgba(30, 27, 23, 0.8);
  --text: #ece5d6;
  --muted: #a89985;
  --border: #332c22;
  --accent: #bd9780;
  --accent-secondary: #b3a67f;
  --accent-contrast: #1a1712;
  --accent-soft: #3a2f24;
  --danger: #e2685c;
  --warning: #f2b84d;
}

* { box-sizing: border-box; }

/* Full STARLUX wordmark as a faint background watermark — user supplied
   the source photo directly and asked for it "去背調淡之後當成背景的浮水
   印" (background removed, faded, used as a background watermark). An
   earlier, different background-watermark attempt this project had was
   explicitly rejected as looking bad ("背景浮水印不太好看 直接拿掉") —
   this is a deliberate second attempt with better source material and a
   much lower opacity, not a repeat of that.
   History worth remembering — several approaches tried, in order:
   (1) `body::before` with `position: fixed` — looked right (stays
   anchored while scrolling) but some mobile Safari versions don't repaint
   a `position: fixed` layer when `data-theme` changes on an ancestor
   (`<html>`), even though the CSS variables it depends on did update.
   User verbatim: "再切換深色淺色的時候有些介面不會完整的切換過去".
   (2) `position: absolute` instead — fixed the toggle bug but made the
   watermark invisible: `body`'s own `background: var(--bg)` paints
   *after* (on top of) a negative-z-index positioned descendant in the
   same stacking context, so the negative z-index needed to keep it behind
   foreground content also put it behind body's own background fill.
   (3) baking the fade into the PNG's alpha channel and layering it in
   body's own `background` shorthand (no separate element at all) — fixed
   both bugs, but on content-dense tabs the flight cards covered the
   single large mark's fixed spot entirely. User: "在設定有看到浮水印是正
   確的，但是在班表和歷史班表的介面裡面沒有看到".
   (4) reserving empty space for it with padding — fixed visibility, but
   read as a static banner instead of a background. User: "他應該是要當
   背景的浮水印 不是固定的圖片在那邊".
   (5) a small repeating tile instead of one big mark — solved the "feels
   like a banner" complaint, but user preferred approach (1)'s actual look
   after all: "我還是覺得第一版做出來的最好，但是有bug".
   (6) `background-attachment: fixed` on body's own background (no
   separate element, but still viewport-anchored while scrolling) — same
   visual goal as (1) via a different rendering path. Toggle worked, but
   the anchored-while-scrolling effect itself didn't — iOS Safari's
   support for `background-attachment: fixed` is unreliable in general,
   not just in old versions. User: "沒有固定在畫面上、內容從上面捲過去的
   效果".
   (7) `z-index: -1` (behind all content, (1)'s original recipe) — worked
   fine when the app had light content density, but after the STARLUX card
   restyle made every list-mode row (including plain off-days) a full-width
   opaque `.flight-card` with only ~12px gaps between them, the mark ended
   up permanently hidden behind cards in practice: this `position: fixed`
   element stays glued to one spot in the *viewport* (42% down) regardless
   of scroll, and that spot lands inside some card's vertical extent for
   the entire length of every list, on every device. Confirmed 2026-08-01
   by enumerating every `.flight-card`'s bounding rect — the 42% anchor
   never once landed in an inter-card gap. Tried making `.flight-card`
   itself translucent instead (compounds two low-opacity layers — even at
   80% card opacity, the mark's already-faint 0.05 opacity became ~0.01
   effective, imperceptible; going translucent enough to matter would have
   made cards look frosted/broken, undermining the crisp-card look the
   user explicitly wanted throughout this session). User verbatim on the
   desired behavior: "我要的效果是當前景是透明的時候 能看到背景的浮水印".
   **Current approach**: keep everything from (1)/(6) but flip `z-index`
   to sit *above* normal content instead of behind it — a conventional
   watermark (think: a stamp across a document) rather than a background
   fill, so it no longer depends on any content being transparent to show
   through. `pointer-events: none` (already present) keeps it from
   blocking taps/scrolls on whatever's underneath. Still below `header`
   (z-index: 10) so it doesn't double up with `header::before`'s own,
   separate watermark — the two never overlap spatially anyway since this
   one is centered well below the header's height. Instead of avoiding
   `position: fixed`, this fixes the repaint bug at its source: app.js's
   theme-toggle handler forces a synchronous reflow right after flipping
   `data-theme`, via a `.theme-reflow` class toggle (see below) rather
   than the `display: none`/`""` toggle this used originally — that first
   version was assumed invisible (synchronous, before the next paint) but
   turned out to actually flash once `header` became `position: sticky`
   — user verbatim: "深淺色切換的時候 螢幕上方會有不自然的閃爍". Hiding
   `body` entirely, even for one synchronous frame, removes it from
   rendering — `html` has no background of its own, so anything showing
   through during that gap would flash unstyled white/black. `.theme-reflow`
   forces the same class of compositing-layer recompute via `transform`
   instead, without ever removing content from the render tree. */
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang TC", "Microsoft JhengHei", sans-serif;
  background: var(--bg);
  color: var(--text);
  /* Backup for the viewport meta tag's user-scalable=no — some mobile
     browsers still trigger a double-tap zoom on fast taps regardless. */
  touch-action: manipulation;
  position: relative;
}

/* Belt-and-suspenders alongside the .theme-reflow fix above — if `body`
   is ever transparent/hidden for even a frame (now or in some future
   change), the viewport shows this instead of browser-default
   white/black, which would otherwise mismatch the active theme. */
html {
  background: var(--bg);
}

/* Forces a compositing-layer recompute (see the theme-toggle click
   handler in app.js) without hiding anything — `translateZ(0)` promotes
   body to its own GPU layer and back, which is enough to make a stuck
   `position: fixed` descendant (body::before) repaint, unlike a plain
   layout-only reflow. */
.theme-reflow {
  transform: translateZ(0);
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image: url("watermark-logo.png?v=1792935067");
  background-repeat: no-repeat;
  background-position: center 42%;
  background-size: min(640px, 85vw) auto;
  opacity: 0.05;
  pointer-events: none;
  z-index: 5;
}

/* watermark-logo.png is dark gray (~#3c3c3c) — at the same 5% opacity used
   in light mode, blending it onto dark mode's near-black --bg (#131110)
   only shifts pixel brightness by ~2 levels (vs ~9 in light mode against
   the light --bg), which is why the user reported it as "幾乎看不到了"
   (almost invisible) even though the CSS itself hadn't changed between
   themes. A dark mark on a dark background can't be fixed by opacity alone
   — inverting it to light-on-dark first (then re-darkening slightly with
   brightness so it doesn't overshoot to pure white) restores real contrast
   against the dark background, matching the visibility light mode already
   had. */
@media (prefers-color-scheme: dark) {
  body::before {
    filter: invert(1) brightness(0.9);
    opacity: 0.07;
  }
}

:root[data-theme="dark"] body::before {
  filter: invert(1) brightness(0.9);
  opacity: 0.07;
}

:root[data-theme="light"] body::before {
  filter: none;
  opacity: 0.05;
}

/* Custom pull-to-refresh — iOS's "add to home screen" standalone mode drops
   Safari's address-bar chrome, and the native pull-to-refresh bounce lives
   in that chrome, so it disappears along with it. This hand-rolled version
   (see app.js) works the same in a normal browser tab too. Sits above
   everything (z-index higher than header's own stacking context) but starts
   pulled fully out of view via a negative margin-top, animated back in as
   the user drags down. */
.pull-refresh-indicator {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 50px;
  margin-top: -50px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card-bg);
  color: var(--muted);
  font-size: 0.85rem;
  z-index: 500;
  transition: margin-top 0.2s ease;
  pointer-events: none;
}

/* header now also contains .calendar-toolbar (merged 2026-08-01 — user
   verbatim: "我覺得可以讓右側的浮水印當作背景，按鈕蓋過去沒關係，這樣就可
   以適度的重新調整上部整體的排列...並且我覺得上部應該要可以一直在上方，
   方便操作") — one sticky block instead of two separately-padded/bordered
   ones stacked on top of each other, which is also what was eating extra
   vertical space before any real content showed. `position: sticky` keeps
   the whole thing (brand + month-nav + icon buttons) reachable while
   scrolling through a long flight list, without needing to scroll back up
   first. */
header {
  position: sticky;
  top: 0;
  z-index: 10;
  overflow: hidden;
  padding: 0.65rem 1.5rem 0;
  background: var(--card-bg);
  /* Soft fade instead of a hard border line at the bottom edge — user
     request verbatim: "那條區分上下部位的實心線可以移除或者調整一下，讓
     她不會這麼突兀". A regular box-shadow would get cut off by this
     element's own `overflow: hidden` (needed to clip the corner
     watermark), so `inset` is used instead — it stays within header's own
     box and reads as a gentle elevation edge rather than a drawn line. */
  box-shadow: inset 0 -10px 8px -8px rgba(0, 0, 0, 0.1);
}

/* Real logo file (logo.png — cropped from the user-provided official
   asset, near its native ~94x78 resolution here so it stays crisp), shown
   as a ghost watermark behind the title, separate from the crisp visible
   .brand-logo image in the flow below. Enlarged and allowed to sit behind
   the toolbar row too (not just the brand row) per the same request above
   — buttons/text overlapping it is fine now, it's pure background. */
header::before {
  content: "";
  position: absolute;
  top: 45%;
  right: -1rem;
  width: 130px;
  height: 108px;
  transform: translateY(-50%);
  z-index: -1;
  background-image: url("logo.png?v=1792935067");
  background-repeat: no-repeat;
  background-size: contain;
  opacity: 0.16;
  pointer-events: none;
}

.header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}

/* Now the entry point into 設定 (a <button>, #brand-settings-btn — see
   switchTab) instead of a plain div, replacing the dedicated settings icon
   button that used to sit in the toolbar. User request verbatim: "設定按
   鈕可以刪除 改成按標題進入設定頁面 現在的標題是搖搖馬". Resets the
   default <button> chrome (border/background/font/padding) back to how
   the plain div looked before — visually identical to a non-interactive
   title, just tappable now. */
.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

.brand-logo {
  height: 30px;
  width: auto;
  display: block;
}

header h1 {
  margin: 0;
  font-size: 1.25rem;
  letter-spacing: 0.02em;
}

main {
  max-width: 900px;
  margin: 0 auto;
  padding: 1.5rem;
}

.tab-panel { display: none; }
.tab-panel.active { display: block; }

.hint { color: var(--muted); font-size: 0.9rem; }
.error { color: var(--danger); }

.upload-progress-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  margin-top: 0.5rem;
  overflow: hidden;
}

.upload-progress-fill {
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.3s ease;
}

.month-filter {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.month-filter::-webkit-scrollbar {
  display: none;
}

/* 編輯班表資料's own year/month filter (see loadAdminTable) is still a
   pair of plain `<select>` dropdowns, not the arrow-nav pattern the main
   班表/歷史班表 merge uses — admin editing is a rarer, more deliberate task
   where jumping straight to an arbitrary year is more useful than stepping
   month-by-month. Was accidentally left unstyled (falling back to bare
   browser chrome) after `.month-filter select` got replaced by `.month-nav`
   during the 歷史班表 arrow-nav redesign — restyled here, still shared via
   the `.month-filter` class both use as their container. */
.month-filter select {
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card-bg);
  color: var(--text);
  font-size: 0.95rem;
}

/* 班表's 列表/月曆 switcher — one icon button (not two text pills),
   pinned to the right of the month-filter row instead of its own row.
   User request verbatim: "切換列表和月曆的按鈕改成圖示，兩個來回切換，只
   用一顆按鈕就好，放在上一行的最右側". */
/* Moved out of 班表's tab-panel to sit permanently at the top of the page,
   2026-08-01 — user request verbatim: "設定和深淺色切換按鈕改到切換列表月
   曆按鈕的旁邊，並且改成圖示風格統一", confirmed via AskUserQuestion that
   the old separate nav tab bar should be removed entirely in favor of this
   one persistent toolbar. Styled like a second header strip (own
   background/border, full-width, `main`'s own max-width/centering repeated
   here) rather than inheriting `main`'s padding the way it did as a nested
   element. */
.calendar-toolbar {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding-bottom: 0.75rem;
}

/* No longer flex:1 (stretch-to-fill) — with only 2 icon buttons left in
   the toolbar (settings moved onto the brand title, see
   #brand-settings-btn), stretching this to consume all remaining width
   left an awkward, overly wide dead gap between 月份 and the icon pair
   instead of reading as one coherent toolbar. Natural width lets
   #view-mode-toggle-btn sit right next to it instead. */
.calendar-toolbar .month-filter {
  min-width: 0;
  margin-bottom: 0;
}

.view-mode-icon-btn {
  flex: 0 0 auto;
  width: 1.95rem;
  height: 1.95rem;
  padding: 0.4rem;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--text);
  border-radius: 999px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Earnings-toggle-btn's "currently revealed" state — same accent-fill
   treatment as .settings-subtab-btn.active, so the button itself signals
   whether tapping it right now would show or hide the estimate. */
.view-mode-icon-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* Both icon buttons stay grouped together and pinned as a unit on the far
   right of the toolbar, never adjacent to 月份 — user was explicit after a
   brief attempt at the alternative (icons flowing naturally next to 月份
   to close up the gap): "改成月曆/列表切換圖示直接貼著月份導覽 我不要這樣
   我要固定放在右側". A shared wrapper (rather than `margin-left: auto` on
   each button individually) avoids splitting the two buttons apart: auto
   margins on two separate flex siblings would each claim their own share
   of the free space, opening a gap *between* the icons too, not just
   before them. `margin-left: auto` on the wrapper itself pushes the whole
   group right as one piece; on 設定 view (where #month-filter and
   #view-mode-toggle-btn are hidden — see switchTab) the wrapper itself
   stays visible and keeps doing this with just #theme-toggle-btn inside
   it, which is what keeps 深淺色 in the same spot across both views. */
.toolbar-actions {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: auto;
}

/* #daily-schedule-btn/#allowance-list-btn's wrapper — identical gap/
   right-alignment recipe as .toolbar-actions above, on purpose: same
   button width (.view-mode-icon-btn) + same gap means each button here
   lines up directly above its counterpart one row down. User verbatim:
   "當日航班的按鈕要排列整齊，要對其下方的金錢按鈕才對". */
.header-actions {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: auto;
}

.view-mode-icon-btn svg {
  width: 100%;
  height: 100%;
}

/* 月曆模式 — user request verbatim: "在班表的地方 新增一個月曆模式 把航班
   填入月曆裡面". A traditional 7-column day grid for whichever single
   month is currently selected (see renderCalendarGrid — a grid only makes
   sense for one month, unlike 列表 mode which can show "全部"). */
/* One flat CSS Grid for the whole month — weekday headers, every day
   number, and every event bar are all direct children placed via explicit
   inline grid-row/grid-column (computed in renderCalendarGrid), rather
   than nesting a separate mini-grid per week. This is what lets an
   overnight duty's bar actually span multiple day columns in one
   continuous piece — user request verbatim: "同一趟任務可以用一個長條畫
   在一起 這樣更好懂有跨夜" (draw the same trip as one continuous bar,
   easier to read that it spans a night) — instead of the earlier version
   which drew a separate chip in each individual day cell. */
/* Restyled 2026-08-01 to match STARLUX's own official ROSTER app screen
   (user supplied a screenshot of it as the reference: "月曆可以參考這張圖
   來做") — flat full-bleed solid-color day blocks with hairline dividers,
   not the earlier rounded-pill-in-a-bordered-grid look. Still a single flat
   CSS Grid (see the block comment above renderCalendarGrid in app.js) so
   the uniform fixed-row-height fix from the previous round is preserved. */
/* Tried widening this to a fixed per-column px width with horizontal
   scroll so labels/amounts could stay on one line — user then explicitly
   rejected the swipe-to-see-the-rest-of-the-week interaction: "都在一個
   頁面完成 不要用滑動的效果". Back to 1fr columns filling the actual
   viewport width; the taller fixed row height (see renderCalendarGrid in
   app.js) gives text room to wrap within its own cell instead of needing
   more horizontal space. */
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: var(--card-bg);
  border: 1px solid var(--border);
  overflow: hidden;
}

.cal-weekday {
  background: var(--card-bg);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  text-align: center;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--border);
}

.cal-daynum-cell {
  background: var(--card-bg);
  font-size: 0.7rem;
  color: var(--muted);
  text-align: right;
  padding: 0.3rem 0.4rem 0.15rem;
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
}

.cal-daynum-cell.cal-col-first {
  border-left: none;
}

.cal-daynum-cell.cal-cell-empty {
  background: var(--bg);
  color: var(--border);
}

/* Fills the bar row's background for every day, bars painting on top —
   without this, a day with no duty shows its number sitting on a filled
   cell with a gap-colored empty strip underneath, instead of reading as
   one continuous card surface. */
.cal-cell-bg {
  background: var(--card-bg);
  border-left: 1px solid var(--border);
}

.cal-cell-bg.cal-col-first {
  border-left: none;
}

.cal-cell-bg.cal-cell-empty {
  background: var(--bg);
}

.cal-daynum-cell.cal-cell-today,
.cal-cell-bg.cal-cell-today {
  background: var(--accent-soft);
}

.cal-daynum-cell.cal-cell-today span {
  color: var(--text);
  font-weight: 700;
}

/* One continuous bar per duty, spanning as many day-columns as it lasts
   (grid-column start/end set inline per bar, clipped to the current
   week's row when a duty crosses a week boundary — see
   renderCalendarGrid). Single-day flights/duty-days are just a bar that
   happens to span exactly one column, so every entry — overnight or not —
   uses the same visual language. Solid fill + centered white text, like
   the reference app's day blocks, rather than the earlier inset chip. */
/* Went through several rounds on how to fit both the destination/duty
   label and the earnings amount in one narrow single-day cell (shrunk
   fonts, a widened+scrollable grid, amount replacing the label entirely —
   all previously tried and reverted one way or another). Landed on: label
   on its own line, amount on a second line below via an explicit <br> (see
   renderCalendarGrid in app.js), both at a normal/near-normal font size —
   user verbatim: "現在這樣的字體大小可以，切換金錢的時候應該可以把金額加
   在第二行，所有的資訊兩行就能顯示出來". white-space:nowrap still governs
   each line's own text (so an unexpectedly long value still ellipses
   rather than wrapping a third time), it just doesn't suppress the
   explicit <br> between the two lines. */
.cal-event-bar {
  background: #8a2331;
  min-height: 1.7rem;
  margin: 2px 3px;
  padding: 0.15rem 0.3rem;
  border-radius: 4px;
  font-size: 0.65rem;
  font-weight: 600;
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow: hidden;
  cursor: pointer;
}

/* Each line's own overflow/ellipsis safety net — a flex column can't apply
   text-overflow to the container as a whole, only to a real line box. */
.cal-event-main,
.cal-event-amount {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Slightly smaller than the bar's normal 0.65rem, same small safety margin
   verified in the amount-only round — real-device testing showed this
   exact kind of narrow-column text truncating even when headless
   measurements said it should just barely fit at the full size. */
.cal-event-amount {
  font-size: 0.6rem;
}

.cal-event-bar.cal-phase-in_flight {
  background: var(--accent);
  color: var(--accent-contrast);
}

.cal-event-bar.cal-phase-landed {
  background: var(--muted);
  color: var(--card-bg);
}

/* Non-flight duty days, color-coded by category to match the reference
   app's own convention: day off = neutral gray, leave = tan, standby =
   green. Falls back to gray for any code that doesn't match a known
   category (see dutyCategoryClass in app.js). */
.cal-event-bar.cal-duty-off {
  background: var(--border);
  color: var(--muted);
}

.cal-event-bar.cal-duty-leave {
  background: #b9a06b;
  color: #3a2f14;
}

.cal-event-bar.cal-duty-standby {
  background: #4d7a5f;
  color: #fff;
}

/* RT (複訓/定期訓練) — matches the reference app's own blue color for this
   duty code, kept distinct from standby's green since it's a different
   kind of mandatory-but-non-flight duty (training, not waiting on call). */
.cal-event-bar.cal-duty-training {
  background: #2f6690;
  color: #fff;
}

/* The segment(s) of a multi-day bar after its first day (i.e. after the
   week-boundary split, or just visually continuing without repeating the
   destination text) — same color, just no label, so it still reads as
   "this is the same trip continuing" without cluttering every column with
   repeated text. */
.cal-event-bar.cal-span-continue {
  justify-content: center;
}

.calendar-grid-wrap {
  margin-bottom: 1rem;
}

/* Brief highlight when 月曆模式 jumps back to 列表 mode and scrolls to a
   specific card, so the user's eye actually finds it instead of just
   landing somewhere on a page full of near-identical cards. */
@keyframes card-flash {
  0%, 100% { box-shadow: 0 2px 8px rgba(143, 102, 77, 0.07); }
  30% { box-shadow: 0 0 0 3px var(--accent); }
}

.flight-card-flash {
  animation: card-flash 1.5s ease;
}

/* 歷史班表 tab's month picker — previous/next arrows around a label,
   replacing the old year/month dropdown pair (user request verbatim:
   "歷史班表的月份切換可以做的更好用嗎", picked this style explicitly —
   see renderHistoryMonthFilter). Shares the .month-filter/.calendar-toolbar
   flex container with 班表's chip row, so .month-nav itself doesn't need
   its own flex:1 rule. */
.month-nav {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.month-nav-btn {
  flex: 0 0 auto;
  width: 1.95rem;
  height: 1.95rem;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--text);
  border-radius: 999px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8rem;
  line-height: 1;
}

/* One-tap jump back to the current month — user request verbatim: "在月曆
   裡面要有一個按鈕 能快速定位到今天的日期". Text content ("今天") instead
   of a fixed circular size like the arrow buttons, since it needs to fit
   two characters — same height/border/radius language, just auto width. */
/* Shrunk from a text pill ("今天") to an icon matching .month-nav-btn's
   own fixed circular size — user report verbatim, with an actual phone
   screenshot showing the real problem: "我用手機 今天的按鈕會有問題 太擁
   擠". A 2-character text pill was wide enough to make the whole toolbar
   (arrows + label + this + 3 more icon buttons) crowd together on a real
   phone width, something the desktop-width testing this session had been
   using didn't surface. */
.month-today-btn {
  padding: 0.45rem;
  color: var(--accent);
  border-color: var(--accent);
}

.month-today-btn svg {
  width: 100%;
  height: 100%;
}

/* No earlier/later month with any data — user request verbatim: "選取月份
   的按鈕 沒有資料的月份 就不能點過去" (see adjacentMonthKeyWithData). */
.month-nav-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

/* At least as large as .flight-date (1rem, inherited default — no
   explicit font-size set on it anywhere in its ancestor chain) — user
   verbatim: "我覺得上部年月的字體還是太小 應該要至少和下方的日期字體一樣
   才對". */
.month-nav-label {
  font-weight: 600;
  font-size: 1rem;
  white-space: nowrap;
}

.flight-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.month-header {
  margin-top: 0.5rem;
  padding-bottom: 0.6rem;
  border-bottom: 2px solid var(--accent);
}

.month-header:first-child { margin-top: 0; }

/* Marks where already-passed entries begin, once the list is re-ranked to
   sink them below whatever's still upcoming (see renderFlightList/
   renderGroup) — user verbatim: "已經過去的日期要和還沒過的日期中間有個間
   隔 方便區分". Extra margin on top of `.flight-list`'s own 0.75rem flex
   gap so the split reads as a deliberate section break, not just another
   inter-card gap. A small clock icon marks the *reason* for the break
   instead of relying on the gap size alone — was a "已過去" text label at
   first, swapped for an icon per follow-up: "已過去三個字改成用簡單的圖示
   來表示". */
.list-past-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.75rem 0;
  color: var(--muted);
}

.list-past-divider::before,
.list-past-divider::after {
  content: "";
  flex: 1;
  border-top: 1px dashed var(--border);
}

.list-past-divider-icon {
  display: flex;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.list-past-divider-icon svg {
  width: 100%;
  height: 100%;
}

/* Simple two-number summary (total flights, total duty/off days) —
   simplified after one round: user first asked to improve this ("月總結
   的部分可以優化的更好"), which became a full category-by-category chip
   breakdown, then immediately called that too busy ("月總結現在的設計有
   點太過複雜 麻煩用簡單明瞭的方式 簡單總結"). Still small colored chips
   (not plain gray text) for a bit of visual life, just only the two
   numbers that actually matter at a glance. */
/* flex-wrap: wrap wasn't reliable — fit fine at one test width but still
   wrapped on the user's own phone (narrower usable width / larger system
   font than the desktop test), user verbatim (with a screenshot): "我的
   頂部系統還是換行了". nowrap + horizontal scroll guarantees one line on
   any width instead of depending on exactly how much room chip text + gaps
   happen to need — user's actual ask was "一行就完整顯示", which a scrollable
   single line still satisfies even on a device narrow enough to need it. */
.month-summary {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.4rem;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.month-summary::-webkit-scrollbar {
  display: none;
}

.summary-chip {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  white-space: nowrap;
  /* Without this, flex's default shrink-to-fit would squeeze/wrap chip text
     inside a fixed-width chip once the row can no longer wrap onto a second
     line — needs to keep its natural width so the row scrolls instead. */
  flex-shrink: 0;
}

.summary-chip-flight {
  background: rgba(143, 102, 77, 0.14);
  color: var(--accent);
}

.summary-chip-off {
  background: var(--border);
  color: var(--muted);
}

/* Gold, not the neutral summary-chip-flight tan — user verbatim: "錢的標籤
   底色可以用成金色". Same gradient as .card-earnings' own badge. */
.summary-chip-earnings {
  background: linear-gradient(135deg, #f7e7b0, #c9a227 50%, #f0d786);
  color: #4a3a08;
}

/* Owner-only correction control for monthly_official_totals — see
   monthlyTotalCorrectionHtml in app.js. Small and understated (muted text,
   thin border) since this is a maintenance control, not something meant
   to draw the eye the way the chips above do. */
.monthly-total-correction {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.35rem;
  font-size: 0.75rem;
  color: var(--muted);
}

.monthly-total-correction-label {
  white-space: nowrap;
}

.monthly-total-correction input {
  width: 7.5rem;
  font-size: 0.75rem;
  padding: 0.2rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--card-bg);
  color: var(--text);
}

.monthly-total-correction button {
  font-size: 0.75rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--border);
  color: var(--text);
  cursor: pointer;
}

.monthly-total-save-status {
  white-space: nowrap;
}

.flight-card {
  position: relative;
  overflow: hidden;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
  /* Soft warm-tinted shadow (not a plain grey one) for a bit of the tactile,
     softly-lit lounge quality from the cabin reference photos, instead of a
     flat border-only card. */
  box-shadow: 0 2px 8px rgba(143, 102, 77, 0.07);
}

/* Cards for an actual flight leg (as opposed to a plain duty/off-day entry
   — see .duty-card below) sit above the global watermark (z-index: 5 on
   body::before) instead of having it bleed across the flight info. User
   verbatim: "有航班的卡片不需要做成透明" — the watermark-over-content look
   is fine for the rest of the page (gaps, off-day cards, calendar grid,
   settings) but shouldn't wash over the times/progress-bar/flight number
   the user actually reads closely. `.duty-card` (plain off-days) is
   intentionally excluded from this rule, so it still shows the watermark
   like everything else. */
.flight-card:not(.duty-card) {
  z-index: 6;
}

/* Destination emoji watermark — see the LANDMARKS table in app.js. Bottom-
   right per user preference ("我覺得右下角還是比較好看"). Sized and placed
   for a more considered, "commercial" watermark feel per follow-up request
   ("卡片浮水印的大小和位置，請依照可商用的美感來設計") — larger and bled
   further off the corner (mostly clipped by .flight-card's overflow:hidden)
   than a small contained sticker would be, at a lower opacity so it reads
   as background texture rather than a badge, the way premium app cards
   often let a graphic peek from a rounded corner. Kept upright (no tilt) —
   rotating a specific, already-hard-won-recognizable shape risks reading as
   crooked rather than intentional. Every leg always renders at least one
   duty-time line below its progress bar (see buildFlightCard —
   wakeInfoHtml/offDutyHtml are never both empty), so this size still stays
   clear of the progress bar itself, confined to that trailing text — user
   report verbatim (of the original, oversized version): "右下角的圖示會和
   回程的進度條打架". Grayscale keeps it from popping in full color against
   the card. .card-content sits above it (z-index) so card text is never
   affected. */
.card-landmark {
  position: absolute;
  right: -1rem;
  bottom: -1rem;
  font-size: 5rem;
  line-height: 1;
  opacity: 0.11;
  filter: grayscale(1);
  pointer-events: none;
  z-index: 0;
}

.card-content {
  position: relative;
  z-index: 1;
}

/* A leg that's airborne right now — accent-colored border + a slightly
   stronger, accent-tinted shadow so an in-progress duty visually pops out
   of the list instead of blending in with upcoming/finished ones. */
/* Solid accent border dropped per follow-up: "拿掉外框的實心線 但時還是要
   有光暈的呼吸效果" — border now stays .flight-card's plain default, only
   the breathing glow (box-shadow) still marks the card. */
.flight-card-active {
  box-shadow: 0 2px 12px rgba(143, 102, 77, 0.22);
  animation: card-breathe 2.6s ease-in-out infinite;
}

@keyframes card-breathe {
  0%, 100% {
    box-shadow: 0 2px 12px rgba(143, 102, 77, 0.22), 0 0 0 0 rgba(143, 102, 77, 0.4);
  }
  50% {
    box-shadow: 0 2px 12px rgba(143, 102, 77, 0.22), 0 0 16px 5px rgba(143, 102, 77, 0.5);
  }
}

@media (prefers-reduced-motion: reduce) {
  .flight-card-active {
    animation: none;
  }
}

.flight-card-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem 0.75rem;
  margin-bottom: 0.5rem;
}

.flight-date { font-weight: 600; }
.flight-no { color: var(--accent); font-weight: 600; }

/* 尚未起飛/飛行中/已抵達 — one badge style for all three (see
   legProgressHtml for why: they're the same kind of thing and shouldn't get
   different treatment). Only the in_flight variant breathes, since that's
   the one live/active state. */
.phase-badge {
  margin-left: auto;
  font-size: 0.85rem;
  font-weight: 700;
  padding: 0.2rem 0.65rem;
  border-radius: 999px;
  background: var(--border);
  color: var(--muted);
}

.phase-badge-in_flight,
/* Same highlight for 尚未起飛/已抵達 whenever the leg's own duty day is
   today — matches .duty-badge-today's precedent (see its own comment
   below). User verbatim: "當天的狀態也需要high light". Without this,
   today's card got the breathing border (.flight-card-active, see
   buildFlightCard) but its 尚未起飛/已抵達 badge stayed the same flat muted
   pill as any other day's, which read as inconsistent once the card-level
   highlight stopped being tied to in_flight specifically. */
.phase-badge-today {
  background: var(--accent);
  color: var(--accent-contrast);
  animation: badge-breathe 2.6s ease-in-out infinite;
  /* iOS Safari re-hints/re-rasterizes CJK glyphs at each opacity step of a
     plain (non-GPU-promoted) animation, which reads as the text visibly
     jittering up/down a pixel every beat — user verbatim, seen specifically
     on a 已抵達 badge on iPhone Safari: "已抵達的字樣會上下抖動". Forcing
     this onto its own compositing layer keeps the glyph raster fixed for
     the whole animation, so only opacity actually changes. */
  transform: translateZ(0);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

@keyframes badge-breathe {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .phase-badge-in_flight,
  .phase-badge-today {
    animation: none;
  }
}

/* No flight involved, so no boxed card — user request verbatim: "在班表中
   沒有航班的其他假別 不需要用實色的卡片框起來". Overrides .flight-card's
   background/border/shadow/radius entirely; reads as a plain row separated
   by a hairline instead of its own floating card. */
.duty-card {
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  box-shadow: none;
  padding: 0.6rem 0.2rem;
}

/* Soft accent tint for today's row — same var(--accent-soft) 月曆模式's own
   "today" cell already uses (.cal-cell-today), not a solid/boxed
   background, so it stays consistent with .duty-card being deliberately
   un-boxed while still reading as visibly different from every other day.
   User verbatim: "放假時的當天卡片 感覺沒有很明顯". Small negative margin
   bleeds the tint to the card's usual edges since .duty-card's own padding
   is tighter than a real .flight-card's. */
.duty-card-today {
  background: var(--accent-soft);
  margin: 0 -0.6rem;
  padding: 0.6rem;
  border-radius: 8px;
  animation: duty-card-breathe 2.6s ease-in-out infinite;
}

/* Animates background-color specifically (not the element's opacity) so
   the date/badge text stays fully crisp throughout — only the tint itself
   pulses. Same 2.6s ease-in-out timing as every other "pay attention to
   this now" breathing effect in the app (.duty-badge-today,
   .phase-badge-in_flight, .flight-card-active). User verbatim: "底色也要
   有呼吸效果". */
@keyframes duty-card-breathe {
  0%, 100% { background-color: var(--bg); }
  50% { background-color: var(--accent-soft); }
}

@media (prefers-reduced-motion: reduce) {
  .duty-card-today {
    animation: none;
  }
}

.duty-card .flight-card-header {
  margin-bottom: 0;
}

/* Same reasoning for the badge itself — user verbatim: "右邊的假別也不用反色
   特別high light 除非今天是當天". Plain muted pill by default; only today's
   entry gets the inverted accent-color treatment (.duty-badge-today, set in
   buildDutyCard) so it still stands out as "this is today" without every
   other day competing for the same visual weight. */
.duty-badge {
  margin-left: auto;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background: var(--border);
  color: var(--muted);
}

/* Category color-coding, matching 月曆模式's already-established off/leave/
   standby swatches (see dutyCategory/dutyCategoryClass) — user follow-up:
   "放假時的卡片效果 感覺不是很明顯". Every non-flight entry used to render
   as the same flat muted pill regardless of type; now 放假 stays as that
   plain default (.duty-badge-off is a no-op re-statement of it, kept
   explicit rather than implicit) while 待命/請假-type entries pick up the
   same colors their calendar bar already uses, so the two views agree on
   what each category looks like instead of only the calendar being
   color-coded. */
.duty-badge-off {
  background: var(--border);
  color: var(--muted);
}

.duty-badge-leave {
  background: #b9a06b;
  color: #3a2f14;
}

.duty-badge-standby {
  background: #4d7a5f;
  color: #fff;
}

.duty-badge-training {
  background: #2f6690;
  color: #fff;
}

/* User request verbatim: "當天的狀態high light要有呼吸效果" — reuses the
   same badge-breathe keyframes/timing .phase-badge-in_flight already
   established, so every "this needs your attention right now" badge in
   the app pulses the same way instead of each one inventing its own. */
.duty-badge-today {
  background: var(--accent);
  color: var(--accent-contrast);
  animation: badge-breathe 2.6s ease-in-out infinite;
  /* Same iOS Safari text-jitter fix as .phase-badge-today above — same
     badge-breathe opacity animation on the same kind of CJK text badge, so
     it's subject to the identical WebKit rasterization bug. */
  transform: translateZ(0);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .duty-badge-today {
    animation: none;
  }
}

.flight-route {
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.route-arrow { color: var(--muted); font-weight: 400; }

.leg {
  padding: 0.6rem 0;
  border-top: 1px dashed var(--border);
}

.leg:first-of-type {
  border-top: none;
  padding-top: 0;
}

.leg-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.35rem;
}

.leg-label {
  font-weight: 600;
  color: var(--accent);
}

/* Flight number links to its FlightAware live-tracking page (see
   legProgressHtml's flightNoHtml) — inherits the leg-label's color instead
   of the browser's default blue/purple link colors, with just an
   underline as the tap affordance. */
.flight-no-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-underline-offset: 0.15rem;
}

/* Small badge next to the flight number, marking exactly which leg is
   confirmed on a special-livery tail (currently just STARLUX's AirSorayama
   A350, B-58553 — see SPECIAL_LIVERY_TAILS in app.js) — still useful even
   though the whole card re-themes below (.flight-card-sorayama-*), since
   the *other* leg on the same card might be a perfectly ordinary aircraft. */
.special-livery-badge {
  font-size: 0.7rem;
  font-weight: 700;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  /* Default (plain-card fallback, e.g. before the whole-card theme classes
     below load) stays silver-toned; each theme overrides to its own metal
     just below — user follow-up: "金色配金色 銀色配銀色", this badge was
     the one piece still hardcoded silver on every card regardless of
     theme. */
  background: linear-gradient(135deg, #e8e8e8, #8a8a8a 50%, #e8e8e8);
  color: #262626;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Aircraft model + tail number, shown together on their own line once
   AeroDataBox has resolved either — not just special-livery ones (those
   also get .special-livery-badge up in the header). User verbatim:
   "如果有查詢到資料，飛機型號和機尾編號統一顯示在航班訊息的下一行，飛機
   型號在左邊" (follow-up to an earlier inline-badge version, superseded by
   this). Kept deliberately quiet/muted, unlike the special badge, since
   this is routine info rather than something worth calling out. */
.aircraft-info {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  margin-top: -0.1rem;
  margin-bottom: 0.15rem;
}

/* Whole-card theme override for a confirmed special-livery flight — user
   verbatim: "整張卡片可以都改成空山基風格，包含底色和本來的所有配色".
   Redefining the same CSS custom properties the rest of the app already
   reads (--card-bg, --border, --accent, etc.) is what makes this a *whole*-
   card reskin without having to touch every individual rule by hand — every
   child element (route text, phase badges, progress bar fill, times)
   already resolves its color through one of these variables, so overriding
   them here cascades automatically. `.flight-card`'s own base rule already
   supplies `border: 1px solid var(--border)` and `background: var(--card-
   bg)`, so no extra background-image/border-image plumbing is needed here.
   Background gone through several passes: originally a loud brushed-metal
   reskin (wide multi-band repeating gradient, diagonal gloss sheen,
   gradient border, retinted body text) — dialed back after "卡片背景目前
   太過於搶戲 可以用低調一點的方式來顯示" (flattened to a plain faint
   tint), then "背景可以改成金屬髮絲紋" asked texture back as a 1px
   repeating hairline grain, then "金屬髮絲紋的效果不是很好 可以用金屬反光
   漸層試試看" swapped that for the current single smooth diagonal gradient
   — a few wide, softly-blended tonal stops reading as light sweeping
   across a polished metal surface, instead of either a busy repeating
   pattern or a flat fill. --text/--muted stay unset in light mode so body
   copy reads exactly like every other card (dark mode below is the
   exception — see its own comment). */
.flight-card-sorayama-silver {
  --card-bg: #f7f8f9;
  --border: #c7c9ce;
  --accent: #6b6e78;
  --accent-secondary: #585b63;
  --accent-contrast: #ffffff;
  --accent-soft: #e7e8eb;
  background: linear-gradient(120deg, #eef0f2 0%, #fbfcfd 30%, #e4e6e9 55%, #f7f8f9 75%, #fdfdfe 100%);
  box-shadow: 0 2px 8px rgba(90, 95, 105, 0.1);
}

/* The static gradient above reads as "light metal" but not as actually
   catching/reflecting light — user verbatim: "空山銀航班的銀色閃爍效果不是
   很明顯". Adds a real animated highlight: a narrow bright diagonal band
   that sweeps across the card on a slow loop, like light moving across a
   polished/brushed metal surface. Clipped by .flight-card's own
   overflow:hidden, so it never spills past the card's rounded corners;
   pointer-events:none so it can't intercept taps meant for the card
   itself. Paused while the OS/browser prefers reduced motion. */
/* Animated shimmer removed entirely — user verbatim after several rounds
   trying to get it right (translating box, sliding background-position,
   opacity pulse, fixed-pixel sliding band at various widths/softness):
   "動畫效果都不太好 可以取消". Back to just the static base gradient
   defined on .flight-card-sorayama-silver/-gold above, no ::after,
   no @keyframes. */

/* Chrome wordmark treatment — user referenced the real "STARLUX
   AIRSORAYAMA" collab poster's glossy 3D lettering (IMG_3826.webp) and
   asked to apply that text style here: "把這個文字和風格套用到空山基的
   航班卡片裡面". Swaps the flat gradient-pill badge for gradient-clipped
   text (banded light/dark/light stops read as a brushed-chrome letter
   catching light) plus a light/dark text-shadow pair for the embossed
   look, instead of solid text on a colored background. */
.flight-card-sorayama-silver .special-livery-badge {
  background: none;
  background-image: linear-gradient(180deg, #ffffff 0%, #c9cbd1 28%, #3d4046 50%, #fafafb 60%, #8a8c93 80%, #e8e9ec 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7), 0 -1px 1px rgba(30, 30, 34, 0.35);
  padding: 0.1rem 0.15rem;
  font-weight: 800;
  letter-spacing: 0.03em;
}

.flight-card-sorayama-gold {
  --card-bg: #fdf9ee;
  --border: #d9c489;
  --accent: #a3833c;
  --accent-secondary: #8a6a28;
  --accent-contrast: #fffdf5;
  --accent-soft: #f0e4c4;
  background: linear-gradient(120deg, #f5ecd2 0%, #fffcf2 30%, #ecdfbc 55%, #fdf9ee 75%, #fffef8 100%);
  box-shadow: 0 2px 8px rgba(156, 122, 31, 0.1);
}

/* Same chrome-wordmark treatment as silver above, warm side. */
.flight-card-sorayama-gold .special-livery-badge {
  background: none;
  background-image: linear-gradient(180deg, #fff8e2 0%, #ecd48e 28%, #7a5f18 50%, #fdf6df 60%, #b8944a 80%, #f0dca0 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 1px 0 rgba(255, 248, 226, 0.7), 0 -1px 1px rgba(60, 42, 8, 0.35);
  padding: 0.1rem 0.15rem;
  font-weight: 800;
  letter-spacing: 0.03em;
}

/* Dark-mode variants — the light-mode rules above hardcode a light
   --card-bg unconditionally, so in dark mode the card stayed light while
   --text/--muted (left unset, so they inherited the *dark*-mode page
   default: pale cream) were also pale — pale-on-pale, unreadable. User
   verbatim: "暗黑模式的資訊顯示很不清楚 請金銀一起檢查". Same dual-selector
   pattern the rest of the app uses for dark mode (see :root's own
   `@media (prefers-color-scheme: dark)` + `:root[data-theme="dark"]`
   blocks near the top of this file): system-default dark and the explicit
   toggle both need their own copy, since neither alone covers both paths.
   --text/--muted now get real overrides here (unlike the light-mode rules)
   specifically so this card is guaranteed legible regardless of what the
   page-level dark text color happens to be. */
@media (prefers-color-scheme: dark) {
  .flight-card-sorayama-silver {
    --card-bg: #26282c;
    --text: #e7e8ec;
    --muted: #9a9da5;
    --border: #4d5058;
    --accent: #b8bcc4;
    --accent-secondary: #9498a1;
    --accent-contrast: #1c1d20;
    --accent-soft: #383b40;
    background: linear-gradient(120deg, #202226 0%, #2e3034 30%, #1e2024 55%, #26282c 75%, #303236 100%);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  }

  .flight-card-sorayama-gold {
    --card-bg: #2b2411;
    --text: #f0e6c8;
    --muted: #b8a878;
    --border: #5c4d22;
    --accent: #d9bc74;
    --accent-secondary: #c2a35a;
    --accent-contrast: #241c08;
    --accent-soft: #453a1c;
    background: linear-gradient(120deg, #241e0c 0%, #332b16 30%, #201a0a 55%, #2b2411 75%, #362d18 100%);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  }
}

:root[data-theme="dark"] .flight-card-sorayama-silver {
  --card-bg: #26282c;
  --text: #e7e8ec;
  --muted: #9a9da5;
  --border: #4d5058;
  --accent: #b8bcc4;
  --accent-secondary: #9498a1;
  --accent-contrast: #1c1d20;
  --accent-soft: #383b40;
  background: linear-gradient(120deg, #202226 0%, #2e3034 30%, #1e2024 55%, #26282c 75%, #303236 100%);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

:root[data-theme="dark"] .flight-card-sorayama-gold {
  --card-bg: #2b2411;
  --text: #f0e6c8;
  --muted: #b8a878;
  --border: #5c4d22;
  --accent: #d9bc74;
  --accent-secondary: #c2a35a;
  --accent-contrast: #241c08;
  --accent-soft: #453a1c;
  background: linear-gradient(120deg, #241e0c 0%, #332b16 30%, #201a0a 55%, #2b2411 75%, #362d18 100%);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

/* :root[data-theme="light"] forces light even when the OS is set to dark —
   without this, the @media block above would still apply its dark
   overrides on top since prefers-color-scheme doesn't care about the
   manual toggle, silently breaking the "explicit light" choice.

   --text/--muted are explicit here now — they weren't before, which was
   the actual bug behind "表定和實際都看不到" (confirmed against a real
   screenshot: the schedule time was rendering in the *dark-mode* --text
   color, a pale near-white #e7e8ec, while sitting on this light card).
   Cause: with the system set to dark but this app manually switched to
   light, BOTH `@media (prefers-color-scheme: dark) .flight-card-sorayama-
   silver` (sets --text/--muted to their dark-mode pale values) and this
   `:root[data-theme="light"]` rule (higher specificity, but previously
   only touched --card-bg/--border/--accent*) matched at once — since this
   rule never declared --text/--muted, it never overrode them, so the
   lower-specificity dark-mode block's pale values won for those two
   properties specifically while everything else correctly went light.
   Same fix applied to gold below. */
:root[data-theme="light"] .flight-card-sorayama-silver {
  --card-bg: #f7f8f9;
  --text: #3f4b4e;
  --muted: #6b6a63;
  --border: #c7c9ce;
  --accent: #6b6e78;
  --accent-secondary: #585b63;
  --accent-contrast: #ffffff;
  --accent-soft: #e7e8eb;
  background: linear-gradient(120deg, #eef0f2 0%, #fbfcfd 30%, #e4e6e9 55%, #f7f8f9 75%, #fdfdfe 100%);
  box-shadow: 0 2px 8px rgba(90, 95, 105, 0.1);
}

:root[data-theme="light"] .flight-card-sorayama-gold {
  --card-bg: #fdf9ee;
  --text: #3f4b4e;
  --muted: #6b6a63;
  --border: #d9c489;
  --accent: #a3833c;
  --accent-secondary: #8a6a28;
  --accent-contrast: #fffdf5;
  --accent-soft: #f0e4c4;
  background: linear-gradient(120deg, #f5ecd2 0%, #fffcf2 30%, #ecdfbc 55%, #fdf9ee 75%, #fffef8 100%);
  box-shadow: 0 2px 8px rgba(156, 122, 31, 0.1);
}

/* .flight-card-active's own breathing glow is a hardcoded warm rgba (not a
   CSS variable, since it was written before this theme existed), so on a
   card that's both "today" and special-livery it would otherwise clash
   against the cool silver/gold card instead of matching it. These compound
   selectors (higher specificity than either single class, so they win
   regardless of source order) swap in a much gentler themed glow — kept
   low-key like the rest of this card per "卡片背景目前太過於搶戲 可以用
   低調一點的方式來顯示", so the peak alpha here is deliberately faint
   compared to the app's normal breathing glow, not stronger. */
.flight-card-sorayama-silver.flight-card-active {
  animation: card-breathe-silver 2.6s ease-in-out infinite;
}

@keyframes card-breathe-silver {
  0%, 100% { box-shadow: 0 2px 8px rgba(90, 95, 105, 0.1), 0 0 0 0 rgba(107, 110, 120, 0.2); }
  50% { box-shadow: 0 2px 8px rgba(90, 95, 105, 0.1), 0 0 10px 3px rgba(107, 110, 120, 0.3); }
}

.flight-card-sorayama-gold.flight-card-active {
  animation: card-breathe-gold 2.6s ease-in-out infinite;
}

@keyframes card-breathe-gold {
  0%, 100% { box-shadow: 0 2px 8px rgba(156, 122, 31, 0.1), 0 0 0 0 rgba(163, 131, 60, 0.2); }
  50% { box-shadow: 0 2px 8px rgba(156, 122, 31, 0.1), 0 0 10px 3px rgba(163, 131, 60, 0.3); }
}

@media (prefers-reduced-motion: reduce) {
  .flight-card-sorayama-silver.flight-card-active,
  .flight-card-sorayama-gold.flight-card-active {
    animation: none;
  }
}

.flight-progress {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* Tall enough to reserve clearance for the much larger plane marker
     (see .progress-plane) so it doesn't overlap the leg-header row above
     or schedule-compare below — user request verbatim: "有需要的話進度條
     上下的內容可以做位置的調整". */
  min-height: 34px;
  /* Top margin bumped up from the original 0.4rem — the plane markers grew
     taller since this was last tuned (real-length-proportional sizing, up
     to 37px for A350-900/40px+ for special-livery), so the tail fin (which
     sticks up ~70% of the icon's own height above the track's center) now
     reaches close enough to crowd the leg-header text above it. User
     verbatim: "現在飛機圖示的垂直尾翼離上一行文字的距離很近，幫忙適度的
     調整". Bottom margin left alone since only the top spacing was flagged.
  */
  margin: 0.9rem 0 0.4rem;
}

.schedule-compare {
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  margin-bottom: 0.35rem;
}

/* Full-strength text color, not --muted — user verbatim: "現在我看卡片上
   實際的時間顯示很淺 看不清楚". This is the ONLY time shown on any
   not-yet-departed flight (.actual-line only appears once real live data
   exists), so for most cards this muted gray WAS the primary/only time
   readout, not a secondary detail — too faint to read at a glance despite
   being the main content. .actual-line's own bold weight still keeps it
   visually senior on the (rarer) cards where both lines appear together. */
.sched-line {
  color: var(--text);
}

.actual-line {
  color: var(--text);
  font-weight: 600;
}

/* Marks a schedule time that came from the same-flight-number historical
   fallback (AeroDataBox had no data) rather than a real lookup — not
   confirmed, so it's flagged distinctly instead of looking identical to a
   verified 表定 time. */
.inferred-badge {
  margin-left: 0.35rem;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--warning);
  color: var(--accent-contrast);
}

/* User verbatim: "在表定時間和實際時間後面 顯示飛行時數". Muted and
   slightly smaller than the time range itself on both lines — same
   subordinate-detail weight as .sched-line normally has, so it reads as
   "by the way, that's how long" rather than competing with the times. */
.flight-duration {
  margin-left: 0.4rem;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--muted);
}

.progress-track {
  position: relative;
  flex: 1;
  height: 4px;
  border-radius: 999px;
  background: var(--border);
}

.progress-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.3s ease;
}

.progress-track.phase-upcoming .progress-fill {
  background: var(--border);
}

/* The user's own STARLUX A321neo reference photo, used directly — real
   livery colors (not recolored/simplified), just the white background
   stripped out (the source file has an opaque white background, not real
   transparency, so showing it as-is would draw a white box on top of the
   colored progress track) and scaled down for the marker. Wide/short
   instead of square because that's the plane's actual proportions; forcing
   it into a small square icon flattened it into an unrecognizable sliver.
   User request verbatim: "就用這張圖 你需要做的只有調整大小 / 顏色都用我
   提供的原圖就好". */
.progress-plane {
  position: absolute;
  top: 50%;
  /* The tail fin sticks up much further above the fuselage than the wing/
     engine hangs below it, so the fuselage's own centerline sits at ~70%
     down the image, not 50% — centering by bounding box (-50%) would leave
     the fuselage sitting visibly above the track. -69.7% aligns the actual
     fuselage line with the track's center instead. */
  transform: translate(-50%, -69.7%);
  width: 95px;
  height: 25px;
  background-image: url("plane-icon.png?v=1792935067");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  transition: left 0.3s ease;
}

/* Upcoming legs used to render the plane grayscale + 50% opacity here —
   removed per user verbatim: "沒有起飛的飛機不用反灰". Full color at every
   phase now; only .progress-track.phase-upcoming .progress-fill above
   still marks "no progress yet" (a plain muted bar, not the plane icon
   itself). */
.progress-track.phase-landed .progress-plane {
  opacity: 0.85;
}

/* User's own chrome-livery icon artwork, swapped a third time as better
   reference photos came in — user verbatim each round: "我不喜歡你改出來
   的飛機圖示效果 我直接給你空山銀的圖示 幫我套用進去" (first, generic
   "STARLUX"/B-58201 wordmark), "我要改成這個圖示" (second, added the real
   "STARLUX AIRSORAYAMA" wordmark + B-58553 reg), "空山銀的飛機圖示改成這張
   圖" (third/current). Same treatment every time: background removed (was
   opaque black, not real transparency) and cropped tight to the plane's own
   bounding box, no recoloring. */
.flight-card-sorayama-silver .progress-plane {
  background-image: url("silver-plane-icon.png?v=1792935067");
}

/* User's own gold-livery icon artwork (real B-58554 registration, same
   "STARLUX AIRSORAYAMA" wordmark as the silver rule above) — user verbatim:
   "給你空山金的飛機圖示 幫我修改". Same treatment as silver: background
   removed (opaque black, not real transparency), cropped tight to the
   plane's own bounding box, no recoloring. */
.flight-card-sorayama-gold .progress-plane {
  background-image: url("gold-plane-icon.png?v=1792935067");
}

/* Dark-mode swaps to the halo-cleaned photo — same reasoning and file split
   as AIRCRAFT_TYPE_ICONS in app.js (search there for the full explanation):
   the original photo's faint white edge fringe is invisible on a light
   card but reads as a visible halo on a dark one. User verbatim: "有辦法
   黑暗模式的時候用沒有白邊的版本 淺色模式用有白邊的版本嗎". Same three-block
   pattern (media query + both manual overrides) as the rest of this card
   theme's own dark-mode handling above. */
@media (prefers-color-scheme: dark) {
  .flight-card-sorayama-silver .progress-plane {
    background-image: url("silver-plane-icon-dark.png?v=1792935067");
  }
  .flight-card-sorayama-gold .progress-plane {
    background-image: url("gold-plane-icon-dark.png?v=1792935067");
  }
}
:root[data-theme="dark"] .flight-card-sorayama-silver .progress-plane {
  background-image: url("silver-plane-icon-dark.png?v=1792935067");
}
:root[data-theme="dark"] .flight-card-sorayama-gold .progress-plane {
  background-image: url("gold-plane-icon-dark.png?v=1792935067");
}
:root[data-theme="light"] .flight-card-sorayama-silver .progress-plane {
  background-image: url("silver-plane-icon.png?v=1792935067");
}
:root[data-theme="light"] .flight-card-sorayama-gold .progress-plane {
  background-image: url("gold-plane-icon.png?v=1792935067");
}

/* 起床/到公司/下班時間 — secondary info, not the card's main content: small,
   plain, no urgency color-coding. User request verbatim: "起床 到公司 下班
   時間 前面應該不用加日期 也不需要用特別的顏色標記". */
.duty-time {
  font-size: 0.8rem;
  color: var(--muted);
}

/* Moved out of the header to sit under the route line instead — user
   request verbatim: "當天來回或者過夜可以放在TPE>NRT的下方". */
.stay-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  margin: -0.35rem 0 0.6rem;
}

/* Per-trip pay estimate — user verbatim: "後續我會想要新增每趟航班能夠賺
   的錢". Only ever rendered when entryEarnings() is actually positive (see
   buildFlightCard), so this rule never has to account for an empty/zero
   state. Absolutely positioned top-right corner badge (anchored to
   .card-content, which already has `position: relative`) instead of a
   normal-flow line, so revealing/hiding it never shifts the rest of the
   card's layout — user verbatim: "錢顯示的位置可以放在卡片的右上角，或者
   不要影響到本來卡片的排版位置". Gold pill per follow-up: "錢的標籤底色
   可以用成金色", same gradient family as the special-livery-badge's own
   metal treatment elsewhere in this file, just applied as a solid badge
   background instead of clipped text. */
.card-earnings {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 0.8rem;
  font-weight: 700;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  background: linear-gradient(135deg, #f7e7b0, #c9a227 50%, #f0d786);
  color: #4a3a08;
  white-space: nowrap;
  z-index: 2;
  cursor: pointer;
}

/* Same gold pill, but a normal in-flow flex sibling instead of absolutely
   positioned — duty cards (buildDutyCard) already have .duty-badge sitting
   at the top-right corner via margin-left: auto, so .card-earnings itself
   would land directly on top of it there. Used only for standby days,
   which now carry a real flat Credit Hour value — user verbatim: "待命一天
   會算credit hour 2". margin-left:auto here too (it comes before
   .duty-badge in the header's DOM order) so the two right-align as a
   group instead of the amount sitting left, stranded right after the date
   — user verbatim: "列表模式 待命的金額顯示需要靠右". */
.card-earnings-inline {
  margin-left: auto;
  font-size: 0.8rem;
  font-weight: 700;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  background: linear-gradient(135deg, #f7e7b0, #c9a227 50%, #f0d786);
  color: #4a3a08;
  white-space: nowrap;
  cursor: pointer;
}

/* Tap-to-see breakdown modal — user verbatim: "每個卡片金額的地方按下去要
   能跳出一個小視窗，顯示該金額是怎麼算出來的". One shared overlay, filled
   in per-tap (see earnings-breakdown-overlay in index.html / app.js)
   rather than a modal per card. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: 1.2rem;
}

.modal-box {
  position: relative;
  background: var(--card-bg);
  color: var(--text);
  border-radius: 14px;
  padding: 1.4rem 1.2rem 1.2rem;
  max-width: 420px;
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
}

.modal-box h3 {
  margin: 0 0 0.8rem;
  font-size: 1.05rem;
}

.modal-close {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  width: 1.8rem;
  height: 1.8rem;
  border: none;
  background: var(--border);
  color: var(--text);
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
}

.breakdown-leg {
  padding: 0.5rem 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.85rem;
  color: var(--muted);
}

/* Title + round sort-toggle button on one line, date moved to its own small
   line below — user verbatim: "可以做成圓形的切換扭放在當日航班 標題的
   旁邊 當天的日期可以小字放在下一行". */
.daily-schedule-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}

.daily-schedule-header h2 {
  margin: 0;
}

/* Reuses .month-nav's own layout, just with bottom spacing added before the
   flight list — user verbatim: "顯示當天星期幾就好，然後要能手動調整星期
   幾". */
.daily-schedule-weekday-nav {
  margin: 0.2rem 0 1rem;
}

/* User verbatim (reverting the brief compact-grid experiment): "當日航班
   我比較喜歡本來的樣子" — back to the original times-left / stacked-right
   layout. */
.daily-schedule-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.85rem;
}

.daily-schedule-row:last-child {
  border-bottom: none;
}

/* Stacked (dep above arr) instead of one line with an inline arrow — the
   switch to 12-hour time (dailyScheduleFmtTime, e.g. "上午08:30") made a
   single "08:30 → 12:55"-style line too wide for a phone screen, squeezing
   .daily-schedule-route into an ugly multi-line wrap. User verbatim asking
   for 12-hour time: "當日航班 不要使用24小時制". */
.daily-schedule-times {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  font-size: 1.05rem;
  font-weight: 700;
}

/* User verbatim: "當日航班的時間 改成出發 抵達 不要用箭號" — replaces the
   arrow between dep/arr times with plain 出發/抵達 labels. */
.daily-schedule-time-label {
  color: var(--muted);
  font-weight: 400;
  font-size: 0.7rem;
  margin-right: 0.3rem;
}

.daily-schedule-route {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  text-align: right;
  color: var(--text);
  flex: 1 1 auto;
  min-width: 0;
}

/* Estimated pay now sits on the flight number's own line, at that line's
   left edge — user verbatim, trying the alternative from the previous
   round: "我想試試看顯示金額放在航班的同一行左邊". width:100% makes this
   one line span the full route column (unlike its .daily-schedule-route
   siblings, which shrink to their own content width) so space-between can
   anchor the flight number at the true right edge regardless of whether
   the badge ahead of it is present — same "no shift when toggled" property
   as the earlier layouts. */
/* User verbatim, refining the previous "same line" placement: "金額的部分
   靠右（貼近航班）不要靠左" — both the badge and the flight number now
   group together at the line's right edge (flex-end) instead of sitting at
   opposite ends (space-between). Still shift-safe when the badge is hidden:
   flex-end anchors .daily-schedule-flightno at the same right edge either
   way, the gap next to an empty .daily-schedule-pay-slot just collapses to
   nothing rather than leaving a gap. */
.daily-schedule-flightno-line {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  width: 100%;
  gap: 0.4rem;
}

.daily-schedule-flightno-line .card-earnings-inline {
  margin-left: 0;
  font-size: 0.75rem;
}

.daily-schedule-flightno {
  font-weight: 700;
  color: var(--accent);
  font-size: 0.95rem;
}

.daily-schedule-note {
  font-size: 0.7rem;
  color: var(--muted);
}

.daily-schedule-day-offset {
  color: var(--accent);
  font-weight: 700;
  margin-left: 0.1rem;
}

/* 津貼列表 — same compact-row spirit as .daily-schedule-row above. */
.allowance-section-title {
  margin: 1rem 0 0.4rem;
  font-size: 0.95rem;
}

.allowance-section-title:first-child {
  margin-top: 0;
}

.allowance-list {
  margin-bottom: 0.5rem;
}

.allowance-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.4rem 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.8rem;
}

.allowance-row:last-child {
  border-bottom: none;
}

.allowance-flightno {
  font-weight: 600;
  color: var(--accent);
  font-size: 0.75rem;
  flex: 0 0 auto;
}

.allowance-dest {
  color: var(--text);
  flex: 1 1 auto;
}

.allowance-amount {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* 排班建議 — one card-like block per duty, ranked worst-to-best by
   suggestionSwapPriority. */
.suggestion-row {
  padding: 0.7rem 0;
  border-bottom: 1px dashed var(--border);
}

.suggestion-row:last-child {
  border-bottom: none;
}

.suggestion-row-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}

.suggestion-date {
  font-weight: 700;
  color: var(--text);
  font-size: 0.9rem;
}

.suggestion-badge {
  font-size: 0.7rem;
  font-weight: 700;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  white-space: nowrap;
}

.suggestion-badge-swap {
  background: #f3d3ce;
  color: #7a2e1f;
}

.suggestion-badge-keep {
  background: #d6e8d6;
  color: #29542c;
}

@media (prefers-color-scheme: dark) {
  .suggestion-badge-swap {
    background: #5c2c22;
    color: #f3d3ce;
  }
  .suggestion-badge-keep {
    background: #2c4a2e;
    color: #d6e8d6;
  }
}

:root[data-theme="dark"] .suggestion-badge-swap {
  background: #5c2c22;
  color: #f3d3ce;
}

:root[data-theme="dark"] .suggestion-badge-keep {
  background: #2c4a2e;
  color: #d6e8d6;
}

:root[data-theme="light"] .suggestion-badge-swap {
  background: #f3d3ce;
  color: #7a2e1f;
}

:root[data-theme="light"] .suggestion-badge-keep {
  background: #d6e8d6;
  color: #29542c;
}

.suggestion-route {
  font-size: 0.85rem;
  color: var(--text);
  margin-bottom: 0.15rem;
}

.suggestion-cp {
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 0.15rem;
}

.suggestion-flags {
  font-size: 0.75rem;
  color: var(--accent);
}

.suggestion-flags-none {
  color: var(--muted);
}

.breakdown-leg-title {
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0.2rem;
}

.breakdown-summary {
  margin-top: 0.6rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.breakdown-total {
  margin-top: 0.4rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
}

.hidden { display: none !important; }

button {
  font-family: inherit;
}

#parse-btn, #save-settings-btn, #settings-unlock-btn {
  margin-top: 0.5rem;
  margin-right: 0.5rem;
  padding: 0.5rem 1rem;
  border: none;
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  border-radius: 6px;
  cursor: pointer;
}

/* 設定 tab's 上傳/排程/編輯班表/後台管理 switcher — user verbatim: "設定裡面的ui
   需要優化，目前有三個大功能 應該要用按鍵切換 不要都混在一起". Segmented-
   control look (shared pill-shaped container, active segment gets the
   solid accent fill) rather than plain tab underlines, so it reads as
   "pick one of three modes" rather than a second-level nav bar. */
.settings-subtabs {
  display: flex;
  gap: 0.25rem;
  background: var(--border);
  padding: 0.25rem;
  border-radius: 8px;
  margin-bottom: 1rem;
}

.settings-subtab-btn {
  flex: 1;
  padding: 0.5rem 0.4rem;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
}

.settings-subtab-btn.active {
  background: var(--accent);
  color: var(--accent-contrast);
}

.settings-subtab-panel { display: none; }
.settings-subtab-panel.active { display: block; }

/* Shown in place of the flight list when this device hasn't been
   authorized yet (see showDeviceBlockedScreen in app.js). */
.device-blocked {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--muted);
}

.device-blocked h2 {
  color: var(--text);
  margin-bottom: 0.5rem;
}

.device-blocked-code {
  margin-top: 1rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
}

.device-code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 600;
}

/* Splits 已知裝置 into 已命名/陌生 groups instead of one flat IP-sorted
   list — user verbatim: "已知裝置裡面，應該要把已經取完名字的裝置和陌生的
   裝置分開". A named device is a settled matter; an unnamed one is what
   actually needs a decision, so it gets its own clearly-labeled section
   rather than being mixed in. */
.device-group-title {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--muted);
  margin: 0.75rem 0 0.35rem;
}

.device-group-title:first-child {
  margin-top: 0;
}

/* Bolder than the other columns on purpose — user follow-up on the
   device-splitting issue: "裝置代碼 + IP 一起顯示". Devices are sorted by
   IP (see db.list_devices) so matching rows already sit next to each
   other; making the IP itself stand out is what actually lets the owner
   *notice* the match at a glance, not just be adjacent in the list. */
.device-ip {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 700;
  color: var(--text);
}

#tab-settings label {
  display: block;
  margin-bottom: 0.75rem;
}

#tab-settings input {
  display: block;
  margin-top: 0.25rem;
  padding: 0.4rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card-bg);
  color: var(--text);
}

/* Radio rows for 未知機型圖示 — overrides the block/full-width number-input
   styling above so the radio dot sits inline with its own label text. */
.icon-fallback-option {
  display: flex !important;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem !important;
}

#tab-settings .icon-fallback-option input[type="radio"] {
  display: inline-block;
  width: auto;
  margin: 0;
  padding: 0;
}

#tab-settings .icon-fallback-option select {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card-bg);
  color: var(--text);
}

.admin-toolbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.admin-delete-month-btn {
  padding: 0.45rem 0.8rem;
  font-size: 0.8rem;
  font-weight: 600;
  border: 1px solid var(--danger);
  border-radius: 6px;
  background: transparent;
  color: var(--danger);
  cursor: pointer;
}

/* Drops this device's admin-password bypass (see the click handler in
   app.js) — a quiet text-link, not a prominent button, since it's a rarely-
   needed escape hatch rather than part of the normal settings workflow. */
.admin-logout-btn {
  display: block;
  margin-bottom: 1rem;
  padding: 0;
  border: none;
  background: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
}

.admin-delete-month-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.admin-table-wrap {
  overflow-x: auto;
  margin-top: 0.75rem;
}

/* 編輯班表資料's card list — replaces the old 14-column table (unusable on
   a phone, constant horizontal scroll). One collapsed summary line per
   entry; tapping it expands the full grouped form for just that entry
   (accordion — only one open at a time, see expandedAdminEntryId in
   app.js). User verbatim: "編輯班表資料的表格呈現太複雜不好用，需要重新
   優化，並且按鍵也需要重新檢視優化". */
.roster-editor-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.roster-editor-card {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card-bg);
  overflow: hidden;
}

.roster-editor-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  padding: 0.6rem 0.8rem;
  border: none;
  background: none;
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.roster-editor-chevron {
  color: var(--muted);
  font-size: 0.7rem;
  flex-shrink: 0;
}

.roster-editor-form {
  padding: 0 0.8rem 0.8rem;
  border-top: 1px solid var(--border);
}

.roster-editor-group {
  padding-top: 0.6rem;
}

.roster-editor-group-title {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.35rem;
}

.roster-editor-field {
  display: block;
  font-size: 0.78rem;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

.roster-editor-field input {
  display: block;
  width: 100%;
  margin-top: 0.2rem;
  padding: 0.4rem;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
}

.admin-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.8rem;
}

.admin-table th, .admin-table td {
  border: 1px solid var(--border);
  padding: 0.3rem 0.4rem;
  white-space: nowrap;
}

.admin-table th {
  background: var(--bg);
  color: var(--muted);
  font-weight: 600;
  position: sticky;
  top: 0;
}

/* Overrides #tab-settings input's block display/margin — these live inside
   a dense table, not a stacked label list. */
.admin-table input {
  display: inline-block;
  margin: 0;
  width: 100%;
  min-width: 92px;
  padding: 0.2rem 0.3rem;
  font-size: 0.8rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--card-bg);
  color: var(--text);
}

.admin-actions {
  display: flex;
  gap: 0.3rem;
  flex-wrap: wrap;
}

.admin-actions .device-merge-select {
  font-size: 0.8rem;
  padding: 0.2rem 0.3rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--card-bg);
  color: var(--text);
}

/* Suggested-twin hint under the 裝置 column — see _suggestMergeGroups.
   white-space: normal overrides the table's own nowrap default (see
   .admin-table th/td above) since this needs to wrap under the UA label
   rather than force the whole row wider. */
.device-merge-hint {
  white-space: normal;
  font-size: 0.75rem;
  color: var(--muted);
  margin-top: 0.2rem;
}

.roster-editor-form .admin-actions {
  margin-top: 0.5rem;
}

.roster-editor-form .admin-save-btn,
.roster-editor-form .admin-delete-btn {
  padding: 0.5rem 0.9rem;
  font-size: 0.85rem;
}

.admin-save-btn, .admin-delete-btn {
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.admin-save-btn {
  background: var(--accent);
  color: var(--accent-contrast);
}

.admin-delete-btn {
  background: var(--danger);
  color: #fff;
}
