:root {
  --bg: #0c0c0e;
  --bg-elevated: #1c1c1f;
  --surface: #161618;
  --surface-hover: #1c1c1f;
  --border: #38383a;
  --border-muted: #2c2c2e;
  --text: #ffffff;
  --text-secondary: #c7c7cc;
  --text-muted: #a1a1a6;
  --text-disabled: #68686f;
  --primary: #d4d4dc;
  --primary-hover: #eaeaef;
  --primary-active: #b8b8c2;
  /* Text drawn on top of --primary. Pale silver needs near-black. */
  --on-primary: #0c0c0e;
  /* Raised from --border-muted so an off toggle is visible on a card. */
  --control-off: #6e6e78;
  --success: #30d158;
  --warning: #ff9f0a;
  --danger: #ff453a;
  --mono: "Berkeley Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular,
    Menlo, Monaco, Consolas, "Liberation Mono", monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  /* Long unbroken values (IPv6 addresses, Dhan IDs) must wrap rather than
     force the whole page to scroll sideways on a phone. */
  overflow-wrap: break-word;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 24px;
  border-bottom: 1px solid var(--border-muted);
}
.brand { font-weight: 700; font-size: 15px; color: var(--text); }
.brand:hover { text-decoration: none; color: var(--text); }
.brand span { color: var(--primary); }
.topnav { display: flex; gap: 20px; align-items: center; }
.topnav a { color: var(--text-muted); font-size: 13px; }
.topnav a:hover { color: var(--text); text-decoration: none; }

.container { max-width: 940px; margin: 0 auto; padding: 48px 24px 96px; }
.container.narrow { max-width: 440px; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 28px;
  margin-bottom: 20px;
}
.card h2 { font-size: 15px; font-weight: 600; margin-bottom: 6px; }
.card .sub { color: var(--text-muted); font-size: 13px; margin-bottom: 24px; }

label {
  display: block;
  font-size: 11px;
  color: var(--text-secondary);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.field { margin-bottom: 18px; }

input {
  width: 100%;
  background: var(--bg);
  /* Was --border, measured at 1.5:1 against the card. --control-off
     is 3.6:1 and is the same token the toggle already uses. */
  border: 1px solid var(--control-off);
  border-radius: 7px;
  color: var(--text);
  font-family: var(--mono);
  font-size: 14px;
  padding: 10px 12px;
  outline: none;
}
input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(212,212,220,0.18); }
input::placeholder { color: var(--text-disabled); }

button {
  width: 100%;
  background: var(--primary);
  /* Was #fff. On the pale silver primary that was 1.9:1, so the label was
     effectively invisible. Near-black is 15.9:1. */
  color: var(--on-primary);
  border: none;
  border-radius: 7px;
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 500;
  padding: 11px 16px;
  cursor: pointer;
}
button:hover { background: var(--primary-hover); }
button:active { background: var(--primary-active); }
button:disabled {
  background: var(--bg-elevated);
  color: var(--text-muted);
  border: 1px solid var(--border);
  cursor: not-allowed;
}

.alert {
  border-radius: 7px;
  padding: 11px 14px;
  font-size: 13px;
  margin-bottom: 18px;
  display: none;
}
.alert.show { display: block; }
.alert.warn {
  background: rgba(255,159,10,0.12);
  border: 1px solid var(--warning);
  color: #ffc466;
  font-weight: 500;
}
.alert.error { background: rgba(255,69,58,0.12); border: 1px solid var(--danger); color: #ff8a80; }
.alert.ok { background: rgba(48,209,88,0.12); border: 1px solid var(--success); color: #6ee88a; }
.alert.info { background: rgba(212,212,220,0.10); border: 1px solid var(--primary); color: #e6e6ec; }

.footer-link { text-align: center; font-size: 13px; color: var(--text-muted); margin-top: 20px; }

.status-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.stat { background: var(--bg-elevated); border: 1px solid var(--border-muted); border-radius: 8px; padding: 16px; }
.stat .k { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 6px; }
.stat .v { font-size: 14px; color: var(--text); word-break: break-all; }

.badge {
  display: inline-block;
  font-size: 11px;
  padding: 3px 9px;
  border-radius: 20px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 500;
}
.badge.ok { background: rgba(48,209,88,0.15); color: var(--success); }
.badge.warn { background: rgba(255,159,10,0.15); color: var(--warning); }
.badge.err { background: rgba(255,69,58,0.15); color: var(--danger); }
.badge.idle { background: var(--border-muted); color: var(--text-muted); }

.divider { border: 0; border-top: 1px solid var(--border-muted); margin: 26px 0; }
.mono-dim { color: var(--text-muted); font-size: 12px; }
.hidden { display: none !important; }

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  .status-grid { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  .container { padding: 32px 16px 64px; }
  .card { padding: 20px 16px; margin-bottom: 16px; }

  /* The topbar had no rules at all. A long email address pushed the Sign out
     link off the right edge, because flex children default to not shrinking
     below their content width. */
  .topbar { padding: 12px 16px; gap: 12px; flex-wrap: wrap; }
  .topnav { gap: 14px; min-width: 0; }
  .topnav #who {
    /* Truncates with an ellipsis instead of forcing the bar wider. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 46vw;
  }
  .brand { font-size: 15px; }

  /* The parameter rules stack: the switch no longer sits beside the label,
     and the input goes full width so it is not squeezed to an unusable size. */
  .rule { flex-wrap: wrap; gap: 10px; }
  .rule-body { min-width: 100%; }
  .rule input[type="number"],
  .rule input[type="time"],
  .rule select {
  /* Native selects ship a white background by default, which is the
     white box in the screenshot. Forced to the app's own greys. */
  color-scheme: dark; width: 100%; }

  /* Seven weekday chips will not fit one row on a phone. */
  .weekdays { gap: 8px; }
  .weekdays label { flex: 1 1 28%; justify-content: center; padding: 9px 6px; }

  #paramActions { flex-direction: column; align-items: stretch; }
  #paramActions .m-btn, #paramActions button { width: 100%; }

  .channel-head { flex-wrap: wrap; gap: 8px; }
  .channel input[type="tel"] { max-width: 100%; }
}

@media (max-width: 400px) {
  .container { padding: 24px 12px 48px; }
  .weekdays label { flex: 1 1 30%; font-size: 12px; }
}

/* --- dashboard: parameter rules --- */

.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.card-head h2 { margin: 0; }

.rule {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 18px 0;
  border-top: 1px solid var(--border-muted);
}
.rule:first-of-type { border-top: 0; }
.rule-body { flex: 1; min-width: 0; }
.rule-body > label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; }
.rule .hint { font-size: 12px; color: var(--text-muted); margin-top: 6px; line-height: 1.5; }
.rule input:disabled, .rule select:disabled { opacity: 0.45; cursor: not-allowed; }

.switch { position: relative; display: inline-block; width: 40px; height: 22px; flex: 0 0 40px; margin-top: 2px; cursor: pointer; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch span {
  position: absolute; inset: 0;
  /* Was --border-muted, about 1.4:1 against the card. */
  background: var(--control-off);
  border: 1px solid var(--control-off);
  border-radius: 22px;
  transition: background 0.15s;
}
.switch span::before {
  content: ""; position: absolute;
  height: 16px; width: 16px; left: 3px; bottom: 3px;
  /* Was #fff, which vanished against the pale silver on-state. */
  background: var(--on-primary);
  border-radius: 50%;
  transition: transform 0.15s;
}
/* This used to reference an "accent" variable that was never defined in this
   stylesheet, so a checked toggle had no fill and read as off. */
.switch input:checked + span {
  background: var(--primary);
  border-color: var(--primary);
}
.switch input:checked + span::before { transform: translateX(18px); }
.switch input:disabled + span { opacity: 0.45; cursor: not-allowed; }

.weekdays { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.weekdays label {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; padding: 7px 12px;
  /* Was --border-muted, 1.2:1 against the card. */
  border: 1px solid var(--control-off); border-radius: 6px;
  cursor: pointer; user-select: none;
}
.weekdays input:disabled + *, .weekdays label:has(input:disabled) { opacity: 0.45; cursor: not-allowed; }

#paramActions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding-top: 18px; border-top: 1px solid var(--border-muted); }

/* --- dashboard: channels --- */

.channel { padding: 18px 0; border-top: 1px solid var(--border-muted); }
.channel:first-of-type { border-top: 0; }
.channel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 6px; }
.channel .hint { font-size: 12px; color: var(--text-muted); line-height: 1.5; margin-bottom: 10px; }
.channel input[type="tel"] { max-width: 260px; margin-bottom: 10px; }
.channel code { background: var(--bg-elevated); padding: 1px 5px; border-radius: 4px; font-size: 12px; }
#tgLink { display: block; word-break: break-all; margin: 8px 0; font-size: 12px; }

/* Outlined secondary action. Referenced by dashboard.html for Connect
   Telegram, Connect WhatsApp, Save number, Lock and Unlock, but was never
   defined, so each fell back to the full-width primary style. */
button.secondary {
  background: var(--bg-elevated);
  color: var(--text);
  border: 1px solid var(--control-off);
  width: auto;
  display: inline-block;
  padding: 9px 16px;
  font-size: 13px;
}
button.secondary:hover { background: var(--surface-hover); border-color: var(--text-muted); }
button.secondary:active { background: var(--border-muted); }
button.secondary:disabled {
  background: var(--surface);
  color: var(--text-disabled);
  border-color: var(--border);
}

/* Five weekday chips fit one row comfortably; seven did not. */
.weekdays label { flex: 0 1 auto; }

/* A row of secondary buttons, so they sit side by side rather than stacking
   into full-width slabs. */
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* The lock and channel actions need a label above the controls. */
.control-label {
  display: block;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

/* --- per-rule lock control --- */

.rule-lock {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--border-muted);
  font-size: 12px;
}
.rule-lock .lock-date {
  width: auto;
  min-width: 150px;
  padding: 7px 10px;
  font-size: 12px;
  font-family: var(--mono);
}
.rule-lock button {
  width: auto;
  padding: 7px 14px;
  font-size: 12px;
}
.rule-lock-state {
  color: var(--text-muted);
  font-size: 12px;
  margin-right: auto;
}

/* Locked: the label states the release time, so the rule cannot be mistaken
   for one that is merely switched off. */
.rule-lock.is-locked .rule-lock-state {
  color: var(--warning);
  font-weight: 600;
}
.rule.is-locked { border-left: 3px solid var(--warning); padding-left: 14px; }

/* A locked rule's input is disabled. Dimmed further than the normal read-only
   state so it is obvious this one is deliberate rather than incidental. */
.rule.is-locked input,
.rule.is-locked select,
.rule.is-locked .switch span {
  opacity: 0.4;
}

@media (max-width: 640px) {
  .rule-lock { flex-direction: column; align-items: stretch; }
  .rule-lock .lock-date { width: 100%; min-width: 0; }
  .rule-lock button { width: 100%; }
  .rule-lock-state { margin-right: 0; }
  .rule.is-locked { padding-left: 10px; }
}

/* The date picker glyph.

   Three faults were here, all found by screenshotting the control and reading its
   computed style rather than by reading this stylesheet.

   The phone block below used to add padding: 8px to the indicator, to make the
   glyph easier to hit. Padding on this pseudo element does not grow a hit area
   around the glyph, it grows the element, and Chromium lays the indicator out to
   fill its input. The computed style read width 149.8px and height 48px: the
   indicator had become a block painting the input's dark background over both the
   text and the glyph, which is why the field looked empty and black.

   The invert filter is gone. color-scheme: dark below already makes Chromium draw
   the glyph light, so inverting a correct glyph is exactly what hid it. The two
   rules were fighting: one assumed a dark glyph and the other produced one.

   The hit area is grown with a transparent outline instead, which does not change
   the element's own box. */

input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: 0.75;
  cursor: pointer;
  /* Outline rather than padding: the indicator fills its input in Chromium, so
     padding here would stretch the element and hide the glyph it draws. */
  outline: 11px solid transparent;
  border-radius: 4px;
  transition: opacity 0.12s;
}

input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="date"]:focus::-webkit-calendar-picker-indicator {
  opacity: 1;
}

input[type="date"]:active::-webkit-calendar-picker-indicator {
  opacity: 0.6;
}

/* Firefox draws its own indicator, and the edit text needs to be told the control
   is dark rather than being painted by the browser's default light scheme. */
input[type="date"] { color-scheme: dark; }
input[type="date"]::-webkit-datetime-edit { color: var(--text); }

/* A locked row's date is read-only. renderLocks puts is-locked on the .rule, not
   on .rule-lock, so the previous .rule-lock.is-locked selector matched nothing and
   a locked row kept a live-looking picker beside a disabled input. */
.rule.is-locked .lock-date {
  color: var(--text-muted);
  cursor: not-allowed;
}

.rule.is-locked .lock-date::-webkit-calendar-picker-indicator {
  opacity: 0.2;
  pointer-events: none;
}

/* --- subscription card --- */

.commodity-toggle {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px;
  margin: 18px 0 22px;
  background: var(--bg-elevated);
  border: 1px solid var(--control-off);
  border-radius: 10px;
  cursor: pointer;
}
.commodity-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-box {
  position: relative;
  flex: 0 0 40px;
  width: 40px;
  height: 22px;
  margin-top: 2px;
  background: var(--control-off);
  border-radius: 22px;
  transition: background 0.15s;
}
.switch-box::before {
  content: "";
  position: absolute;
  width: 16px; height: 16px;
  left: 3px; bottom: 3px;
  background: var(--on-primary);
  border-radius: 50%;
  transition: transform 0.15s;
}
.commodity-toggle input:checked + .switch-box { background: var(--primary); }
.commodity-toggle input:checked + .switch-box::before { transform: translateX(18px); }
.commodity-label strong { display: block; font-size: 14px; margin-bottom: 3px; }
.commodity-label small { display: block; font-size: 12px; color: var(--text-muted); line-height: 1.5; }

.plan-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
.plan-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 20px 18px;
  background: var(--surface);
  border: 1px solid var(--border-muted);
  border-radius: 10px;
}
.plan-best { border-color: var(--control-off); }
.plan-tag {
  position: absolute;
  top: -10px; left: 16px;
  padding: 2px 9px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--primary);
  color: var(--on-primary);
  border-radius: 20px;
}
.plan-name { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
.plan-blurb { font-size: 12px; color: var(--text-muted); min-height: 30px; }
.plan-price { display: flex; align-items: baseline; gap: 4px; margin: 10px 0 2px; }
.plan-price b { font-size: 24px; font-weight: 600; letter-spacing: -0.02em; }
.plan-price span { font-size: 12px; color: var(--text-dim, var(--text-muted)); }
.plan-per-month { font-size: 11px; color: var(--text-muted); }
.plan-saving { font-size: 11px; color: var(--success); margin-top: 4px; min-height: 15px; }
.plan-commodity { font-size: 11px; color: var(--text-muted); margin-top: 4px; min-height: 15px; }
.plan-buy { margin-top: 14px; width: 100%; }

#payNote { margin-top: 16px; font-size: 12px; color: var(--text-muted); }

/* Shown when the worker is waiting for payment rather than running. */
#monitorGate {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 14px;
  margin-bottom: 16px;
  background: rgba(255,159,10,0.10);
  border: 1px solid var(--warning);
  border-radius: 8px;
  font-size: 13px;
  color: #ffc466;
}

@media (max-width: 900px) { .plan-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) {
  .plan-grid { grid-template-columns: 1fr; }
  .commodity-toggle { padding: 14px; }
}

/* ---- dashboard tabs -----------------------------------------------------
   The customer asked for a menu top right instead of one long page. Three
   panels, one visible at a time. */

.tabstrip {
  display: flex;
  gap: 4px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.tabstrip::-webkit-scrollbar { display: none; }

.tabstrip button {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--text-muted);
  font-size: 14px;
  font-weight: 500;
  padding: 12px 16px;
  cursor: pointer;
  white-space: nowrap;
  transition: color 120ms ease, border-color 120ms ease;
}

.tabstrip button:hover { color: var(--text); background: none; }

/* aria-selected is what a screen reader reads, so the class and the attribute
   are set together. Colour alone would not convey the state. */
.tabstrip button[aria-selected="true"] {
  color: var(--text);
  border-bottom-color: var(--text);
}

/* Keyboard users move between tabs with the arrow keys, so focus lands on the
   newly selected tab. It has to be visible or they cannot tell where they are. */
.tabstrip button:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: -4px;
  border-radius: 4px;
}

.tabpanel:focus { outline: none; }

/* On a phone three labels plus padding overflow, so the strip scrolls sideways
   rather than wrapping to three rows and pushing content off screen. */
@media (max-width: 640px) {
  .tabstrip { margin-bottom: 18px; }
  .tabstrip button { padding: 10px 12px; font-size: 13px; }
}

/* ---- form classes that were used but never defined ---------------------
   .field-row appeared three times in dashboard.html with no rule anywhere, and
   .param-actions twice, also with none. Labels and inputs therefore fell back to
   block flow and the save button landed wherever inline flow put it, which is
   what read as misalignment. Defined at every width, not just small ones. */

.field-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
}

.field-row label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

/* Inputs and selects default to a content-box width plus padding, which overflows
   any grid or flex track it is placed in. min-width:0 lets them shrink inside a
   track, and box-sizing keeps the border inside it. */
.field-row input,
.field-row select {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

.param-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 18px;
}

.param-actions button { width: auto; }

/* ---- collapsible menu -------------------------------------------------- */

.hamburger {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 0 10px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  flex: 0 0 auto;
}

/* The three lines. Width rather than height is animated so the cross forms from
   the same three elements rather than swapping in an icon. */
.hamburger span {
  display: block;
  width: 100%;
  height: 2px;
  background: var(--text);
  border-radius: 2px;
  transition: transform 160ms ease, opacity 120ms ease;
}

.hamburger:hover { background: var(--bg-elevated); }
.hamburger:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

/* aria-expanded drives the cross, so the visual state cannot drift from the
   state a screen reader announces. */
.hamburger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.hamburger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.hamburger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* The dimmed backdrop. Its real job is dismissal and telling the customer the
   panel is modal on a phone. */
.menu-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  opacity: 0;
  visibility: hidden;
  transition: opacity 160ms ease, visibility 160ms ease;
  z-index: 40;
}

.menu-scrim.is-open { opacity: 1; visibility: visible; }

.menu-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(420px, 100vw);
  background: var(--bg);
  border-left: 1px solid var(--border);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 20px 20px 64px;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 200ms ease, visibility 200ms ease;
  z-index: 50;
}

.menu-panel.is-open { transform: translateX(0); visibility: visible; }

.menu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
}

.menu-head h2 { margin: 0; font-size: 16px; }

/* Two sections, not tabs. The customer asked for a menu, so both are reachable
   from here and neither hides the other behind a tab. */
.menu-section { margin-bottom: 32px; }

.menu-section > h3 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin: 0 0 14px;
}

.menu-panel .card { padding: 20px; margin-bottom: 16px; }

/* While the panel is open the page behind must not scroll under the customer's
   thumb. Toggled from JS alongside the class, and restored on close. */
body.menu-open { overflow: hidden; }

/* ---- phones ------------------------------------------------------------
   The panel takes the full width rather than sitting 420px from the right,
   because a sheet narrower than the screen is worse than no menu at all. */

@media (max-width: 640px) {
  .menu-panel {
    width: 100%;
    border-left: none;
    padding: 16px 16px 72px;
  }

  /* 44px minimum, which is the smallest reliably tappable target. The 38px the
     buttons were at is below it. */
  .menu-panel button,
  .menu-panel input[type="tel"],
  .menu-panel input[type="email"],
  .menu-panel input[type="text"] {
    min-height: 44px;
  }

  .menu-panel .param-actions button { width: 100%; }

  /* The topbar email is the first thing to overflow on a narrow screen. */
  .topnav { gap: 12px; min-width: 0; }
  .topnav #who {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* At 400px the status and plan grids are still two columns, which leaves each
   cell about 150px. Long values such as the IPv6 address and the renewal date
   then wrap mid-token and look broken. */
@media (max-width: 480px) {
  .status-grid,
  .plan-grid { grid-template-columns: 1fr; }

  .container { padding: 32px 16px 72px; }
  .card { padding: 20px 16px; }

  .field-row input,
  .field-row select { font-size: 16px; }

  /* 16px stops iOS Safari zooming the viewport when a field is focused. */
  .menu-panel input,
  .menu-panel select { font-size: 16px; }
}

/* Short landscape, where a phone is wide but there is almost no height. The
   sheet scrolls internally, so it must not also be taller than the viewport. */
@media (max-height: 480px) and (orientation: landscape) {
  .menu-panel { padding-top: 12px; padding-bottom: 40px; }
  .menu-section { margin-bottom: 20px; }
}

/* ---- phone ergonomics, measured not guessed ----------------------------
   At 320-412px a real browser measured inputs at 38px with 14px text, date
   pickers 36px with 12px, buttons 30-38px, and topbar links 21-24px. 44px is the
   smallest reliable tap target.

   The 16px input text is not cosmetic: below it iOS Safari zooms the viewport
   when the field takes focus, which leaves the page scrolled sideways and the
   form misaligned after the customer taps it. */

@media (max-width: 640px) {
  input:not([type="checkbox"]):not([type="radio"]),
  select,
  textarea {
    font-size: 16px;
    min-height: 44px;
  }

  input[type="date"],
  input[type="time"] {
    font-size: 16px;
    min-height: 44px;
  }

  button {
    min-height: 44px;
  }

  /* Full width on a phone: a 38px button next to a full width input is harder to
     hit than either would be on its own row. */
  .param-actions button,
  .channel button,
  .plan button { width: 100%; }

  /* Topbar links are text links at 21px, which is well under the target. Padding
     them out keeps the visual weight and fixes the hit area. */
  .topnav a,
  .menu-link,
  .menu-panel a[href] {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 6px 2px;
  }

  /* The switch keeps its own size: its checkbox is visually hidden behind a styled
     span, so growing the box would break the control. */
  .switch { min-height: 44px; }
  .lock-set { min-height: 44px; }

  .menu-head button { min-height: 44px; }
}

/* The menu links are the primary navigation on a phone, so they get their own
   block rather than inheriting inline text-link sizing. */
.menu-link {
  display: block;
  color: var(--text);
  font-size: 16px;
  font-weight: 500;
  text-decoration: none;
  padding: 14px 0;
  border-bottom: 1px solid var(--border-muted);
}

.menu-link:hover { color: var(--text); text-decoration: none; }

.menu-section .hint { margin: 8px 0 0; }

/* ---- account: read-only values ------------------------------------------
   The customer asked for the details shown as text with a separate Edit button,
   rather than three live inputs on arrival. Styled to read as values: a muted
   label and plain text. A dashed underline would read as "editable", which is the
   opposite of what these are. */

.profile-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border-muted);
}

.profile-row:first-of-type { border-top: 1px solid var(--border-muted); }

.profile-k {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  flex: 0 0 auto;
}

.profile-v {
  font-size: 14px;
  color: var(--text);
  text-align: right;
  /* A long email or a long name must not push the row wider than the card. */
  min-width: 0;
  overflow-wrap: anywhere;
}

#editProfile { margin-top: 18px; }

/* ---- menu sign out ------------------------------------------------------
   Given a rule above it and no border of its own, so it does not look like a
   third destination to navigate to. Signing out ends the session, and styling it
   like a page link invites a mis-tap with real consequences. */

#menuLogout {
  color: var(--text-muted);
  border-bottom: none;
}

#menuLogout:hover { color: var(--text); }

.menu-section:has(#menuLogout) {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

@media (max-width: 640px) {
  .profile-row { min-height: 44px; }
  .profile-v { font-size: 15px; }
}

/* ---- back to dashboard --------------------------------------------------
   A real link rather than a script, so the middle-click and open-in-new-tab that
   people use on navigation all work. Sits above the card rather than below it,
   because a control under the fold on a short page is one most people never find. */

.backlink {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  margin-bottom: 18px;
  padding: 10px 14px 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
}

.backlink:hover { background: var(--bg-elevated); color: var(--text); text-decoration: none; }
.backlink:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

/* The connection rows sit under a heading so it is clear they are about the Dhan
   account rather than about the person. */
#dhanDetails {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}

#dhanDetails::before {
  content: "Dhan connection";
  display: block;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 4px;
}

/* ---- shell tap targets, every width --------------------------------------
   A browser measured these at 24px for the logo, 21px for sign out and 35px for
   the menu close button. The phone ergonomics block above is capped at 640px, so
   it never applied at 834px or 1280px.

   These are not phone problems. A 24px logo is a poor target on a laptop too, and a
   mouse does not make a 21px link a good one. So this sits outside any media query.

   Padding plus a min-height rather than a fixed height, so the type size is
   untouched and a longer label cannot be clipped. */

.brand,
.topnav a,
.menu-head button,
button.secondary {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.brand { padding: 8px 2px; }
.topnav a { padding: 10px 4px; }
.menu-head button,
button.secondary { padding: 10px 16px; }

/* The buttons that sit beside their own label rather than in a row of controls. */
#subExtend,
#saveProfile,
#editProfile,
.lock-set {
  min-height: 44px;
}

/* ---- risk rule, stacked for a phone -------------------------------------
   Measured before this change, at 375px: the input was about 170px wide while the
   date picker and Lock button kept their width, and the toggle read as a blob.

   The first attempt at this used grid-template-areas with "switch label", but the
   label is a child of .rule-body and not of .rule, so it was never a grid item of
   .rule and the second column stayed empty. It also padded the label wrapping the
   switch, which grew the switch from 22px to 66px and destroyed the pill shape.

   So no grid areas here. The switch sits on its own row above the label and input,
   which is what actually reads well at this width, and the switch keeps its real
   dimensions.

   The 44px hit area is a transparent pseudo element rather than padding on the
   switch, so the visible control is exactly the size it was designed to be. */

@media (max-width: 640px) {
  .rule {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 12px;
    row-gap: 10px;
    align-items: start;
    padding: 20px 0;
  }

  /* The hit area is 44px so the switch is comfortably tappable, but the visible
     control must stay exactly the size it was designed to be.

     The previous version gave the label padding to reach 44px and left the span on
     inset: 0, so the face inherited the taller box: measured at 40x44 with a 22px
     radius. That is not a switch, it is a blob, because a 22px radius on a 44px
     height makes the curves meet and the ends look rounded off.

     So the face is pinned to 22px and centred in the taller hit area instead. */
  .rule > .switch {
    margin-top: 0;
    padding: 11px 0;
    box-sizing: border-box;
    height: 44px;
    align-self: start;
  }

  .rule > .switch span {
    top: 11px;
    left: 0;
    right: 0;
    bottom: auto;
    height: 22px;
  }

  /* Keep the knob and its travel matched to a 22px face. */
  .rule > .switch span::before {
    bottom: 3px;
  }

  .rule > .switch input:checked + span::before {
    transform: translateX(18px);
  }

  .rule-body {
    min-width: 0;
  }

  .rule-body > label {
    margin-bottom: 8px;
  }

  /* The value gets the width, which is the control that matters. min-width:0 stops
     the padding from pushing it past the column. */
  .rule-body input,
  .rule-body select {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    font-size: 16px;
    min-height: 44px;
  }

  .rule-lock {
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    margin-top: 6px;
    padding-top: 14px;
  }

  .rule-lock .lock-date {
    flex: 1 1 140px;
    width: auto;
    min-width: 0;
    min-height: 44px;
    font-size: 16px;
  }

  .rule-lock .lock-set {
    flex: 0 0 auto;
    min-height: 44px;
    width: auto;
    padding: 10px 18px;
  }

  /* One line per rule now, but a long one still wraps rather than pushing the row
     wider than the screen. */
  .rule .hint {
    margin-top: 8px;
    font-size: 12.5px;
    line-height: 1.45;
    overflow-wrap: anywhere;
  }

  .rule.is-locked { padding-left: 12px; }
}

/* At 320px the weekday chips are the tightest thing on the page. */
@media (max-width: 400px) {
  .weekdays { gap: 8px; }
  .weekdays label { padding: 9px 10px; font-size: 12px; }
}

/* ---- sizes that are not phone-specific -----------------------------------
   The sweep found these still short at 768px and above, because their rules sat
   inside a max-width: 640px block and never applied there. A 38px input is a
   small target on a laptop as well as a phone, so these live outside any media
   query.

   This is the third time this scoping mistake has been made in this stylesheet:
   the shell links, then the risk controls, then this. The rule that belongs to
   every width belongs outside a width query. */

.rule-body input,
.rule-body select {
  min-height: 44px;
}

.rule-lock .lock-date {
  min-height: 44px;
}

/* The hamburger is a fixed 44x44, but it measured 6px at 768px because a flex
   parent gave it no width and collapsed its contents. flex: 0 0 auto keeps its
   size, and the topbar children stop shrinking below their own content so the
   logo, the address and the button cannot squeeze each other. */
.hamburger {
  flex: 0 0 44px;
  width: 44px;
  min-width: 44px;
  min-height: 44px;
  align-self: center;
}

.topbar .brand { flex: 0 0 auto; }

.topnav > * { flex: 0 0 auto; }

/* Text inside an input is what iOS zooms the viewport for, so the 16px minimum
   belongs wherever a text entry exists, not only on a phone. A tablet is a touch
   device too. */
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="password"],
input[type="number"],
input[type="time"],
input[type="date"] {
  min-height: 44px;
}

/* 16px on text entry at every width, not just on phones.

   The rule was capped at 768px on the reasoning that only touch devices zoom the
   viewport when a field takes focus. That is wrong in both directions that matter:
   a laptop trackpad pinch zooms too, and iPads and Android tablets in landscape
   report widths well above 768 while remaining touch devices.

   It was also inconsistent on its face. The same inputs already carry a 44px
   minimum height at every width, which is a touch-first decision, so having the
   height everywhere and the text only on phones was the same scoping mistake run
   backwards.

   14px is the browser default for form controls, so this raises the default rather
   than restyling anything. The 12px date pickers were monospace for compactness,
   but that is the one control a customer uses when precision matters most. */

input[type="text"],
input[type="email"],
input[type="tel"],
input[type="password"],
input[type="number"],
input[type="time"],
input[type="date"] {
  font-size: 16px;
}
