:root {
  color-scheme: light;
  font-family: "Inter", sans-serif;
  --ink: oklch(0.14 0.01 250);
  --ink-strong: oklch(0.08 0.005 250);
  --muted: oklch(0.16 0.01 250);
  --glass: oklch(0.96 0.005 250 / 0.58);
  --glass-border: oklch(1 0 0 / 0.55);
  --blue: oklch(0.78 0.1 232);
  --red: oklch(0.58 0.22 25);
  /* .content-row/.content-left/.main-grid/.right-rail/.rail-subgrid/
     .profile-cards keep a fixed 22px/18px gap (not this variable) above
     STACK_BREAKPOINT because app.js's column-width math (GRID_GAP,
     PROFILE_CARD_GAP) assumes those exact values. Below the breakpoint
     that math doesn't run, so the @media (max-width: 900px) block
     switches all of them to this same fluid value as .kiosk-shell's own
     gap - otherwise, once every container stacks into one visual column
     of cards, the fixed-vs-fluid gaps next to each other look uneven.
     Keep this in sync with .kiosk-shell's "gap" value below. */
  --stack-gap: clamp(12px, 1.6vh, 22px);
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { position: relative; background: oklch(0.15 0.018 250); color: var(--ink); }
button { font: inherit; }

/* overflow-y: auto (not hidden) is a safety net: on very short/narrow
   screens where stacked widgets need more room than the viewport offers,
   the page scrolls instead of clipping content. */
.kiosk-viewport { position: fixed; inset: 0; overflow-x: hidden; overflow-y: auto; background: oklch(0.15 0.018 250); }

/* .kiosk-scale used to be a fixed 1920x1080 canvas, uniformly scaled via JS
   transform to fit the viewport (letterboxed). It's now a plain fluid
   container - the layout below reflows natively instead of being scaled. */
.kiosk-scale { position: relative; z-index: 2; width: 100%; height: auto; min-height: 100%; }

/* position: fixed (not absolute) is required here: .kiosk-viewport scrolls
   internally when stacked content is taller than the screen (see above),
   and an absolutely-positioned element scrolls along with its scrolling
   ancestor's content. Fixed positioning pins the background to the actual
   browser viewport instead, so it stays put and full-bleed no matter how
   far the content underneath scrolls. */
#background-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-color: oklch(0.15 0.018 250);
}
.bg-layer {
  position: absolute;
  inset: 0;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  opacity: 0;
  transition: opacity 1.2s ease;
}
.bg-layer.active { opacity: 1; }
.background-scrim { position: fixed; inset: 0; z-index: 1; pointer-events: none; background: linear-gradient(180deg, oklch(0.1 0.02 250 / 0.42), oklch(0.1 0.02 250 / 0.18) 35%, oklch(0.1 0.02 250 / 0.18) 65%, oklch(0.1 0.02 250 / 0.48)); }
.kiosk-shell {
  position: relative;
  width: 100%;
  height: auto;
  min-height: 100%;
  max-width: 2400px;
  margin: 0 auto;
  padding: clamp(18px, 2.6vw, 50px) clamp(18px, 3.1vw, 60px);
  display: flex;
  flex-direction: column;
  gap: var(--stack-gap);
}

.datetime-header { display: flex; align-items: flex-end; justify-content: space-between; flex: 0 0 auto; flex-wrap: wrap; gap: 8px; }
#datetime-weekday { font-size: clamp(14px, 1.15vw, 22px); font-weight: 600; color: #fff; letter-spacing: .02em; text-shadow: 0 2px 12px oklch(0 0 0 / .5); }
#datetime-date { margin-top: 6px; font-size: clamp(20px, 1.875vw, 36px); font-weight: 700; color: #fff; letter-spacing: -.01em; text-shadow: 0 2px 12px oklch(0 0 0 / .5); }
#datetime-time { color: #fff; font-size: clamp(40px, 6.25vw, 120px); font-weight: 800; line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums; text-shadow: 0 2px 16px oklch(0 0 0 / .5); }

.content-row { flex: 1; min-height: 0; display: flex; gap: 22px; }
.content-left { flex: 4 1 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: 22px; }
.main-grid { flex: 1; min-height: 0; display: grid; grid-template-rows: minmax(0, 1fr); gap: 22px; }
.glass-card, .rail-card { color: var(--ink); background: var(--glass); border: 1px solid var(--glass-border); backdrop-filter: blur(14px) saturate(1.15); }
.primary-card { position: relative; min-width: 0; min-height: 0; overflow: hidden; display: flex; flex-direction: column; padding: clamp(16px, 1.7vw, 32px); border-radius: 28px; box-shadow: 0 20px 60px oklch(0 0 0 / .35); }
.card-glow { position: absolute; right: -50px; bottom: -60px; width: 180px; height: 180px; border-radius: 50%; pointer-events: none; }
.climate-card .card-glow { background: radial-gradient(circle, oklch(0.7 0.13 232 / .16), transparent 68%); }
.transit-card .card-glow { background: radial-gradient(circle, oklch(0.72 0.12 155 / .16), transparent 68%); }
.weather-card .card-glow { top: -60px; bottom: auto; width: 200px; height: 200px; background: radial-gradient(circle, oklch(0.78 0.13 75 / .16), transparent 68%); }
.card-heading { position: relative; z-index: 1; display: flex; align-items: center; gap: 14px; }
.card-heading h2 { margin: 0; font-size: clamp(16px, 1.15vw, 22px); font-weight: 700; }
.card-heading p { margin: 2px 0 0; color: var(--muted); font-size: clamp(12px, .8vw, 15px); }
.transit-route { position: relative; display: grid; grid-template-columns: 10px 1fr; align-items: center; row-gap: 4px; column-gap: 10px; }
.transit-route::before { content: ""; position: absolute; left: 4px; top: 14px; bottom: 10px; width: 0; border-left: 2px dotted var(--muted); opacity: .5; }
.transit-route-dot { position: relative; z-index: 1; box-sizing: border-box; width: 8px; height: 8px; border-radius: 50%; justify-self: center; }
.transit-route-dot--origin { background: var(--ink); }
.transit-route-dot--dest { background: transparent; border: 2px solid var(--muted); }
.transit-route-name { line-height: 1.15; }
.transit-route-name--origin { color: var(--ink); font-size: clamp(16px, 1.15vw, 22px); font-weight: 700; }
.transit-route-name--dest { color: var(--muted); font-size: clamp(12px, .8vw, 15px); font-weight: 500; }
.large-icon { position: relative; width: clamp(38px, 2.7vw, 52px); height: clamp(38px, 2.7vw, 52px); flex: 0 0 clamp(38px, 2.7vw, 52px); display: grid; place-items: center; border-radius: 16px; }
.climate-icon { color: oklch(0.78 0.1 232); background: oklch(0.4 0.09 232 / .35); }
.climate-icon span { position: relative; width: 11px; height: 24px; border: 3px solid currentColor; border-radius: 8px 8px 5px 5px; }
.climate-icon span::after { content: ""; position: absolute; width: 15px; height: 15px; left: -5px; bottom: -8px; border-radius: 50%; background: currentColor; }
.transit-icon { color: oklch(0.8 0.1 155); background: oklch(0.42 0.09 155 / .35); }
.transit-icon span { position: relative; width: 24px; height: 20px; border: 3px solid currentColor; border-radius: 6px; }
.transit-icon span::after { content: ""; position: absolute; left: 2px; bottom: -6px; width: 5px; height: 5px; border-radius: 50%; background: currentColor; box-shadow: 12px 0 currentColor; }
.weather-chip { background: oklch(0.44 0.1 75 / .35); }
.weather-chip img { width: 30px; height: 30px; }
.primary-value-block { position: relative; z-index: 1; flex: 1; display: flex; flex-direction: column; justify-content: center; }
.primary-value { font-size: clamp(30px, 3.75vw, 72px); font-weight: 800; letter-spacing: -.02em; white-space: nowrap; }
.primary-sub { margin-top: 8px; color: var(--muted); font-size: clamp(14px, 1.15vw, 22px); }
.departure-list { position: relative; z-index: 1; flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 14px; font-variant-numeric: tabular-nums; }
.departure-list > div { display: flex; justify-content: space-between; padding-bottom: 12px; border-bottom: 1px solid oklch(1 0 0 / .08); font-size: clamp(16px, 1.46vw, 28px); font-weight: 600; }
.departure-list > div:last-child { padding-bottom: 0; border-bottom: 0; }
.weather-card, #transit-widget { cursor: pointer; }
.weather-card:focus-visible, .rail-card:focus-visible, .profile-card:focus-visible, #transit-widget:focus-visible, .rotating-bar:focus-visible, .news-item:focus-visible { outline: 3px solid oklch(0.78 0.1 232); outline-offset: -4px; }
.weather-current { position: relative; z-index: 1; flex: 1; display: flex; align-items: center; gap: clamp(14px, 1.46vw, 28px); }
.weather-icon { width: clamp(56px, 5.7vw, 110px); height: clamp(56px, 5.7vw, 110px); }
.weather-value { font-size: clamp(32px, 3.96vw, 76px); font-weight: 800; line-height: 1; letter-spacing: -.02em; white-space: nowrap; }
.weather-mini-forecast { position: relative; z-index: 1; display: flex; gap: clamp(10px, 1.25vw, 24px); min-height: 66px; padding-top: 20px; border-top: 1px solid oklch(1 0 0 / .08); }
.mini-forecast-day { flex: 1; min-width: 0; display: flex; align-items: center; gap: 12px; }
.mini-forecast-day img { width: 30px; height: 30px; }
.mini-forecast-label { color: var(--muted); font-size: 15px; }
.mini-forecast-temp { margin-top: 2px; font-size: 19px; font-weight: 600; white-space: nowrap; }

.right-rail { flex: .95 1 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: 22px; }
.rail-subgrid { min-height: 0; display: flex; flex-direction: column; gap: 22px; }
.rail-card { width: 100%; height: 100%; min-height: 0; min-width: 0; overflow: hidden; display: flex; flex-direction: column; justify-content: center; gap: 8px; padding: clamp(14px, 1.35vw, 26px); border-radius: 24px; text-align: left; cursor: pointer; }
.room-live-card, .maintenance-live-card, .ticket-live-card { padding: clamp(12px, .94vw, 18px) clamp(14px, 1.15vw, 22px); }
.rail-heading { display: flex; align-items: center; gap: 10px; }
.rail-icon { width: 34px; height: 34px; flex: 0 0 34px; display: grid; place-items: center; border-radius: 10px; }
.room-icon { color: oklch(0.78 0.1 232); background: oklch(0.4 0.09 232 / .35); }
.room-icon span { position: relative; width: 16px; height: 14px; border: 2px solid currentColor; border-radius: 4px; }
.room-icon span::before { content: ""; position: absolute; left: -2px; right: -2px; top: -5px; border-top: 2px solid currentColor; }
.maintenance-icon { color: oklch(0.82 0.1 75); background: oklch(0.45 0.11 75 / .35); }
.ticket-icon { color: oklch(0.8 0.12 300); background: oklch(0.42 0.14 300 / .35); }
.ticket-icon span { position: relative; width: 17px; height: 12px; border: 2px solid currentColor; border-radius: 3px; }
.ticket-icon span::before, .ticket-icon span::after { content: ""; position: absolute; top: 3px; width: 4px; height: 4px; border-radius: 50%; background: currentColor; }
.ticket-icon span::before { left: -4px; } .ticket-icon span::after { right: -4px; }
.rail-label { color: var(--muted); font-size: clamp(11px, .73vw, 14px); text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.rail-dots { display: flex; gap: 6px; flex: 0 0 auto; margin-left: auto; }
.rail-dots .dot { width: 6px; height: 6px; border-radius: 50%; background: oklch(0.16 0.01 250 / .18); }
.rail-dots .dot.active { background: var(--red); }
.rail-card strong { font-size: clamp(15px, 1.25vw, 24px); font-weight: 700; }
.room-live-card.free strong { color: var(--muted); font-size: clamp(14px, 1.04vw, 20px); font-weight: 600; }
.room-live-card:disabled { cursor: default; }
.time-pill { width: max-content; padding: 5px 12px; border-radius: 999px; color: var(--red); background: oklch(0.93 0.06 25); font-size: clamp(12px, .83vw, 16px); font-weight: 700; font-variant-numeric: tabular-nums; }
.rail-sub { color: var(--muted); font-size: clamp(12px, .78vw, 15px); }
/* Wraps instead of truncating: a fixed-height card (see layoutRailCards in
   app.js) has the vertical room for a short phrase like "Overdue by 17
   days" to wrap onto a second line, which reads better than an ellipsis
   mid-word and reflows more predictably while the column is resized. */
#maintenance-live-value { color: var(--red); font-size: clamp(14px, 1.15vw, 22px); font-weight: 700; white-space: normal; overflow-wrap: break-word; }
.ticket-live-card strong { font-size: clamp(14px, 1.15vw, 22px); color: var(--red); }

.profile-cards { flex: 0 0 190px; display: flex; gap: 18px; overflow-x: auto; overflow-y: hidden; }
.profile-card { flex: 0 0 min(364px, 86vw); min-width: 0; display: flex; flex-direction: column; gap: 10px; padding: 20px 22px; border-radius: 26px; color: var(--ink); cursor: pointer; background: oklch(0.96 0.005 250 / .58); border: 1px solid oklch(1 0 0 / .5); box-shadow: 0 16px 40px oklch(0 0 0 / .18); text-align: left; backdrop-filter: blur(14px) saturate(1.15); }
.profile-top { display: flex; align-items: center; gap: 14px; }
.avatar { width: 56px; height: 56px; flex: 0 0 56px; overflow: hidden; display: grid; place-items: center; border: 2px solid oklch(1 0 0 / .7); border-radius: 50%; color: oklch(0.98 0.005 250); background: oklch(0.4 0.09 232 / .4); font-size: 24px; font-weight: 700; }
.avatar-tone-2 { background: oklch(0.55 0.11 155 / .4); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.profile-info { min-width: 0; }
.profile-eyebrow { overflow: hidden; color: oklch(0.16 0.1 232); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; text-overflow: ellipsis; }
.profile-name { margin-top: 4px; overflow: hidden; color: var(--ink-strong); font-size: 24px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.profile-role { margin-top: 2px; color: var(--ink); font-size: 16px; }
.profile-fact { padding-top: 8px; border-top: 1px solid oklch(0.14 0.01 250 / .16); color: oklch(0.12 0.01 250); font-size: 15px; font-style: italic; }
.visitor-eyebrow { color: oklch(0.5 0.19 25); }
.visitor-details { display: flex; flex-direction: column; gap: 6px; padding-top: 8px; border-top: 1px solid oklch(0.14 0.01 250 / .16); }
.visitor-host-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.visitor-host-label { color: var(--muted); font-size: 15px; font-weight: 600; }
.visitor-host-value { min-width: 0; color: var(--ink-strong); font-size: 18px; font-weight: 600; }
.visitor-details .profile-fact { padding-top: 0; border-top: none; }
.visitor-card .avatar { background: oklch(0.58 0.22 25 / .35); }

/* margin-top (on top of .kiosk-shell's own gap) separates this from
   .content-row above, and the negative margin-top on .news-row/.fact-row
   below pulls the three ticker widgets closer together - together these
   read as one distinct group under the main dashboard grid instead of
   being evenly spaced like every other row in the stack. */
.announcement-row { flex: 0 0 clamp(60px, 7.4vh, 80px); display: flex; margin-top: 14px; }
.rotating-bar { flex: 1; display: flex; align-items: center; gap: clamp(10px, 1.85vw, 20px); min-width: 0; padding: clamp(10px, 1.3vw, 14px) clamp(16px, 3vw, 32px); border-radius: 24px; background: oklch(0.42 0.1 232 / .55); border: 1px solid oklch(1 0 0 / .45); overflow: hidden; cursor: pointer; backdrop-filter: blur(14px) saturate(1.15); }
.rotating-icon { display: none; }
.rotating-icon-chip { width: 44px; height: 44px; flex: 0 0 44px; border-radius: 14px; background: oklch(0.4 0.09 232 / .4); display: grid; place-items: center; }
.mail-glyph { position: relative; width: 24px; height: 20px; }
.mail-glyph::before { content: ""; position: absolute; left: 0; top: 5px; width: 7px; height: 10px; background: oklch(0.85 0.09 232); border-radius: 2px; }
.mail-glyph::after { content: ""; position: absolute; left: 6px; top: 0; width: 0; height: 0; border-top: 10px solid transparent; border-bottom: 10px solid transparent; border-left: 14px solid oklch(0.85 0.09 232); }
.rotating-eyebrow { flex: 0 0 auto; color: #fff; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; }
.rotating-divider { width: 1px; align-self: stretch; background: oklch(1 0 0 / .25); flex: 0 0 1px; }
.rotating-message { flex: 1; min-width: 0; color: #fff; font-size: clamp(18px, 1.67vw, 32px); font-weight: 600; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; opacity: 1; transition: opacity .3s ease; text-shadow: 0 1px 8px oklch(0 0 0 / .35); }
.rotating-message.fading { opacity: 0; }
.rotating-message.warning { color: var(--red); }
.rotating-dots { display: flex; flex: 0 0 auto; gap: 8px; }
.rotating-dots .dot { width: 8px; height: 8px; border-radius: 50%; background: oklch(1 0 0 / .3); }
.rotating-dots .dot.active { background: #fff; }

/* Sits between the announcement bar and the fact row - deliberately
   shorter than both, since it only ever needs to hold a topic label, a
   headline clamped to 2 lines, and a source/time meta line. flex-basis is
   vw-driven (not vh, unlike the other ticker rows) because the content
   that determines its height - .news-headline's font-size and .news-panel's
   padding - are themselves vw-clamped; a vh-based basis drifted out of
   sync with that and measured 10-24px too short for a real 2-line headline
   (Playwright, 1024-1920px wide), so a long headline forced the row taller
   than its flex-basis and pushed the fact row down. overflow: hidden is a
   backstop so that can't happen again even outside the measured range. */
.news-row { flex: 0 0 clamp(90px, calc(80px + 3vw), 138px); display: flex; margin-top: -8px; overflow: hidden; }
.news-panel { flex: 1; display: flex; align-items: center; gap: clamp(14px, 2vw, 28px); min-width: 0; padding: clamp(12px, 1.6vw, 20px) clamp(18px, 3vw, 32px); border-radius: 24px; background: var(--glass); border: 1px solid var(--glass-border); backdrop-filter: blur(14px) saturate(1.15); }
.news-icon-chip { flex: 0 0 40px; width: 40px; height: 40px; border-radius: 14px; background: oklch(0.4 0.1 170 / .35); color: oklch(0.3 0.12 170); display: grid; place-items: center; }
.news-item { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 3px; cursor: pointer; border-radius: 12px; }
.news-eyebrow { color: oklch(0.32 0.12 170); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; }
.news-headline { color: var(--ink); font-weight: 600; line-height: 1.25; font-size: clamp(16px, 1.2vw, 21px); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; opacity: 1; transition: opacity .3s ease; }
.news-headline.fading { opacity: 0; }
.news-meta { color: var(--muted); font-size: clamp(11px, .75vw, 13px); }
.news-divider { width: 1px; align-self: stretch; background: oklch(0.14 0.01 250 / .22); flex: 0 0 1px; }

.fact-row { flex: 0 0 clamp(140px, 18vh, 196px); display: flex; position: relative; margin-top: -8px; }
/* justify-content: flex-start (not center) so the label's position depends
   only on the panel's fixed padding, never on how many lines the following
   text wraps to — centering here caused the label to jump between facts
   of different lengths. */
.fact-panel { flex: 1; cursor: pointer; text-align: left; border: 1px solid oklch(1 0 0 / .45); background: var(--glass); display: flex; flex-direction: column; justify-content: flex-start; min-width: 0; padding: clamp(14px, 1.85vw, 20px) clamp(18px, 3vw, 32px); padding-top: clamp(20px, 2.4vw, 28px); border-radius: 24px; overflow: hidden; font: inherit; color: inherit; backdrop-filter: blur(14px) saturate(1.15); }
.fact-label { color: oklch(0.22 0.14 300); font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; margin-bottom: 12px; }
.fact-text { color: var(--ink); font-weight: 500; line-height: 1.35; font-size: clamp(17px, 1.4vw, 22px); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; opacity: 1; transition: opacity .3s ease; margin-bottom: 8px; }
.fact-text.fading { opacity: 0; }
/* margin-top: auto pushes the sub-line to consume all remaining flex space
   above it, anchoring it to the panel's bottom-right corner for both fact
   types alike (quote attribution and on-this-day's usually-empty sub);
   margin-bottom clears #fact-credit's pill, which sits absolutely
   positioned in that same corner (see #fact-credit below). */
.fact-sub { color: var(--muted); font-size: 16px; margin-top: auto; margin-bottom: 28px; text-align: right; }
.fact-expand-hint { font-size: 12px; font-weight: 600; color: oklch(0.35 0.14 300); }

/* Quote of the Day and On This Day share the same layout (label always on
   top) so the widget doesn't jump when it rotates between fact types; they're
   distinguished only by style: quote text is italic, and the on-this-day
   label uses --blue's hue rather than the quote label's violet. */
.fact-panel[data-type="quote"] .fact-text { font-style: italic; }
.fact-panel[data-type="onthisday"] .fact-label { color: oklch(0.22 0.14 232); }

/* ZenQuotes free-tier terms require attribution with a link back to
   zenquotes.io; sibling of the fact-panel button (not nested inside it,
   since buttons can't legally contain interactive <a> elements) but
   visually anchored to its corner, same subtle "fine print" idea as
   #background-credit below. */
#fact-credit { position: absolute; right: clamp(14px, 1.8vw, 22px); bottom: clamp(10px, 1.3vw, 14px); z-index: 2; padding: 3px 9px; border-radius: 999px; background: oklch(1 0 0 / .55); color: var(--muted); font-size: 11px; line-height: 1.3; backdrop-filter: blur(6px); }
#fact-credit a { color: inherit; text-decoration-color: oklch(0.16 0.01 250 / .45); }
#fact-credit:empty { display: none; }

.detail-dialog { width: min(680px, 92vw); max-height: 88vh; overflow: auto; padding: clamp(22px, 3.75vw, 36px) clamp(22px, 4.2vw, 40px); border: 0; border-radius: 26px; color: var(--ink); background: oklch(0.98 0.005 250); box-shadow: 0 30px 90px oklch(0 0 0 / .4); }
.detail-dialog::backdrop { background: oklch(0.1 0.02 250 / .55); }
.dialog-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; }
.dialog-label { color: oklch(0.4 0.02 250); font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; }
.dialog-header h2 { margin: 6px 0 0; font-size: 32px; }
.dialog-close { width: 44px; height: 44px; flex: 0 0 44px; border: 1.5px solid oklch(0.14 0.01 250 / .25); border-radius: 50%; background: transparent; color: oklch(0.3 0.02 250); font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.detail-list { margin-top: 26px; display: grid; gap: 14px; }
/* Prepended/appended into #operational-dialog-body only by showNewsDetails()
   - every other dialog (transit, rooms, tickets, etc.) only ever populates
   .detail-list rows, so these rules have no effect on them. */
.news-dialog-snippet { margin: 0 0 22px; font-size: 17px; line-height: 1.55; color: oklch(0.3 0.02 250); }
.news-dialog-snippet em { font-style: normal; font-weight: 700; background: oklch(0.86 0.14 95 / .55); padding: 0 3px; border-radius: 4px; }
.news-dialog-snippet-empty { color: oklch(0.5 0.02 250); font-style: italic; }
.news-dialog-qr { margin: 4px 0 22px; display: flex; align-items: center; gap: 16px; }
.news-dialog-qr img { border-radius: 8px; background: #fff; padding: 6px; }
.news-dialog-qr-caption { margin: 0; font-size: 15px; color: oklch(0.45 0.02 250); }
.detail-row, .profile-detail { display: flex; justify-content: space-between; gap: 20px; padding-bottom: 12px; border-bottom: 1px solid oklch(0.14 0.01 250 / .1); font-size: 18px; }
.detail-row span, .profile-detail span { color: oklch(0.45 0.02 250); }
.detail-row strong, .profile-detail strong { text-align: right; }
.weather-dialog { width: min(760px, 92vw); }
.forecast-list { margin-top: 22px; }
.forecast-row { display: grid; grid-template-columns: 100px 36px 1fr auto auto; gap: 14px; align-items: center; min-height: 62px; border-bottom: 1px solid oklch(0.14 0.01 250 / .1); }
.forecast-row img { width: 38px; height: 38px; }
.forecast-condition, .forecast-low { color: oklch(0.45 0.02 250); }
.forecast-temperatures, .forecast-rain { text-align: right; }
.profile-dialog-identity { display: flex; align-items: center; gap: 16px; }
.profile-dialog-photo { width: 72px; height: 72px; flex: 0 0 72px; overflow: hidden; display: grid; place-items: center; border-radius: 50%; color: #fff; background: oklch(0.5 0.14 232); font-size: 28px; font-weight: 700; }
.profile-dialog-photo img { width: 100%; height: 100%; object-fit: cover; }
.profile-dialog-role { margin-top: 3px; color: oklch(0.45 0.02 250); }
.profile-dialog-fact { margin-top: 24px; padding: 16px; border-radius: 12px; background: oklch(0.95 0.005 250); font-size: 17px; }
.profile-dialog-details { grid-template-columns: 1fr; }
.ticket-dialog { width: min(860px, 92vw); max-height: 80vh; overflow: auto; }
.ticket-detail-list { margin-top: 24px; display: grid; gap: 12px; }
.ticket-detail-row { width: 100%; display: grid; grid-template-columns: 110px 1fr auto; gap: 16px; align-items: center; padding: 14px 0; border: 0; border-bottom: 1px solid oklch(0.14 0.01 250 / .1); color: inherit; background: transparent; text-align: left; cursor: pointer; }
.ticket-detail-row:last-child { border-bottom: 0; }
.ticket-detail-row:hover { background: oklch(0.94 0.02 232 / .65); }
.ticket-detail-row:focus-visible { outline: 3px solid oklch(0.7 0.13 232); outline-offset: 2px; }
.ticket-number { color: oklch(0.42 0.02 250); font-size: 14px; font-weight: 700; }
.ticket-copy { min-width: 0; }
.ticket-title { display: -webkit-box; overflow: hidden; font-size: 18px; font-weight: 700; line-height: 1.25; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.ticket-meta { margin-top: 4px; color: oklch(0.45 0.02 250); font-size: 14px; }
.ticket-badge { padding: 5px 10px; border-radius: 999px; background: oklch(0.93 0.025 250); font-size: 12px; font-weight: 700; white-space: nowrap; }
.ticket-badge.overdue { color: oklch(0.48 0.2 25); background: oklch(0.93 0.06 25); }
.ticket-badge.due_today { color: oklch(0.48 0.14 65); background: oklch(0.94 0.06 75); }
.ticket-item-dialog { width: min(720px, 92vw); overflow: hidden; border-top: 7px solid oklch(0.65 0.04 250); }
.ticket-item-dialog.ticket-priority-urgent { border-top-color: oklch(0.58 0.22 25); }
.ticket-item-dialog.ticket-priority-high { border-top-color: oklch(0.67 0.17 50); }
.ticket-item-dialog.ticket-priority-medium { border-top-color: oklch(0.78 0.15 85); }
.ticket-item-dialog.ticket-priority-low { border-top-color: oklch(0.66 0.1 232); }
.ticket-item-dialog .dialog-label { color: oklch(0.43 0.08 250); font-size: 14px; }
.ticket-item-dialog .dialog-header h2 { max-width: 610px; margin-top: 10px; color: oklch(0.14 0.02 250); font-size: 32px; line-height: 1.2; overflow-wrap: anywhere; }
.ticket-item-badges { margin-top: 22px; display: flex; flex-wrap: wrap; gap: 10px; padding-bottom: 22px; border-bottom: 1px solid oklch(0.14 0.01 250 / .1); }
.ticket-state-badge { display: inline-flex; align-items: center; gap: 9px; padding: 8px 14px; border: 1px solid transparent; border-radius: 999px; font-size: 14px; font-weight: 750; }
.ticket-state-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; box-shadow: 0 0 0 3px currentColor; }
.ticket-state-badge.status-open { color: oklch(0.52 0.2 25); background: oklch(0.96 0.035 25); border-color: oklch(0.82 0.1 25); }
.ticket-state-badge.status-in_progress { color: oklch(0.48 0.14 245); background: oklch(0.95 0.035 245); border-color: oklch(0.82 0.08 245); }
.ticket-state-badge.status-waiting { color: oklch(0.54 0.14 75); background: oklch(0.96 0.045 80); border-color: oklch(0.84 0.1 80); }
.ticket-state-badge.status-closed { color: oklch(0.47 0.12 155); background: oklch(0.95 0.035 155); border-color: oklch(0.82 0.08 155); }
.ticket-state-badge.priority-urgent { color: oklch(0.5 0.22 25); background: oklch(0.94 0.055 25); border-color: oklch(0.78 0.14 25); }
.ticket-state-badge.priority-high { color: oklch(0.53 0.16 50); background: oklch(0.96 0.05 60); border-color: oklch(0.83 0.11 55); }
.ticket-state-badge.priority-medium { color: oklch(0.51 0.13 80); background: oklch(0.97 0.05 88); border-color: oklch(0.84 0.11 85); }
.ticket-state-badge.priority-low { color: oklch(0.45 0.11 232); background: oklch(0.96 0.035 232); border-color: oklch(0.82 0.08 232); }
.ticket-item-dialog .detail-list { gap: 0; }
.ticket-item-dialog .detail-row { align-items: center; min-height: 54px; padding: 13px 4px; }
.ticket-item-dialog .detail-row span { font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: .045em; }
.ticket-item-dialog .detail-row strong { max-width: 70%; color: oklch(0.19 0.02 250); font-size: 16px; }

#background-credit {
  position: fixed;
  right: clamp(10px, 1.67vw, 32px);
  bottom: clamp(10px, 1.25vw, 24px);
  z-index: 4;
  max-width: min(420px, 70vw);
  padding: 6px 10px;
  border-radius: 8px;
  color: oklch(1 0 0 / .92);
  background: oklch(0.08 0.01 250 / .58);
  box-shadow: 0 3px 12px oklch(0 0 0 / .2);
  font-size: 12px;
  line-height: 1.3;
  white-space: normal;
  backdrop-filter: blur(5px);
}
#background-credit:empty { display: none; }
#background-credit a { color: inherit; text-decoration-color: oklch(1 0 0 / .5); }
.hidden { display: none !important; }

/* Below ~900px there isn't room for the weighted side-by-side columns
   app.js computes (see updateMainGridColumns) to stay readable, so this
   breakpoint switches to a single stacked column instead. app.js checks
   the same 900px threshold and clears its inline width/flex overrides in
   that mode so these rules (rather than leftover inline styles) apply. */
@media (max-width: 900px) {
  .content-row { flex-direction: column; }
  .content-left, .right-rail { flex: 1 1 auto; width: 100%; }
  .main-grid { display: flex; flex-direction: column; }
  .primary-card { min-height: 190px; }
  .rail-card { min-height: 90px; }

  /* Every one of these containers stacks into what reads as a single
     vertical column of cards down here, so their gaps need to visually
     match each other and .kiosk-shell's own gap - otherwise the fixed
     22px/18px they use above the breakpoint (kept fixed there to match
     app.js's GRID_GAP/PROFILE_CARD_GAP column-width math) looks uneven
     next to the fluid gap everywhere else once that math no longer runs. */
  .content-row, .content-left, .main-grid, .right-rail, .rail-subgrid, .profile-cards {
    gap: var(--stack-gap);
  }

  /* Only one profile/visitor card is shown at a time down here (see
     profileRowCapacity in app.js) - let it take the full row width and
     match the other stacked cards' height instead of staying pinned to
     the short 190px strip it uses in the multi-card desktop row.
     flex-basis (set via the "flex" shorthand on the base .profile-card
     rule) wins over a plain "width" override for a flex item's size, so
     this has to override "flex" itself - a bare "width: 100%" here would
     be silently ignored and the card would stay at its desktop
     min(364px, 86vw) size. */
  .profile-cards { flex: 0 0 auto; overflow: visible; }
  .profile-card { flex: 1 1 100%; min-height: 190px; }

  /* The eyebrow label and divider are fixed chrome that eats a big chunk
     of a narrow phone's width before the actual message even starts,
     leaving too little room for it to stay readable. Dropping them (the
     icon alone still signals "this is an announcement") gives the message
     the width it needs, and the row allows up to 3 wrapped lines instead
     of clipping at a fixed short height. Height is still a fixed px (not
     "auto") sized for that worst case - measured live via Playwright at
     320-900px, a forced 3-line message tops out at 97px - so the row
     never grows/shrinks between a one-line and three-line message; short
     messages just leave more empty space instead of shrinking the row. */
  .rotating-eyebrow, .rotating-divider { display: none; }
  .announcement-row { flex: 0 0 104px; padding: 6px 0; }
  .rotating-message { -webkit-line-clamp: 3; }

  /* Only room for one headline down here - the second slot and its divider
     drop out rather than squeezing two illegibly narrow columns side by
     side, and the remaining slot gets an extra clamped line since there's
     no neighbor competing for width. Same fixed-height reasoning as
     .announcement-row above: a forced 3-line headline measured 121px
     across 320-900px widths (font is already pinned to its 16px clamp
     floor down here), so the row is fixed at that worst case rather than
     "auto" - otherwise the widget visibly grows/shrinks depending on
     which headline happens to be showing. */
  .news-item-secondary, .news-divider { display: none; }
  .news-row { flex: 0 0 128px; }
  .news-headline { -webkit-line-clamp: 3; }
}
