/* Funnel program board (internal view).
 *
 * Built on the app's existing theme variables (--bg, --text, --muted, --border,
 * --radius, --shadow, --danger) so it follows light and dark mode without a
 * second set of colour definitions.
 *
 * Accents stay in the blue-to-violet family. No green, no amber. The only red
 * is the destructive Delete action.
 */

#view-funnel {
  padding-bottom: 40px;

  /* One height for every interactive control in the view: selects, text
   * inputs, buttons, links styled as buttons, tabs. They were drifting between
   * 28px and 34px depending on which padding and line-height each rule happened
   * to use, which is what made a row read as a pile of loose boxes. Anything
   * clickable in this view sets height to this and nothing else. */
  --fn-ctl: 32px;
}

/* The first row of every cell sits in a band of the same height, so the top
 * line of all six columns shares one horizontal rule across the table. Cells
 * whose first item is a control get it for free; text cells use this. */
.fn-head {
  display: flex;
  align-items: center;
  min-height: var(--fn-ctl);
}

/* ------------------------------------------------------------ wave cards -- */

.fn-waves {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 22px;
}

.fn-wave-card {
  position: relative;
  overflow: hidden;
  padding: 16px 18px 18px;
  border-radius: var(--radius);
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
}

/* Glossy crystal top edge, matching the rest of the app's card treatment. */
.fn-wave-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg,
    var(--gold-deep, #7a5c17), var(--gold, #c8a54a), var(--gold-pale, #f3e2ad));
}
.fn-wave-card::after {
  content: '';
  position: absolute;
  inset: 3px 0 auto 0;
  height: 44%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0));
  pointer-events: none;
}
html.theme-dark .fn-wave-card::after {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0));
}

.fn-wave-name {
  position: relative;
  z-index: 1;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.fn-wave-count {
  position: relative;
  z-index: 1;
  margin: 4px 0 6px;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--text);
}

.fn-wave-meta,
.fn-wave-linked {
  position: relative;
  z-index: 1;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}
.fn-wave-linked { color: var(--gold, #c8a54a); font-weight: 600; }

/* -------------------------------------------------------------- toolbar -- */

.fn-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.fn-tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.fn-tab {
  height: var(--fn-ctl);
  box-sizing: border-box;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: color 160ms ease, border-color 160ms ease, background 160ms ease;
}
.fn-tab:hover { color: var(--text); }
.fn-tab.is-active {
  background: rgba(200, 165, 74, 0.10);
  border-color: rgba(200, 165, 74, 0.35);
  color: var(--gold, #c8a54a);
}

.fn-toolbar-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.fn-search {
  width: 220px;
  height: var(--fn-ctl);
  box-sizing: border-box;
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
}
.fn-search:focus {
  outline: none;
  border-color: rgba(200, 165, 74, 0.5);
  box-shadow: 0 0 0 3px rgba(200, 165, 74, 0.12);
}

/* --------------------------------------------------------------- status -- */

.fn-status {
  margin-bottom: 14px;
  padding: 11px 14px;
  border-radius: 10px;
  font-size: 13px;
  background: rgba(200, 165, 74, 0.08);
  border: 1px solid rgba(200, 165, 74, 0.20);
  color: var(--text);
}
/* Warnings read as "attention", not "error". Slate rather than amber, which
 * the brand rules rule out. */
.fn-status-warn {
  background: rgba(100, 116, 139, 0.12);
  border-color: rgba(100, 116, 139, 0.30);
}
.fn-status-ok {
  background: rgba(200, 165, 74, 0.10);
  border-color: rgba(200, 165, 74, 0.28);
}

/* ------------------------------------------------------------- calendar -- */

/* The whole grid runs on Apollo's gold and nothing else. Upload and stop are
 * not two colours, they are two weights of the same one: bright and filled
 * against deep and hollow. A second hue would have split the view's identity in
 * half for a distinction that weight already carries.
 *
 * Red survives in one place, the overdue notch, so red still means late. */
#view-funnel {
  --fn-upload: var(--gold, #c8a54a);
  --fn-stop: var(--gold-deep, #7a5c17);
}
html.theme-dark #view-funnel {
  /* Deep gold disappears into a near-black cell, so the hollow state borrows
     the mid tone and separates itself by fill instead. */
  --fn-stop: rgba(200, 165, 74, 0.45);
}

.fn-cal-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.fn-cal-nav {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The min-width is what stops the arrows sliding around as the month name
 * changes length (May against September). Centring the text inside that fixed
 * box is what makes the title sit midway between the two arrows instead of
 * hugging the left one. */
.fn-cal-title {
  margin: 0;
  min-width: 190px;
  text-align: center;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
}

.fn-cal-arrow {
  width: var(--fn-ctl);
  padding: 0;
  font-size: 17px;
  line-height: 1;
}

.fn-cal-legend {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--muted);
}
.fn-legend-item { display: inline-flex; align-items: center; gap: 6px; }

/* The two markers themselves, filled and hollow. Two gold squares would have
   said nothing, since the difference is the fill and not the colour. */
.fn-legend-swatch {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}
.fn-legend-swatch.is-upload { background: var(--gold, #c8a54a); }
.fn-legend-swatch.is-stop {
  background: none;
  box-shadow: inset 0 0 0 1px var(--gold-line, rgba(200, 165, 74, 0.40));
}
.fn-legend-hint { opacity: 0.75; }

/* ---- waiting tray ----
   The same rail as the Weekly Review queue's "On hold" shelf, deliberately:
   two places in Apollo park clients for later, and they should look like one
   idea rather than two features that happened to be built by different hands.
   Dashed border, mono label, logo-only chips that name themselves on hover.
   Sits between the month bar and the grid: close enough to the calendar to
   read as "these are not on it yet", and above the weekday row so it never
   competes with a day cell for the drop. */
.fn-tray {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 42px;
  margin: 0 0 12px;
  padding: 6px 14px;
  border: 1px dashed rgba(200, 165, 74, 0.38);
  border-radius: 14px;
  background: rgba(10, 9, 8, 0.72);
  transition: border-color 0.14s ease, background 0.14s ease;
}
html:not(.theme-dark) .fn-tray {
  border-color: rgba(122, 92, 23, 0.34);
  background: rgba(122, 92, 23, 0.05);
}
/* Lit while a card from the calendar hovers it, on its way back to waiting. */
.fn-tray.is-drop {
  border-color: var(--gold, #c8a54a);
  border-style: solid;
  background: rgba(200, 165, 74, 0.12);
  box-shadow: inset 0 0 0 1px rgba(230, 201, 119, 0.30), 0 0 22px rgba(200, 165, 74, 0.18);
}
.fn-tray-hint {
  font-size: 11.5px;
  font-style: italic;
  color: rgba(230, 201, 119, 0.45);
}
html:not(.theme-dark) .fn-tray-hint { color: rgba(122, 92, 23, 0.55); }

.fn-tray-label {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
  font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold, #c8a54a);
  opacity: 0.85;
}
html:not(.theme-dark) .fn-tray-label { color: var(--gold-deep, #7a5c17); }

/* The count is the reason to look at the rail at all, so it is a pill rather
   than another word in the label. */
.fn-tray-count {
  min-width: 18px;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 10px;
  letter-spacing: 0;
  text-align: center;
  color: #1a1508;
  background: var(--gold, #c8a54a);
}

/* Wraps rather than scrolls. A hidden queue is the thing this rail exists to
   prevent, so every waiting client stays visible even at fifteen of them. */
.fn-tray-slots {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  min-width: 0;
}

/* Parked chip: logo only, exactly like the review shelf. */
.fn-tray-chip {
  --hz: 1.95;
  position: relative;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: 10px;
  cursor: grab;
  transition: transform 0.16s ease, box-shadow 0.16s ease;
}
.fn-tray-chip:active { cursor: grabbing; }
.fn-tray-chip.is-dragging { opacity: 0.45; }

.fn-tray-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border-radius: 10px;
  overflow: hidden;
  background: linear-gradient(145deg, #2b2721, #14120e);
  box-shadow: inset 0 0 0 1px rgba(200, 165, 74, 0.45);
  font-size: 13px;
  font-weight: 700;
  color: rgba(230, 201, 119, 0.85);
}
.fn-tray-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 10px;
}
.fn-tray-chip:hover {
  transform: scale(var(--hz));
  z-index: 40;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.65);
}
.fn-tray-chip:hover .fn-tray-avatar {
  box-shadow: inset 0 0 0 1px var(--gold, #c8a54a), 0 0 18px rgba(200, 165, 74, 0.35);
}
/* The name only while enlarged, so the rail itself stays purely visual and a
   dozen waiting clients still fit on one line. Counter-scaled by 1/--hz so it
   reads at its normal size instead of being blown up with the logo. */
.fn-tray-chip::after {
  content: attr(data-name);
  position: absolute;
  top: calc(100% + calc(5px / var(--hz)));
  left: 50%;
  transform: translateX(-50%) scale(calc(1 / var(--hz)));
  max-width: 260px;
  padding: 3px 8px;
  border-radius: 8px;
  background: rgba(10, 9, 8, 0.94);
  box-shadow: 0 0 0 1px rgba(200, 165, 74, 0.30);
  font-size: 11px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #f0e6cf;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
.fn-tray-chip:hover::after { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .fn-tray-chip { transition: none; }
}

.fn-cal-dows,
.fn-cal {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
}

.fn-dow {
  padding: 4px 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.fn-cal { margin-top: 2px; }

.fn-day {
  min-height: 118px;
  padding: 7px 8px 9px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: border-color 140ms ease, background 140ms ease;
}

/* Days from the neighbouring months stay visible but recede: they exist to put
 * the 1st under the right weekday, not to be worked from. */
.fn-day.is-outside { opacity: 0.45; }

.fn-day.is-today {
  border-color: rgba(200, 165, 74, 0.55);
  box-shadow: inset 0 0 0 1px rgba(200, 165, 74, 0.22);
}

/* Drop target while a chip is being dragged over it. */
.fn-day.is-drop {
  border-color: var(--gold, #c8a54a);
  background: var(--gold-wash, rgba(200, 165, 74, 0.13));
}

.fn-day-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

.fn-day-num {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--muted);
}
.fn-day.is-today .fn-day-num { color: var(--gold, #c8a54a); }

.fn-day-today {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--gold, #c8a54a);
}

.fn-day-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

/* ------------------------------------------------------------ task rows -- */
/*
 * Flat, and on one grid.
 *
 * What read as messy was not the styling, it was the geometry: the client name
 * was allowed to wrap to two lines, so some rows in a day were three lines tall
 * and others were two, and no two markers sat on the same axis. Names here run
 * to "LN - Queshundra Crawford - Q&A Bouncy Attractions Ad Acct - EH Owned", so
 * wrapping was the normal case, not the exception.
 *
 * The name is now ONE line with an ellipsis. Every row is the same height, every
 * marker lands on the same axis, and a day reads as a list instead of a pile.
 * The full name is on the row's tooltip, where a truncated name belongs.
 *
 * No shadows, no glows, no gradients, no blur. The only thing distinguishing an
 * upload from a stop is whether its marker is filled or hollow, which is the
 * least ink that can carry the difference.
 */

.fn-task {
  position: relative;
  display: grid;
  grid-template-columns: 17px minmax(0, 1fr);
  align-items: center;
  gap: 9px;
  padding: 4px 5px;
  border-radius: 6px;
  cursor: grab;
  transition: background 140ms ease, opacity 140ms ease;
}

.fn-task:hover { background: rgba(200, 165, 74, 0.09); }
.fn-task.is-dragging { opacity: 0.35; cursor: grabbing; }

/* ---- the marker, which is also the button ---- */

.fn-task-orb {
  width: 17px;
  height: 17px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  font-family: inherit;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums lining-nums;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 140ms ease, box-shadow 140ms ease, color 140ms ease;
}
.fn-task-orb:focus-visible {
  outline: 1.5px solid var(--gold-bright, #e6c977);
  outline-offset: 2px;
}
.fn-task-orb:disabled { cursor: default; }

/* Filled: a step going up. */
.fn-task-upload .fn-task-orb {
  background: var(--gold, #c8a54a);
  color: var(--gold-ink, #1a1205);
}
.fn-task-upload:hover .fn-task-orb { background: var(--gold-bright, #e6c977); }

/* Hollow: a step going off. box-shadow rather than border so the ring sits
   inside the same 17px as the filled marker and the two line up exactly. */
.fn-task-stop .fn-task-orb {
  background: none;
  box-shadow: inset 0 0 0 1px var(--gold-line, rgba(200, 165, 74, 0.40));
  color: var(--gold, #c8a54a);
}
.fn-task-stop:hover .fn-task-orb {
  box-shadow: inset 0 0 0 1px var(--gold, #c8a54a);
}

/* Done. It stays on the day it happened so the month doubles as a record, it
   just stops asking for attention. */
.fn-task.is-done { opacity: 0.38; cursor: default; }
.fn-task.is-done:hover { background: none; }
.fn-task.is-done .fn-task-orb {
  background: none;
  box-shadow: inset 0 0 0 1px var(--gold-line, rgba(200, 165, 74, 0.40));
  color: var(--gold, #c8a54a);
}
.fn-task.is-done .fn-task-name { text-decoration: line-through; }

/* Late. A 2px rule outside the row, clear of everything else, so red still
   means late and only that. */
.fn-task.is-overdue::before {
  content: '';
  position: absolute;
  left: -3px;
  top: 6px;
  bottom: 6px;
  width: 2px;
  background: var(--danger, #ef4444);
}

/* ---- text ---- */

.fn-task-body { min-width: 0; }

.fn-task-name {
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--text);
  /* One line, always. This is what keeps every row the same height. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.fn-task-meta {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.35;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.fn-task-kind { color: var(--gold, #c8a54a); }
html:not(.theme-dark) .fn-task-kind { color: var(--gold-deep, #7a5c17); }
.fn-task-round { opacity: 0.6; }


/* Below this the seven columns stop being usable as a grid: a 150px cell that
 * has to hold a client name and two chips becomes unreadable. The calendar
 * turns into a single scrolling column of days, which is how a month reads on
 * a phone anyway. */
@media (max-width: 900px) {
  .fn-cal-dows { display: none; }
  .fn-cal { grid-template-columns: 1fr; }
  .fn-day { min-height: 0; }
  /* Empty days would be a wall of blank cards in one column. */
  .fn-day:not(:has(.fn-task)) { display: none; }
  .fn-day-head { border-bottom: 1px solid var(--border); padding-bottom: 5px; }
}

/* ---------------------------------------------------------------- table -- */

.fn-table-wrap {
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--bg);
  box-shadow: var(--shadow);
  overflow-x: auto;
  /* Scrollbars hidden at rest, per house rule. */
  scrollbar-width: none;
}
.fn-table-wrap::-webkit-scrollbar { height: 8px; }
.fn-table-wrap::-webkit-scrollbar-thumb {
  background: transparent;
  border-radius: 8px;
  transition: background 240ms ease;
}
.fn-table-wrap:hover { scrollbar-width: thin; }
.fn-table-wrap:hover::-webkit-scrollbar-thumb { background: rgba(15, 23, 42, 0.22); }
html.theme-dark .fn-table-wrap:hover::-webkit-scrollbar-thumb { background: rgba(226, 232, 240, 0.24); }

.fn-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  /* Fixed, with the columns below sized in percentages that add to 100.
   *
   * On auto layout the browser sized each column off whatever text landed in
   * it, then handed out the leftover width unevenly, which is what put ragged
   * gaps between the cells. Fixed keeps the same proportions at every width,
   * and every control fills its cell, so the only whitespace left in a row is
   * the cell padding. */
  table-layout: fixed;
  min-width: 1300px;
}

.fn-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 12px 14px;
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.fn-table tbody td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.fn-table tbody tr:last-child td { border-bottom: none; }
.fn-table tbody tr:hover { background: var(--bg-subtle); }

/* Adds to 100. Percentages, not pixels, so the proportions hold at any width
 * instead of the slack piling up in whichever column the browser picks. */
.fn-col-client   { width: 18%; }
.fn-col-plan     { width: 14%; }
.fn-col-wave     { width: 9%; }
.fn-col-stage    { width: 13%; }
.fn-col-creative { width: 22%; }
.fn-col-link     { width: 24%; }

.fn-client-name {
  font-weight: 600;
  color: var(--text);
  line-height: 1.35;
}
/* No top margin: the name above it already sits in a 32px band, which supplies
 * the breathing room. */
.fn-client-id {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------- controls -- */

.fn-select,
.fn-input {
  width: 100%;
  height: var(--fn-ctl);
  box-sizing: border-box;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 12.5px;
  font-family: inherit;
}
.fn-select { cursor: pointer; }
.fn-select:focus,
.fn-input:focus {
  outline: none;
  border-color: rgba(200, 165, 74, 0.5);
  box-shadow: 0 0 0 3px rgba(200, 165, 74, 0.12);
}

.fn-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: var(--fn-ctl);
  box-sizing: border-box;
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: border-color 160ms ease, background 160ms ease, color 160ms ease;
}
.fn-btn:hover { border-color: rgba(200, 165, 74, 0.45); color: var(--gold, #c8a54a); }
.fn-btn:disabled { opacity: 0.5; cursor: default; }
.fn-btn svg { width: 14px; height: 14px; }

.fn-btn-primary {
  background: linear-gradient(160deg, var(--gold-bright, #e6c977), var(--gold, #c8a54a));
  border-color: transparent;
  color: var(--gold-ink, #1a1205);
  box-shadow: 0 4px 14px rgba(200, 165, 74, 0.26);
}
.fn-btn-primary:hover { color: var(--gold-ink, #1a1205); filter: brightness(1.06); }

.fn-btn-danger { color: var(--danger); }
.fn-btn-danger:hover { border-color: var(--danger); color: var(--danger); }

/* -------------------------------------------------------------- creative -- */

.fn-creative {
  display: flex;
  /* 28px on purpose: it matches the gutter between two columns, which is the
   * 14px right padding of one cell plus the 14px left padding of the next. The
   * thumbnail then has the same air on both sides and sits centred between the
   * Stage select and the Drive input, instead of hugging the input. */
  gap: 28px;
  align-items: flex-start;
}

/* Landscape, not square, so it matches the 32px control height exactly and
 * still shows more image than a 32px square would. Square at 44px was the last
 * thing in a row sitting at its own height, which is what broke the line. */
.fn-thumb {
  flex: 0 0 48px;
  width: 48px;
  height: var(--fn-ctl);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
}
.fn-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Sized to hold "No file" and "No preview" on one line inside the 44px box.
 * Wrapped, these read as a broken thumbnail rather than an empty one. */
.fn-thumb-empty {
  font-size: 8.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 1.2;
  color: var(--muted);
  text-align: center;
  padding: 0 2px;
}

.fn-creative-right {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.fn-link-out {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--gold, #c8a54a);
  text-decoration: none;
}
.fn-link-out:hover { text-decoration: underline; }

/* ------------------------------------------------------------ link cell -- */

.fn-linkcell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}
/* Content width, not column width. Stretched, the gold Generate link button
 * repeated down 100+ rows became the loudest thing on the page and buried the
 * data it sits next to. */
.fn-linkcell > .fn-btn { align-self: flex-start; }
/* These two do need the full column: the action row relies on it to push
 * Delete to the far right, and the url row to truncate the token. */
.fn-linkcell > .fn-actions,
.fn-linkcell > .fn-url-row { align-self: stretch; }

.fn-url-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--fn-ctl);
}

/* Reads as the read-only field it is: same height, radius and border as the
 * inputs in the other columns, filling its cell. Floating at content width it
 * was one more loose shape in a row that already had too many. */
.fn-url {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  height: var(--fn-ctl);
  box-sizing: border-box;
  /* The token is meaningless past the first few characters, so it truncates
   * rather than pushing the Revoked pill out of the cell. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 10px;
  border-radius: 8px;
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  font-size: 11px;
  color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.fn-pill {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.fn-pill-off {
  background: rgba(100, 116, 139, 0.16);
  color: var(--muted);
}

/* Four equal segments filling the cell, so the group reads as one control
 * instead of four loose buttons with a void between them. Pushing Delete to
 * the far right with margin-left:auto opened exactly that void. */
.fn-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}
.fn-actions .fn-btn {
  flex: 1 1 0;
  min-width: 0;
  padding: 0 6px;
}

.fn-verify {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
.fn-verify input { accent-color: var(--gold, #c8a54a); cursor: pointer; }

/* The checkbox and the last-opened date share one line, separated by a dot. */
.fn-linkmeta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  align-self: stretch;
}
.fn-linkmeta .fn-seen::before {
  content: '.';
  margin-right: 8px;
  color: var(--border);
}

.fn-seen {
  font-size: 11px;
  color: var(--muted);
}

.fn-empty {
  padding: 34px;
  text-align: center;
  font-size: 13.5px;
  color: var(--muted);
}

/* ----------------------------------------------------------- responsive -- */

@media (max-width: 1100px) {
  .fn-waves { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .fn-waves { grid-template-columns: 1fr; }
  .fn-toolbar { align-items: stretch; }
  .fn-toolbar-actions { flex-wrap: wrap; }
  .fn-search { width: 100%; }
}

/* ====================================================== creative plan cell ==
 *
 * Per client: which budget tier they sit in, and what the account needs before
 * design time is spent on it. Computed in funnel.js, never stored.
 *
 * COLOUR. Severity runs red -> violet -> blue -> slate, which keeps the ramp
 * inside the house palette: the blue-to-violet family, with red reserved for
 * the two states that mean the account is actually broken. No amber, no green,
 * so it also survives red-green colour blindness. Every badge is labelled in
 * words too, so colour is reinforcement and never the only carrier of meaning.
 */

/* Neutral fallback for a badge with no tone class.
 *
 * :where() on purpose. It contributes zero specificity, so a tone class always
 * wins no matter where it sits in the file. Declaring the defaults inside
 * .fn-badge instead would silently beat the ramp below, because equal
 * specificity means the later rule wins and .fn-badge is defined last. */
:where(.fn-badge) {
  --fn-tone: 100 116 139;
  --fn-ink: #475569;
}

.fn-badge-s0 { --fn-tone: 220 38 38;   --fn-ink: #b91c1c; }
.fn-badge-s1 { --fn-tone: 220 38 38;   --fn-ink: #b91c1c; }
.fn-badge-s2 { --fn-tone: 139 92 246;  --fn-ink: #6d28d9; }
.fn-badge-s3 { --fn-tone: 59 130 246;  --fn-ink: #1d4ed8; }
.fn-badge-s4 { --fn-tone: 100 116 139; --fn-ink: #475569; }

/* Dark ink. The app marks the theme with html.theme-dark, which is what the
 * 1500-odd dark rules in styles.css key off. */
html.theme-dark .fn-badge-s0,
html.theme-dark .fn-badge-s1 { --fn-ink: #fca5a5; }
html.theme-dark .fn-badge-s2 { --fn-ink: #c4b5fd; }
html.theme-dark .fn-badge-s3 { --fn-ink: #93c5fd; }
html.theme-dark :where(.fn-badge),
html.theme-dark .fn-badge-s4 { --fn-ink: #cbd5e1; }

.fn-col-plan { width: 230px; }

/* Tier rides under the account id: stable context about the client, not an
 * action, so it stays quiet and uncoloured. */
.fn-tier {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  margin-top: 5px;
}
.fn-tier-key {
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(100, 116, 139, 0.10);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.fn-tier-meta { font-size: 10.5px; color: var(--muted); }

.fn-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgb(var(--fn-tone) / 0.12);
  border: 1px solid rgb(var(--fn-tone) / 0.34);
  color: var(--fn-ink);
  font-size: 11.5px;
  font-weight: 700;
  white-space: nowrap;
}
/* Same red as S0 because the account is broken either way, but hollow: the fix
 * is different, and less urgent than money that never leaves the door. */
.fn-badge-s1 {
  background: transparent;
  border-style: dashed;
}

.fn-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: rgb(var(--fn-tone));
}

.fn-plan-ads {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}
.fn-plan-act {
  margin-top: 1px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--muted);
}

/* -------------------------------------------------------- client dialog -- */
/*
 * Centred, dark, one gold hairline round the edge. The calendar behind it is
 * dimmed rather than blurred, so it stays legible: you are usually setting
 * "tell them first" because of a card that is still on screen.
 *
 * There is no Cancel and no corner X. Every field writes the moment it changes,
 * so there is no pending state to abandon, and offering Cancel would promise an
 * undo that does not exist. Done, Escape and the backdrop all just close it.
 */

.fn-panel {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 24px;
}
.fn-panel[hidden] { display: none; }

.fn-panel-scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.62);
}

.fn-panel-sheet {
  position: relative;
  width: min(540px, 100%);
  /* Widened only when the notebook column is actually mounted (funnel.js sets
     the flag), so a dialog without a client keeps the narrow single column. */
  transition: width 200ms ease;
  max-height: min(86vh, 780px);
  display: flex;
  flex-direction: column;
  border-radius: 18px;
  border: 1px solid var(--gold-line, rgba(200, 165, 74, 0.40));
  /* Warm near-black rather than the app's flat panel colour: a dialog floating
     over a dimmed page needs its own ground or it reads as a hole. */
  background: #0e0c08;
  /* One soft warm halo. Enough to lift the dialog off the dimmed page, which a
     floating surface genuinely needs, and nothing beyond that. */
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.60), 0 0 0 1px rgba(200, 165, 74, 0.06);
  overflow: hidden;
}
html:not(.theme-dark) .fn-panel-sheet {
  background: #fffdf8;
  box-shadow: 0 24px 70px rgba(60, 45, 10, 0.22), 0 0 0 1px rgba(200, 165, 74, 0.10);
}

.fn-panel-head {
  padding: 24px 26px 6px;
  text-align: center;
}

.fn-panel-name {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: #f4efe4;
}
html:not(.theme-dark) .fn-panel-name { color: #241d10; }

.fn-panel-sub {
  margin: 7px 0 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.55;
  color: var(--muted);
}

.fn-panel-body {
  padding: 18px 26px 4px;
  overflow-y: auto;
}

/* ---- fields ---- */

.fn-field { margin-bottom: 17px; }

.fn-field-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--gold, #c8a54a);
}
html:not(.theme-dark) .fn-field-label { color: var(--gold-deep, #7a5c17); }

.fn-field-hint {
  margin-top: 3px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}

.fn-field-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; }

.fn-field-input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 13px;
  border: 1px solid rgba(200, 165, 74, 0.22);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.30);
  color: #f4efe4;
  font-family: inherit;
  font-size: 13px;
  line-height: 1.4;
}
html:not(.theme-dark) .fn-field-input {
  background: rgba(255, 255, 255, 0.85);
  color: #241d10;
}
.fn-field-input::placeholder { color: rgba(160, 150, 130, 0.75); }
.fn-field-row .fn-field-input { margin-top: 0; }
.fn-field > .fn-field-input { margin-top: 8px; }
.fn-field-input:focus {
  outline: none;
  border-color: var(--gold, #c8a54a);
}
.fn-field-area { resize: vertical; min-height: 74px; }

.fn-field-open {
  flex: 0 0 auto;
  padding: 11px 15px;
  border: 1px solid rgba(200, 165, 74, 0.30);
  border-radius: 10px;
  color: var(--gold, #c8a54a);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.fn-field-open:hover { border-color: var(--gold, #c8a54a); }

/* ---- choices ---- */
/*
 * A segmented control: one track, equal columns, and a single pill that slides
 * between them. Two buttons that each fade their own gold in and out read as a
 * crossfade, which is what made switching feel muddy. One object moving reads
 * as one decision.
 *
 * Equal columns are what keep the movement clean. With them the pill only needs
 * translateX, so nothing animates a layout property and the rounded corners
 * never stretch on the way across.
 *
 * There is a real third state, "nobody has asked this client yet". The pill
 * fades out for it and holds its position rather than sliding home, so clearing
 * an answer does not look like picking the first one.
 */

.fn-choices {
  --fn-seg-count: 2;
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--fn-seg-count), 1fr);
  margin-top: 9px;
  padding: 3px;
  border: 1px solid rgba(200, 165, 74, 0.22);
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.30);
}
html:not(.theme-dark) .fn-choices { background: rgba(255, 255, 255, 0.85); }

.fn-choice-pill {
  position: absolute;
  z-index: 0;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px) / var(--fn-seg-count));
  border-radius: 9px;
  background: rgba(200, 165, 74, 0.16);
  box-shadow: inset 0 0 0 1px var(--gold, #c8a54a);
  opacity: 0;
  /* Exponential ease-out: fast off the mark, settling rather than stopping.
     Nothing bounces, because real objects decelerate smoothly. */
  transition: transform 340ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 180ms ease;
  pointer-events: none;
}
.fn-choices.has-pick .fn-choice-pill { opacity: 1; }

/* First paint only. Without it, opening the dialog on a client who already has
   an answer would animate the pill in from the left every time. */
.fn-choices.is-init .fn-choice-pill { transition: none; }

.fn-choice {
  position: relative;
  z-index: 1;
  padding: 9px 14px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  /* Slower than the pill and eased differently on purpose: the label should
     still be catching up as the pill lands, which is what stops the two reading
     as one hard switch. */
  transition: color 260ms ease;
}
.fn-choice:hover { color: #f4efe4; }
html:not(.theme-dark) .fn-choice:hover { color: #241d10; }
.fn-choice.is-on { color: var(--gold-pale, #f3e2ad); }
html:not(.theme-dark) .fn-choice.is-on { color: var(--gold-deep, #7a5c17); }
.fn-choice:focus-visible {
  outline: 2px solid var(--gold, #c8a54a);
  outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
  .fn-choice-pill, .fn-choice { transition: none; }
}

/* ---- footer ---- */

.fn-panel-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 26px 22px;
}

/* The only confirmation there is, since every control writes on change. Blank
   until something has actually been saved. */
.fn-panel-saved {
  font-size: 11.5px;
  color: var(--muted);
}
.fn-panel-saved.is-ok { color: var(--gold, #c8a54a); }
.fn-panel-saved.is-bad { color: var(--danger, #ef4444); }

.fn-panel-done {
  margin-left: auto;
  padding: 11px 30px;
  border: 1px solid var(--gold, #c8a54a);
  border-radius: 999px;
  background: rgba(200, 165, 74, 0.18);
  color: var(--gold-pale, #f3e2ad);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease;
}
.fn-panel-done:hover { background: rgba(200, 165, 74, 0.30); color: #fff8e6; }
html:not(.theme-dark) .fn-panel-done { color: var(--gold-deep, #7a5c17); }

/* ---- split: step fields | client notebook ----
   The dialog grew a second column rather than a second dialog, because the
   notes are what you are reading WHILE you fill the step in. Two panes side by
   side means no toggling and no losing your place. The sheet widens only when
   the notebook is actually mounted, so a dialog opened without a client keeps
   its original single-column width. */
.fn-panel[data-notebook="1"] .fn-panel-sheet { width: min(1020px, 100%); }
.fn-panel:not([data-notebook="1"]) .fn-panel-notebook { display: none; }

.fn-panel-split {
  display: flex;
  align-items: stretch;
  min-height: 0;         /* lets both columns scroll instead of the sheet growing */
  flex: 1 1 auto;
}
.fn-panel-split > .fn-panel-body {
  flex: 1 1 52%;
  min-width: 0;
}
.fn-panel-notebook {
  flex: 1 1 48%;
  min-width: 0;
  padding: 18px 22px 4px;
  overflow-y: auto;
  border-left: 1px solid rgba(200, 165, 74, 0.18);
  /* A shade off the sheet's ground so the two panes read as separate surfaces
     without needing a heavier divider. */
  background: rgba(255, 245, 220, 0.022);
}
html:not(.theme-dark) .fn-panel-notebook {
  border-left-color: rgba(122, 92, 23, 0.16);
  background: rgba(122, 92, 23, 0.03);
}
/* The notebook column carries no header of its own (compact scope), so the
   composer and entries sit straight against the top of the pane. */
.cnst-funnelnb-page { height: 100%; }

@media (max-width: 900px) {
  /* Below this the two panes would each be too narrow to write in, so they
     stack and the sheet scrolls as one column. */
  .fn-panel-split { flex-direction: column; }
  .fn-panel-notebook {
    flex: 0 0 auto;
    border-left: none;
    border-top: 1px solid rgba(200, 165, 74, 0.18);
    padding: 14px 26px 4px;
  }
  html:not(.theme-dark) .fn-panel-notebook { border-top-color: rgba(122, 92, 23, 0.16); }
}

@media (max-width: 520px) {
  .fn-panel { padding: 12px; }
  .fn-panel-sheet { max-height: 92vh; }
}
/* ---- sections ---- */
/* One line of gold, then a rule. It is the only thing telling you that the
   folder above changes every step and the preferences below do not, so it is
   worth the two lines of chrome it costs. */
.fn-section {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin: 4px 0 13px;
  padding-bottom: 7px;
  border-bottom: 1px solid rgba(200, 165, 74, 0.18);
}
.fn-section + .fn-field { margin-top: 0; }
.fn-field + .fn-section { margin-top: 22px; }

.fn-section-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--gold, #c8a54a);
}
html:not(.theme-dark) .fn-section-title { color: var(--gold-deep, #7a5c17); }

.fn-section-note {
  font-size: 10.5px;
  color: var(--muted);
}

/* A folder shown for reference rather than for editing: same box as an input so
   the column still lines up, but plainly not typeable. */
.fn-field-static {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  cursor: default;
}
.fn-field-static.is-empty { font-style: italic; }
