/* Per-screen layout. Everything visual that is not a reusable component. */

/* ==========================================================================
   Sign in
   ========================================================================== */
.signin {
  height: 100dvh; width: 100vw; overflow: auto;
  background: var(--page-grad);
  display: flex; align-items: center; justify-content: center;
  padding: 24px 20px;
}
.signin-card {
  width: 100%; max-width: 400px;
  background: var(--panel); border-radius: var(--r-shell);
  box-shadow: var(--shadow-shell);
  padding: clamp(24px, 4vh, 40px) clamp(20px, 3vw, 34px);
  display: flex; flex-direction: column; gap: 16px;
}
/* The wordmark is drawn for black, so on the white card it gets a black plate
 * of its own — the same lockup as the app's welcome screen. */
.signin-brand {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  background: #000; border-radius: var(--r-rail); padding: 26px 20px 20px;
}
/* Nudged right so "IrisReveal" is centred and the ™ overhangs, as in the app. */
.signin-brand .wordmark { width: min(220px, 72%); height: auto; transform: translateX(1.06%); }
.signin-brand .endorse {
  display: flex; align-items: center; gap: 8px;
  font-size: 10px; font-weight: 500; letter-spacing: .22em; text-transform: uppercase;
  color: rgba(255, 255, 255, .74);
}
.signin-brand .endorse img { width: 18px; height: auto; }
.signin-card h1 { margin: 6px 0 0; font-size: clamp(22px, 3vh, 28px); font-weight: 600; letter-spacing: -.01em; }
.signin-card .lede { margin: 0; font-size: 14px; color: var(--muted); line-height: 1.55; }
.signin-card .foot { font-size: 12px; color: var(--muted-faint); line-height: 1.5; }

.formerr {
  display: flex; gap: 8px; align-items: flex-start;
  background: var(--bad-soft); color: var(--bad-ink);
  border-radius: var(--r-btn); padding: 10px 12px;
  font-size: 13px; line-height: 1.45;
}
.formerr svg { flex: 0 0 auto; color: var(--bad); margin-top: 1px; }

/* ==========================================================================
   Submissions — list
   ========================================================================== */
.listhead {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; padding-bottom: clamp(6px, 1vh, 12px);
}
.rows {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 6px;
  overflow-y: auto; overscroll-behavior: contain; padding-right: 2px;
}
.row {
  display: grid;
  grid-template-columns: clamp(38px, 3.8vh, 44px) minmax(0, 1fr) auto auto auto 18px;
  align-items: center; gap: clamp(8px, 1.1vw, 16px);
  flex: 0 0 auto; min-height: clamp(56px, 7.2vh, 84px);
  padding: 8px clamp(8px, 1vw, 14px);
  border: 0; border-radius: var(--r-row);
  background: var(--sub); cursor: pointer; text-align: left; width: 100%;
  transition:
    background .16s var(--ease-soft),
    transform .16s var(--ease-soft),
    box-shadow .16s var(--ease-soft);
}
.row:hover {
  background: var(--accent-soft);
  /* A 1px rise, not a bounce. Enough to say "this responds to you" without
   * the list appearing to breathe as the pointer crosses it. */
  transform: translateY(-1px);
  box-shadow: var(--shadow-card);
}
.row[aria-current="true"] { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-ring); }
.row .who { display: flex; flex-direction: column; min-width: 0; gap: 2px; }
.row .who .n { font-size: var(--t-mid); font-weight: 500; }
.row .who .e { font-size: var(--t-small); color: var(--muted); }
/* The phone variant of the second line; mobile.css swaps which one shows. */
.row .who .meta { display: none; }
.row .count { font-size: var(--t-small); color: var(--ink-4); white-space: nowrap; }
.row .when { font-size: var(--t-small); color: var(--muted); white-space: nowrap; }
.row .chev { display: flex; color: var(--faint); }
@media (max-width: 1079px) {
  .row { grid-template-columns: clamp(38px, 3.8vh, 44px) minmax(0, 1fr) auto auto 18px; }
  .row .when { display: none; }
}

.pager {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding-top: 9px; border-top: 1px solid var(--line-3);
  font-size: var(--t-small); color: var(--ink-4);
}
.pager .grp { display: flex; gap: 6px; align-items: center; }

/* ==========================================================================
   Submissions — detail
   ========================================================================== */
.detail-head {
  flex: 0 0 auto; background: var(--panel); border-radius: var(--r-card);
  padding: clamp(9px, 1.3vh, 16px) clamp(11px, 1.1vw, 20px);
  display: flex; align-items: center; gap: clamp(8px, 1vw, 14px); flex-wrap: wrap;
}
.detail-head .who { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.detail-head .who .n { font-size: var(--t-h2); font-weight: 600; }
.detail-head .who .m { font-size: var(--t-small); color: var(--muted); }

.detail-grid {
  flex: 1 1 auto; min-height: 0;
  display: grid; gap: var(--gap-x);
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  /* An explicit row is what makes the columns fill the panel. Left implicit,
     the row sizes to its content and the cards stop halfway down. */
  grid-template-rows: minmax(0, 1fr);
  overflow: hidden;
}
@media (max-width: 859px) {
  .detail-grid { grid-template-columns: minmax(0, 1fr); grid-auto-rows: min-content; overflow-y: auto; overscroll-behavior: contain; }
  .detail-grid > .card { max-height: none; }
  .photo-groups, .side-col { overflow: visible; }
}

.photo-groups {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: clamp(8px, 1.2vh, 16px); padding-right: 2px;
}
.pgroup { display: flex; flex-direction: column; gap: 7px; }
.pgroup .ghead { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.pgroup .ghead .gn { font-size: var(--t-body); font-weight: 500; }
.pgrid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(clamp(76px, 9vw, 116px), 1fr));
}
.ptile {
  display: flex; flex-direction: column; gap: 5px;
  border: 0; background: transparent; padding: 0; text-align: left;
  cursor: pointer; min-width: 0;
}
.ptile[disabled] { cursor: default; }

/* The image sits in its own box so the hover scrim, the loading shimmer and
   the rounded corner all clip together and the caption stays outside them. */
.ptile .shot {
  position: relative; display: block; width: 100%; aspect-ratio: 1;
  border-radius: var(--r-nav); overflow: hidden; background: var(--sub-3);
  box-shadow: inset 0 0 0 1px var(--line-4);
  transition: transform .22s var(--ease), box-shadow .22s var(--ease);
}
.ptile .img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .3s var(--ease);
}
.ptile .shot.is-ready .img { opacity: 1; }
.ptile .shot.is-ready { box-shadow: inset 0 0 0 1px rgba(20, 19, 23, .10); }

/* A shimmer the shape of the tile, rather than an empty grey hole. */
.ptile .shot::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(100deg, var(--sub-3) 30%, #efeaf6 48%, var(--sub-3) 66%);
  background-size: 260% 100%;
  animation: shimmer 1.5s linear infinite;
  transition: opacity .3s var(--ease);
}
.ptile .shot.is-ready::before, .ptile .shot.is-empty::before { opacity: 0; animation: none; }

/* The zoom affordance: nothing until the pointer arrives, then a soft scrim
   and the expand mark. Hover is never the only route — the whole tile is a
   button, and it is reachable and openable from the keyboard. */
.ptile .peek {
  position: absolute; inset: 0; display: grid; place-items: center; color: #fff;
  background: linear-gradient(180deg, rgba(20, 19, 23, 0) 35%, rgba(20, 19, 23, .5) 100%);
  opacity: 0; transition: opacity .2s var(--ease);
}
.ptile .peek span {
  display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%;
  background: rgba(255, 255, 255, .18); backdrop-filter: blur(6px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3);
  transform: scale(.82); transition: transform .2s var(--ease);
}
.ptile:not([disabled]):hover .peek,
.ptile:not([disabled]):focus-visible .peek { opacity: 1; }
.ptile:not([disabled]):hover .peek span,
.ptile:not([disabled]):focus-visible .peek span { transform: scale(1); }
.ptile:not([disabled]):hover .shot { transform: translateY(-2px); box-shadow: var(--shadow-pop); }
.ptile:not([disabled]):active .shot { transform: translateY(0); }

/* No archive copy. Common on older submissions, so it is designed rather than
   apologised for: the iris mark, greyed, and the reason in words. */
.ptile .shot.is-empty {
  display: grid; place-items: center; color: var(--muted-faint);
  background:
    radial-gradient(circle at 50% 47%, #ded7ea 0 17%, #efeaf6 19% 44%, var(--sub-3) 46% 100%);
}
.ptile .cap { font-size: var(--t-micro); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ptile .cap.off { color: var(--faint); }

@keyframes shimmer { to { background-position: -160% 0; } }

.side-col {
  min-height: 0; display: flex; flex-direction: column; gap: var(--gap);
  overflow-y: auto; overscroll-behavior: contain; padding-right: 2px;
}

/* ==========================================================================
   The photograph viewer
   ==========================================================================
   Dark and translucent on purpose. It sits over a photograph, so the chrome
   has to stay legible without competing with it, and it must never be mistaken
   for the panel underneath.

   Its own token block: these are the only surfaces in the product that are
   light-on-dark, and giving them names keeps the greys from drifting apart
   across the header, the rail and the filmstrip. Every text pair below clears
   4.5:1 against the viewer's backdrop. */
.lightbox {
  --lb-ink:    #f6f3fa;
  --lb-dim:    #b3adc0;      /* 7.4:1 on the backdrop */
  --lb-glass:  rgba(255, 255, 255, .07);
  --lb-glass-2:rgba(255, 255, 255, .14);
  --lb-line:   rgba(255, 255, 255, .13);
  --lb-pad:    clamp(10px, 1.8vh, 20px);

  position: fixed; inset: 0; z-index: 150;
  display: flex; flex-direction: column; gap: var(--lb-pad);
  padding: var(--lb-pad);
  background: rgba(13, 12, 16, .88);
  backdrop-filter: blur(20px) saturate(120%);
  -webkit-backdrop-filter: blur(20px) saturate(120%);
  color: var(--lb-ink);
  animation: fadein .18s var(--ease);
}

/* A single soft brand wash behind the photograph, so the frame is not floating
   in flat black. Decorative only — it carries no meaning. */
.lb-glow {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(58% 46% at 50% 6%, rgba(139, 47, 248, .20) 0%, transparent 70%),
    radial-gradient(46% 40% at 12% 100%, rgba(139, 47, 248, .10) 0%, transparent 72%);
}
.lightbox > *:not(.lb-glow) { position: relative; }

/* --- Top bar ------------------------------------------------------------ */
.lb-top {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  padding: 8px 10px 8px 14px; border-radius: var(--r-head);
  background: var(--lb-glass); box-shadow: inset 0 0 0 1px var(--lb-line);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
.lb-mark {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, #17161b 0 22%, #8b2ff8 24% 54%, #d9bcff 56% 100%);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .18), 0 0 16px rgba(139, 47, 248, .5);
}
.lb-heading { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; gap: 1px; }
.lb-name {
  font-size: var(--t-body); font-weight: 500; letter-spacing: .01em;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.lb-person { font-size: var(--t-micro); color: var(--lb-dim); }
.lb-count {
  flex: 0 0 auto; font-size: var(--t-small); color: var(--lb-dim);
  padding: 4px 10px; border-radius: 999px; background: var(--lb-glass);
  box-shadow: inset 0 0 0 1px var(--lb-line); white-space: nowrap;
}
.lb-acts { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }

/* --- Buttons ------------------------------------------------------------ */
.lb-btn {
  flex: 0 0 auto; width: 38px; height: 38px;
  border: 0; border-radius: var(--r-btn); background: transparent; color: var(--lb-ink);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  transition: background .16s var(--ease), color .16s var(--ease);
}
.lb-btn:hover { background: var(--lb-glass-2); color: #fff; }
.lb-btn[disabled] { opacity: .32; cursor: default; background: transparent; }
.lb-btn[aria-pressed="true"] { background: rgba(139, 47, 248, .38); box-shadow: inset 0 0 0 1px rgba(217, 188, 255, .5); }
.lightbox :focus-visible { outline: 2px solid #d9bcff; outline-offset: 2px; }

/* --- Body: stage plus the details rail ---------------------------------- */
.lb-body { flex: 1 1 auto; min-height: 0; display: flex; gap: var(--lb-pad); }
.lb-stage {
  position: relative; flex: 1 1 auto; min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center;
}

/* Step buttons sit over the stage rather than beside it, so the photograph
   gets the full width. They are 46px, comfortably past the 44px target. */
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 46px; height: 46px; border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(22, 20, 27, .62); color: var(--lb-ink);
  box-shadow: inset 0 0 0 1px var(--lb-line), 0 6px 20px -8px rgba(0, 0, 0, .7);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .16s var(--ease), opacity .16s var(--ease);
}
.lb-prev { left: 6px; }
.lb-next { right: 6px; }
.lb-nav:hover { background: rgba(46, 41, 56, .85); }
.lb-nav[disabled] { opacity: .22; cursor: default; }

/* --- The photograph itself ----------------------------------------------
   The frame takes the whole stage and the photograph is contained inside it,
   rather than the frame being sized to the photograph. Fitting a box to an
   unknown aspect ratio inside two maximums is not something CSS can express
   without object-fit, and every attempt at it collapses the box before the
   file has loaded — which is exactly when the reserved space is needed.

   So the frame carries no visible chrome and the shadow goes on the image,
   where drop-shadow follows the real contained rectangle rather than the
   empty box around it. */
.lb-frame {
  position: relative; width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.lb-frame.is-zoomed { cursor: grab; }
.lb-frame.is-dragging { cursor: grabbing; }

.lb-img {
  display: block; width: 100%; height: 100%;
  object-fit: contain; user-select: none; -webkit-user-drag: none;
  opacity: 0; transform-origin: center center; will-change: transform;
  filter: drop-shadow(0 26px 60px rgba(0, 0, 0, .78));
  transition: opacity .32s var(--ease), transform .18s var(--ease);
}
.lb-frame.is-ready .lb-img { opacity: 1; }
/* A drag has to track the finger exactly; easing it would feel like lag. */
.lb-frame.is-dragging .lb-img { transition: opacity .32s var(--ease); }

/* Sized from the stored dimensions, so the shimmer is the shape of the
   photograph that is coming. Older submissions have no dimensions and fall
   back to 4:3. */
.lb-skeleton {
  position: absolute; height: 100%; max-width: 100%;
  aspect-ratio: var(--lb-ratio, 4 / 3); border-radius: var(--r-card);
  background: linear-gradient(100deg, rgba(255, 255, 255, .04) 30%, rgba(255, 255, 255, .11) 48%, rgba(255, 255, 255, .04) 66%);
  background-size: 260% 100%;
  box-shadow: inset 0 0 0 1px var(--lb-line);
  animation: shimmer 1.4s linear infinite;
  transition: opacity .3s var(--ease);
}
.lb-frame.is-ready .lb-skeleton, .lb-frame.is-broken .lb-skeleton { opacity: 0; animation: none; }

/* A signed link lasts an hour. When one expires the panel refetches, but the
   frame says so in the meantime rather than showing a broken-image glyph. */
.lb-broken {
  position: absolute; inset: 0; display: none;
  flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  color: var(--lb-dim); font-size: var(--t-small);
}
.lb-frame.is-broken .lb-broken { display: flex; }

/* --- Zoom pill ---------------------------------------------------------- */
.lb-zoom {
  position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); z-index: 2;
  display: flex; align-items: center; gap: 2px; padding: 4px;
  border-radius: 999px; background: rgba(22, 20, 27, .72);
  box-shadow: inset 0 0 0 1px var(--lb-line), 0 8px 24px -10px rgba(0, 0, 0, .8);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
.lb-zoom .lb-btn { width: 34px; height: 34px; border-radius: 999px; }
.lb-zoom-reset {
  display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 12px;
  border: 0; border-radius: 999px; background: transparent; color: var(--lb-ink);
  font-size: var(--t-micro); cursor: pointer;
  transition: background .16s var(--ease);
}
.lb-zoom-reset:hover { background: var(--lb-glass-2); }
.lb-zoom-reset[disabled] { color: var(--lb-dim); cursor: default; background: transparent; }
.lb-zoom-v { min-width: 34px; text-align: left; }

/* --- Details rail ------------------------------------------------------- */
.lb-rail {
  flex: 0 0 clamp(240px, 22vw, 310px); min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 3px;
  padding: 14px 16px; border-radius: var(--r-card);
  background: var(--lb-glass); box-shadow: inset 0 0 0 1px var(--lb-line);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  overscroll-behavior: contain;
}
.lb-rail-h {
  font-size: var(--t-micro); letter-spacing: .08em; text-transform: uppercase;
  color: var(--lb-dim); padding: 12px 0 6px;
}
.lb-rail-h:first-child { padding-top: 0; }
.lb-fact { display: flex; flex-direction: column; gap: 1px; padding: 5px 0; border-top: 1px solid rgba(255, 255, 255, .07); }
.lb-fact:first-of-type { border-top: 0; }
.lb-fact .k { font-size: var(--t-micro); color: var(--lb-dim); }
.lb-fact .v { font-size: var(--t-small); line-height: 1.45; }

/* Drive and the archive are independent: either can fail without the other,
   so both are stated with their own mark and never reduced to one verdict.
   The dot is never the only signal — the sentence beside it says the same. */
.lb-dest { display: flex; gap: 9px; align-items: flex-start; padding: 7px 0; }
.lb-dest .dot { flex: 0 0 auto; width: 8px; height: 8px; margin-top: 5px; border-radius: 50%; }
.lb-dest.is-good .dot { background: #4ade80; box-shadow: 0 0 0 3px rgba(74, 222, 128, .18); }
.lb-dest.is-waiting .dot { background: #fbbf24; box-shadow: 0 0 0 3px rgba(251, 191, 36, .18); }
.lb-dest .body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.lb-dest .l { font-size: var(--t-small); }
.lb-dest .n { font-size: var(--t-micro); color: var(--lb-dim); line-height: 1.4; overflow-wrap: anywhere; }

/* --- Filmstrip ---------------------------------------------------------- */
.lb-strip {
  flex: 0 0 auto; display: flex; gap: 7px; padding: 7px;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: thin;
  border-radius: var(--r-head);
  background: var(--lb-glass); box-shadow: inset 0 0 0 1px var(--lb-line);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
.lb-strip::-webkit-scrollbar { height: 6px; }
.lb-strip::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .2); border-radius: 8px; }
.lb-thumb {
  flex: 0 0 auto; width: clamp(46px, 6.4vh, 62px); height: clamp(46px, 6.4vh, 62px);
  padding: 0; border: 0; border-radius: var(--r-sm); overflow: hidden; cursor: pointer;
  background: rgba(255, 255, 255, .05); opacity: .5;
  box-shadow: inset 0 0 0 1px var(--lb-line);
  transition: opacity .18s var(--ease), transform .18s var(--ease), box-shadow .18s var(--ease);
}
.lb-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.lb-thumb:hover { opacity: .85; }
/* The current frame is marked by opacity, a ring and a lift together, so it is
   still obvious with colour perception out of the picture. */
.lb-thumb.is-current {
  opacity: 1; transform: translateY(-2px);
  box-shadow: 0 0 0 2px #d9bcff, 0 8px 18px -8px rgba(0, 0, 0, .9);
}

/* Someone who asked for less motion gets none of the decorative kind. The
   fades stay: they are how a photograph arriving is distinguished from one
   that failed. */
.lb-still, .lb-still * { animation: none !important; }
.lb-still .lb-skeleton { background: rgba(255, 255, 255, .06); }
.lb-still .lb-thumb, .lb-still .lb-img { transition: opacity .2s linear; }

@media (max-width: 1099px) {
  /* The rail becomes a sheet under the stage: at this width it would take a
     third of the photograph, and the photograph is the reason for the screen. */
  .lb-body { flex-direction: column; }
  .lb-rail { flex: 0 0 auto; max-height: 38vh; }
  .lightbox:not(.rail-open) .lb-rail { display: none; }
}

@media (max-width: 599px) {
  .lb-top { padding: 6px 6px 6px 10px; gap: 8px; }
  .lb-count { display: none; }
  .lb-nav { width: 44px; height: 44px; }
  .lb-strip { padding: 5px; gap: 5px; }
  .lb-thumb { width: 44px; height: 44px; }
}

/* ==========================================================================
   Analytics
   ========================================================================== */
/* The screen never scrolls. There is more here than fits at once, so it is
   split into tabs and each panel is sized to fill exactly the height left
   under the header — no nested scrollers, no content below a fold. */
.a-head {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; padding: 0 2px;
}
.a-head-right { display: flex; align-items: center; gap: 10px; min-width: 0; }

.a-tabs {
  display: flex; align-items: center; gap: 2px;
  background: var(--sub-3); border-radius: var(--r-btn); padding: 3px;
}
.a-tab {
  appearance: none; border: 0; cursor: pointer;
  /* 24px is the WCAG 2.2 pointer-target floor; the padding carries it past. */
  min-height: 32px; padding: 0 clamp(10px, 1.1vw, 16px);
  border-radius: calc(var(--r-btn) - 3px);
  background: transparent; color: var(--muted);
  font: inherit; font-size: var(--t-small); font-weight: 500;
  white-space: nowrap;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.a-tab:hover { color: var(--ink); }
.a-tab[aria-selected="true"] {
  background: var(--panel); color: var(--ink);
  box-shadow: 0 1px 2px rgba(20, 19, 23, .08);
}

.a-panel {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
  overflow: hidden;
}
/* A crossfade, because switching tabs replaces content inside one container
   rather than moving between places. */
.a-panel > * { animation: panel-in .22s var(--ease) both; }

@keyframes panel-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* --- Overview tab -------------------------------------------------------- */
.ov-wrap {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: var(--gap);
}
/* Side by side rather than stacked: two plots one above the other need more
   height than the viewport has once the tiles are in. */
.ov-charts {
  flex: 1 1 auto; min-height: 0;
  display: grid; gap: var(--gap-x);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ov-card { min-height: 0; }
.ov-card .chart-plot { height: auto; min-height: 0; }

@media (max-width: 859px) {
  /* One plot at this width, so the remaining one keeps a readable aspect. */
  .ov-charts { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
  .ov-charts > *:last-child { display: none; }
}

/* The headline band. Four across on a wide screen, two on a narrow one — a
   fifth column would push each figure below a comfortable reading size. */
.tilegrid {
  flex: 0 0 auto; display: grid; gap: clamp(7px, .8vw, 15px);
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1079px) { .tilegrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.tile {
  position: relative; overflow: hidden;
  background: var(--panel); border-radius: var(--r-tile);
  padding: clamp(10px, 1.3vh, 16px) clamp(12px, 1vw, 18px);
  display: flex; flex-direction: column; gap: 3px;
  min-height: clamp(96px, 12vh, 130px);
}
.tile .lab { font-size: var(--t-small); color: var(--ink-4); }
.tile .val {
  font-size: clamp(22px, 3vh, 32px); font-weight: 600;
  line-height: 1.1; letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.tile .delta {
  display: flex; align-items: baseline; gap: 4px;
  font-size: var(--t-micro); color: var(--muted); min-width: 0;
}
.tile .delta .arrow { font-size: 1.05em; line-height: 1; }
.tile .delta.good { color: var(--ok); }
.tile .delta.bad  { color: var(--bad); }

/* The sparkline sits behind the figure, bled to the tile edges. */
.tile-spark {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: clamp(30px, 4vh, 42px); pointer-events: none; opacity: .9;
}
.tile-spark .spark { display: block; width: 100%; height: 100%; }

/* Pending Drive and pending archive are the two numbers that mean something
   is actually wrong, rather than merely large. */
.tile.alarm { box-shadow: inset 0 0 0 1.5px #f6c9cc; }
.tile.alarm .val { color: var(--bad); }

/* --- Funnel tab ---------------------------------------------------------- */
.fn-wrap {
  flex: 1 1 auto; min-height: 0; display: grid; gap: var(--gap-x);
  grid-template-columns: minmax(0, 1fr) clamp(232px, 26%, 316px);
  grid-template-rows: minmax(0, 1fr);
}
.fn-main { min-height: 0; }
.fn-side {
  min-height: 0; display: flex; flex-direction: column; gap: var(--gap);
}
/* Takes the slack, so the column ends level with the steps beside it rather
   than leaving a panel of empty white below the last card. */
.fn-plat { flex: 1 1 auto; min-height: 0; overflow: hidden; }
.fn-plat .rowkv { align-items: baseline; }

/* --- Delivery tab -------------------------------------------------------- */
.dl-wrap {
  flex: 1 1 auto; min-height: 0; display: grid; gap: var(--gap-x);
  grid-template-columns: minmax(0, 1fr) clamp(250px, 30%, 360px);
  grid-template-rows: minmax(0, 1fr);
}
.dl-main { min-height: 0; }
.dl-side { min-height: 0; display: flex; flex-direction: column; gap: var(--gap); }
.dl-side > * { flex: 0 0 auto; }

/* The list of what is stuck. Scrolls only when there are more rows than the
   card is tall — which is a list of records, not the page, and is the one
   place a scrollbar is the right answer. */
.stucklist {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 5px;
  overflow-y: auto; overscroll-behavior: contain; padding-right: 2px;
}
.stuckrow {
  flex: 0 0 auto;
  display: grid; grid-template-columns: minmax(0, 1fr) auto 16px;
  align-items: center; gap: clamp(8px, 1vw, 14px);
  min-height: 46px; padding: 7px clamp(9px, .9vw, 14px);
  border: 0; border-radius: var(--r-row);
  background: var(--sub); cursor: pointer; text-align: left; width: 100%;
  transition: background .16s var(--ease);
}
.stuckrow:hover { background: var(--accent-soft); }
.sr-who { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.sr-who .t { font-size: var(--t-small); font-weight: 500; }
.sr-who .s { font-size: var(--t-micro); color: var(--muted); }
.sr-chev { display: flex; color: var(--faint); }

@media (max-width: 1079px) {
  .dl-wrap { grid-template-columns: minmax(0, 1fr) clamp(230px, 34%, 300px); }
}
@media (max-width: 859px) {
  .fn-wrap { grid-template-columns: minmax(0, 1fr); }
  /* The steps are the point of this tab; the summary card repeats what the
     rows already say, so it goes rather than squeezing both. */
  .fn-wrap .fn-side { display: none; }
  /* Same reasoning: the list is what somebody acts on. */
  .dl-wrap { grid-template-columns: minmax(0, 1fr); }
  .dl-wrap .dl-side { display: none; }
}

/* --- Chart ------------------------------------------------------------- */
.chart { position: relative; flex: 1 1 auto; min-height: 0; display: flex; }
.chart-plot { flex: 1 1 auto; min-width: 0; min-height: clamp(150px, 22vh, 260px); }
.chart-plot svg { display: block; }
.chart-axis {
  font-family: 'Outfit', sans-serif; font-size: 11px;
  fill: var(--muted); font-variant-numeric: tabular-nums;
}
.chart-empty {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center;
  font-size: var(--t-small); color: var(--muted); text-align: center;
  min-height: 140px;
}
.chart-tip {
  position: absolute; top: 0; left: 0; z-index: 2;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--dark); color: #fff;
  border-radius: 10px; padding: 7px 10px;
  pointer-events: none; white-space: nowrap;
  box-shadow: var(--shadow-pop);
}
.chart-tip .tip-k { font-size: 11px; color: #a19da9; }
.chart-tip .tip-v { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* A 2px surface gap between segments, so they read as separate without a
   border darkening the colours. */
.stackbar { display: flex; height: 12px; border-radius: 999px; overflow: hidden; gap: 2px; }
.stackbar > span { display: block; min-width: 3px; }
.stackbar > span:first-child { border-radius: 999px 0 0 999px; }
.stackbar > span:last-child { border-radius: 0 999px 999px 0; }
.stackbar > span:only-child { border-radius: 999px; }
.stackbar-empty { flex: 1; background: var(--accent-wash); border-radius: 999px; }

.chart-legend { display: flex; flex-direction: column; gap: 7px; }
.legend-item { display: flex; align-items: center; gap: 8px; font-size: var(--t-small); }
.legend-item .swatch { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 3px; }
.legend-item .legend-label { flex: 1 1 auto; min-width: 0; color: var(--ink-2); }
.legend-item .legend-value { color: var(--ink); font-weight: 600; }

.darkcard {
  flex: 0 0 auto; background: var(--dark); border-radius: var(--r-card);
  padding: clamp(9px, 1.3vh, 16px) clamp(11px, 1vw, 18px);
  display: flex; flex-direction: column; gap: 4px;
}
.darkcard.split { flex-direction: row; align-items: baseline; justify-content: space-between; gap: 14px; }
.darkcard.split .k { flex: 1 1 auto; min-width: 0; }
.darkcard.split .v { flex: 0 0 auto; text-align: right; }
.darkcard .k { font-size: var(--t-small); color: #a19da9; }
.darkcard .v { font-size: clamp(16px, 2.1vh, 21px); font-weight: 600; color: #fff; }
.darkcard .v.bad { color: #ff8f97; }

/* ==========================================================================
   Export
   ========================================================================== */
.exportgrid {
  flex: 1 1 auto; min-height: 0; display: grid; gap: var(--gap-x);
  grid-template-columns: clamp(240px, 27%, 340px) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  overflow: hidden;
}
@media (max-width: 859px) { .exportgrid { grid-template-columns: minmax(0, 1fr); overflow-y: auto; overscroll-behavior: contain; } }

.tablewrap { flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
table.data { width: 100%; border-collapse: collapse; font-size: var(--t-small); }
table.data th {
  position: sticky; top: 0; background: var(--panel); z-index: 1;
  text-align: left; font-weight: 500; color: var(--ink-4);
  padding: 0 12px 8px 0; border-bottom: 1px solid var(--line-4); white-space: nowrap;
}
table.data td {
  padding: 7px 12px 7px 0; border-bottom: 1px dashed #f1eef5;
  color: var(--ink-2); white-space: nowrap; max-width: 220px;
  overflow: hidden; text-overflow: ellipsis;
}
table.data th.num, table.data td.num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 20px; }

.json-pre {
  flex: 1 1 auto; min-height: 0; overflow: auto; margin: 0;
  background: var(--sub); border-radius: var(--r-row); padding: 14px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--t-micro); line-height: 1.55; color: var(--ink-2);
}
.json-pre .jk { color: var(--accent-deep); }
.json-pre .js { color: #1c6b45; }
.json-pre .jn { color: #9a4a12; }
.json-pre .jb { color: var(--bad); }

/* ==========================================================================
   Settings
   ========================================================================== */
.setgrid { flex: 0 0 auto; display: grid; gap: var(--gap-x); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 859px) { .setgrid { grid-template-columns: minmax(0, 1fr); } }

.health {
  flex: 0 0 auto; display: flex; align-items: flex-start; gap: 12px;
  background: var(--panel); border-radius: var(--r-head);
  padding: clamp(10px, 1.2vh, 15px) clamp(12px, 1.2vw, 20px);
}
.health .beacon { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; margin-top: 5px; }
.health .beacon.ok  { background: var(--ok);   box-shadow: 0 0 0 4px var(--ok-bg); }
.health .beacon.bad { background: var(--bad);  box-shadow: 0 0 0 4px var(--bad-bg); }
.health .t { font-size: var(--t-body); font-weight: 600; }
.health ul { margin: 5px 0 0; padding-left: 18px; font-size: var(--t-small); color: var(--ink-4); line-height: 1.6; }

.recip {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: var(--sub); border-radius: var(--r-btn); padding: 7px 8px 7px 12px;
}
.recip .e { font-size: var(--t-small); color: var(--ink); }
.recip > button {
  flex: 0 0 auto; width: 28px; height: 28px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
  color: var(--ink-4); display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.recip > button:hover { border-color: var(--bad); color: var(--bad); }
.setcard-head { display: flex; align-items: center; gap: 9px; }
.setcard-head .h3 { min-width: 0; }
.setcard-head .pill { margin-left: auto; }
.actionrow { display: flex; gap: 7px; flex-wrap: wrap; }

/* --- Capture funnel ------------------------------------------------------ */
/* Ten steps that must stay comparable at a glance, so every row is the same
   height and the bars share one scale: everyone who opened the app. */
/* The rows share whatever height the card has rather than adding up to a
   fixed total — that is what keeps ten steps inside the viewport without a
   scrollbar, at any window height. */
.funnel {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: clamp(2px, .5vh, 7px);
}
.funnel-step {
  flex: 1 1 0; min-height: 26px;
  display: grid;
  grid-template-columns:
    18px clamp(120px, 15vw, 190px) minmax(0, 1fr)
    clamp(38px, 4vw, 54px) clamp(40px, 4vw, 54px) clamp(72px, 7vw, 92px);
  align-items: center; gap: clamp(6px, .8vw, 13px);
  padding-left: 6px;
  border-radius: 7px;
  border-left: 2px solid transparent;
}

/* The step losing the most people, marked in the row rather than only in a
   caption — and by a border and a tinted pill together, never colour alone. */
.funnel-step.worst { border-left-color: var(--warn); background: var(--warn-bg); }

.fs-rank {
  font-size: var(--t-micro); color: var(--faint);
  text-align: right; font-weight: 500;
}
.fs-label { display: flex; flex-direction: column; min-width: 0; }
.fs-label .t { font-size: var(--t-small); font-weight: 500; }
.fs-label .s { font-size: var(--t-micro); color: var(--muted); }
.fs-track {
  display: flex; align-items: center;
  height: clamp(14px, 2.1vh, 20px);
  background: var(--sub-3); border-radius: 6px; overflow: hidden;
}
.fs-fill {
  display: block; height: 100%; border-radius: 6px;
  background: #c9a8f7;
  /* Grown from the left on first paint, staggered down the list so the shape
     of the funnel reads as it draws. Transform, not width, so it cannot
     trigger layout on ten rows at once. */
  transform-origin: left center;
  animation: fs-grow .5s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 35ms);
  transition: width .4s var(--ease);
}
.fs-fill.first { background: var(--accent); }

@keyframes fs-grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
.fs-count { font-size: var(--t-small); font-weight: 600; text-align: right; }
.fs-share { font-size: var(--t-micro); color: var(--muted); text-align: right; }
.fs-drop { justify-self: end; font-size: var(--t-micro); }

.funnel-note {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 11px; border-radius: var(--r-btn);
  background: var(--warn-bg); color: var(--warn);
  font-size: var(--t-small);
}
.funnel-note svg { flex: 0 0 auto; }

/* The conversion summary beside the steps. */
.fn-hero { display: flex; align-items: baseline; gap: 9px; min-width: 0; }
.fn-big {
  font-size: clamp(28px, 4.4vh, 44px); font-weight: 600;
  line-height: 1; letter-spacing: -.025em;
}
.fn-of { font-size: var(--t-small); color: var(--muted); white-space: nowrap; }

.fn-counts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 2px; }
.fn-stat {
  display: flex; flex-direction: column; gap: 1px;
  padding: 8px 10px; border-radius: var(--r-btn); background: var(--sub-3);
}
.fn-stat .v { font-size: var(--t-h3); font-weight: 600; line-height: 1.2; }
.fn-stat .k { font-size: var(--t-micro); color: var(--muted); }

/* Motion here is decoration on top of numbers that are already legible, so it
   is the first thing to go when the reader has asked for less of it. */
@media (prefers-reduced-motion: reduce) {
  .a-panel > *, .fs-fill { animation: none; }
  .fs-fill { transition: none; }
}

/* Columns are dropped from the least load-bearing inwards: the share of the
   top is derivable from the count, and the drop pill restates what the bar
   lengths already show. The label, the bar and the count always survive. */
@media (max-width: 1079px) {
  .funnel-step {
    grid-template-columns: 16px clamp(100px, 26vw, 150px) minmax(0, 1fr) 40px 78px;
  }
  .funnel-step .fs-share { display: none; }
}
@media (max-width: 759px) {
  .funnel-step { grid-template-columns: 16px 96px minmax(0, 1fr) 36px; }
  .funnel-step .fs-drop { display: none; }
  .fs-label .s { display: none; }
}
