/* ============================================================================
   Petra Cloud - product identity.

   The palette is taken from the mark itself rather than chosen alongside it:
   the navy of the wordmark and the amber of the instrument. Nothing else is
   introduced except the greys that carry them, one green for "done" and one
   red for "a person must act".

     navy   #161E66   authority, structure, every primary action
     amber  #DD8934   work in progress and attention, never alarm
     green  #1B7A4B   completion, used sparingly so it still means something
     red    #A8322D   only when somebody actually has to do something

   RESTRAINT IS THE BRIEF. No gradients, no glow, no shadowed text, no animation
   that is not reporting real progress. Confidence comes from space, alignment
   and calm wording - a survey instrument, not a developer dashboard.
   ============================================================================ */

:root{
  /* THREE ROLES, AND EACH COLOUR HAS EXACTLY ONE.
   *
   *   ORANGE  = Petra. Identity, the primary action, the active page, brand detail.
   *   BLUE    = the system working. Upload, progress, securing, processing.
   *   NEUTRAL = everything else - shell, surfaces, tables, text.
   *
   * The previous pass had navy owning the whole header and amber as the only accent,
   * which made the product read blue-first. Blue is not Petra's identity; it is what
   * Petra Cloud is DOING. That distinction is the whole design.
   */
  --orange:#DD8934;
  --orange-600:#C9782A;
  --orange-700:#B96C22;
  --orange-ink:#241402;
  --orange-soft:#8A5312;

  /* Process blue. Used for motion and state, never for the shell. */
  --blue:#3E72C9;
  --blue-600:#3564B4;
  --blue-soft:#25406E;

  --navy:#161E66;
  --navy-700:#1D2878;
  --navy-300:#5C68B8;

  /* Kept as aliases so existing rules keep working while the roles move. */
  --amber:var(--orange);
  --amber-ink:var(--orange-soft);
  --green:#1B7A4B;
  --red:#A8322D;

  /* KEYBOARD FOCUS. Its own token, because it is the one colour that has to stay
     visible against every surface in the product in both themes. Browsers default
     to a near-black ring, which on the dark navy background measured about 1.0:1 -
     a keyboard user could not see where they were, which fails WCAG 2.4.7. */
  --focus:#1D2878;
  --focus-halo:rgba(255,255,255,.92);

  --ink:#131A2E;
  --ink-2:#3B455F;
  --mut:#66708A;
  --line:#E3E7F0;
  --line-2:#EEF1F7;
  --bg:#F4F6FA;
  --card:#FFFFFF;
  --plate:#FFFFFF;

  /* The shell is a deep NEUTRAL, not a brand colour. Orange sits on it. */
  --shell:#141519;
  --shell-line:rgba(255,255,255,.09);
  --shell-ink:#F3F4F6;
  --shell-mut:rgba(243,244,246,.62);

  --accent:var(--orange);
  --accent-ink:var(--orange-ink);

  --radius:14px;
  --radius-sm:9px;
  --shadow:0 1px 2px rgba(19,26,46,.04), 0 6px 20px rgba(19,26,46,.05);
  --shadow-sm:0 1px 2px rgba(19,26,46,.05);
}

@media (prefers-color-scheme: dark){
  :root{
    /* NEUTRAL, NOT NAVY. The first dark palette made every large surface navy - page
       background, cards, header, tables - so the product read as one flat block of
       brand colour, which is what "generic dashboard" looks like. Navy is now
       structure and identity; the surfaces it sits on are neutral graphite. */
    --ink:#ECEEF2; --ink-2:#C3C8D2; --mut:#8A91A0;
    --line:#272B33; --line-2:#1E222A;
    --bg:#0D0F13; --card:#15181E; --plate:#FFFFFF;
    --navy:#2A3585; --navy-700:#35429E; --navy-300:#95A1E8;
    --orange:#E39A4C; --orange-600:#D08A3C; --orange-700:#BD7A2E;
    --orange-ink:#1A0E01; --orange-soft:#F0BE85;
    --blue:#5B8EDC; --blue-600:#4A7DCB; --blue-soft:#9DBDF0;
    --accent:var(--orange); --accent-ink:var(--orange-ink);
    --green:#3FA877; --red:#E0736C; --amber:var(--orange); --amber-ink:var(--orange-soft);
    --shadow:none; --shadow-sm:none;
    --focus:#A8B4FF;
    --focus-halo:rgba(6,9,20,.92);
  }
}

*{box-sizing:border-box}
/* The HTML `hidden` attribute sets display:none from the USER-AGENT stylesheet, which
   any author rule outranks. `.modal{display:flex}` therefore defeated `hidden` and the
   dialog rendered over the application on every page load. Author-level !important is
   the fix; it must come before the rules that set display. */
[hidden]{display:none !important}

body{
  margin:0; background:var(--bg); color:var(--ink);
  font:15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
h1{font-size:26px; line-height:1.25; letter-spacing:-.018em; font-weight:650; margin:0 0 4px}
h2{font-size:12.5px; letter-spacing:.09em; text-transform:uppercase; color:var(--mut);
   font-weight:700; margin:34px 0 12px}
h3{font-size:17px; letter-spacing:-.01em; font-weight:650; margin:0 0 6px}
p{margin:0 0 12px}
a{color:var(--accent); text-decoration:none}
a:hover{text-decoration:underline}
.sub{color:var(--mut); margin:0 0 20px}
.small{font-size:13.5px}
.tiny{font-size:12px}
.muted{color:var(--mut)}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:.92em}
.right{text-align:right}
.grow{flex:1}
.spread{display:flex; align-items:center; justify-content:space-between; gap:16px}
.row{display:flex; gap:10px; flex-wrap:wrap; align-items:center}
.hidden{display:none}

/* ---------------------------------------------------------------- the mark */
/* The logo is navy on white and has no dark variant, so it is always given a white
   plate. A mark that inverts badly is worse than a mark with a plate behind it. */
.mark{display:block; background:var(--plate); border-radius:10px; padding:10px 14px;
  box-shadow:var(--shadow-sm); line-height:0}
.mark img{display:block; height:34px; width:auto}
.mark-lg{padding:18px 26px; border-radius:14px}
.mark-lg img{height:76px}

/* ---------------------------------------------------------------- buttons */
button{font:inherit; cursor:pointer; border-radius:var(--radius-sm);
  border:1px solid transparent; padding:9px 16px; font-weight:560;
  letter-spacing:-.006em; transition:background .12s, border-color .12s, color .12s}
button:disabled{opacity:.45; cursor:default}
/* AMBER IS THE ACTION. Navy is identity and structure; the one thing on a screen
   that the employee is meant to press is the accent. The previous periwinkle came
   from a lightened navy and read as neither brand colour. */
.primary{background:var(--amber); color:#241402; border-color:var(--amber);
  font-weight:640}
.primary:hover:not(:disabled){background:#C9782A; border-color:#C9782A}
.primary:active:not(:disabled){background:#B96C22; border-color:#B96C22}
@media (prefers-color-scheme: dark){
  .primary{color:#1A0E01}
  .primary:hover:not(:disabled){background:#F0AC61; border-color:#F0AC61}
}
.ghost{background:transparent; color:var(--ink-2); border-color:var(--line)}
.ghost:hover:not(:disabled){border-color:var(--mut); color:var(--ink);
  background:var(--line-2)}
/* PROCESS: blue, and only where the button starts or concerns system activity. */
.process{background:transparent; color:var(--blue); border-color:var(--blue)}
.process:hover:not(:disabled){background:color-mix(in srgb, var(--blue) 12%, transparent)}
.danger{background:transparent; color:var(--red); border-color:var(--red)}
.danger:hover:not(:disabled){background:var(--red); color:#fff}
button.wide{width:100%; margin-top:20px}
.actions{display:flex; gap:6px; flex-wrap:wrap}
.actions button{padding:5px 11px; font-size:13px}
td .actions{justify-content:flex-end}

/* ---------------------------------------------------------------- inputs */
label{display:block; font-size:13px; font-weight:620; margin:16px 0 6px; color:var(--ink-2)}
input[type=text],input[type=password],input[type=number],textarea,select{
  width:100%; padding:10px 13px; border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--card); color:var(--ink); font:inherit; outline:none;
  transition:border-color .12s, box-shadow .12s}
input:focus,textarea:focus,select:focus{
  border-color:var(--navy-300); box-shadow:0 0 0 3px rgba(92,104,184,.16)}
.hint{font-size:12.5px; color:var(--mut); margin-top:6px}

/* ---------------------------------------------------------------- focus ring
   ONE rule for everything focusable. Previously only text inputs had a focus
   style, so buttons, links, tabs and the navigation fell through to the browser
   default - which in dark mode drew a near-black ring on a near-black surface.
   Two layers, always: a coloured ring plus a contrasting halo, so the ring is
   visible whether the element sits on the page, on a card, or on a filled
   button. `:focus-visible`, not `:focus`, so a mouse click does not draw it. */
:where(a,button,[role=button],[role=tab],input,textarea,select,summary,[tabindex]):focus-visible{
  outline:2px solid var(--focus);
  outline-offset:2px;
  box-shadow:0 0 0 4px var(--focus-halo), 0 0 0 6px var(--focus);
  border-radius:var(--radius-sm);
}
/* Windows High Contrast: the colours above are all overridden by the OS, and
   `outline` is the only thing that survives. Keep it, drop the halo. */
@media (forced-colors: active){
  :where(a,button,[role=button],[role=tab],input,textarea,select,summary,[tabindex]):focus-visible{
    outline:3px solid Highlight; box-shadow:none;
  }
}

/* ---------------------------------------------------------------- auth card */
/* COLUMN, not row. #boot holds the card AND the credit line beneath it; as a row the
   two became side-by-side flex items and the sign-in card slid to the far left. */
.boot{min-height:100vh; display:flex; flex-direction:column;
  align-items:center; justify-content:center; padding:28px; gap:0}
.boot-card{
  background:var(--card); border:1px solid var(--line); border-radius:18px;
  padding:38px 34px 34px; max-width:432px; width:100%; text-align:center;
  box-shadow:var(--shadow)}
.boot-card .mark{display:inline-block; margin:0 auto 22px}
.boot-title{font-size:13px; letter-spacing:.2em; text-transform:uppercase;
  font-weight:700; color:var(--ink); margin:0 0 5px}
.boot-sub{color:var(--mut); font-size:13.5px; margin:0 0 24px}
.boot-msg{color:var(--mut); margin-bottom:18px; font-size:14.5px}

/* AUTH_UNKNOWN. Deliberately quiet: a page load should look like the product
   opening, not like an operation in progress. */
#bootStage{padding:14px 0 6px}
.boot-spinner{width:22px; height:22px; margin:0 auto 16px; border-radius:50%;
  border:2px solid var(--line); border-top-color:var(--amber);
  animation:pa-spin .8s linear infinite}
@keyframes pa-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion: reduce){
  .boot-spinner{animation-duration:2.4s}
}

/* The product credit. Deliberately small and neutral: Petra Point is the brand,
   this is a signature. */
.boot-credit{margin:20px auto 0; text-align:center; font-size:11.5px;
  letter-spacing:.04em; color:var(--mut); opacity:.75}
.boot-err{margin-top:18px; color:var(--red); font-size:13.5px; text-align:left;
  background:color-mix(in srgb, var(--red) 8%, transparent);
  border:1px solid color-mix(in srgb, var(--red) 28%, transparent);
  border-radius:var(--radius-sm); padding:11px 13px;
  white-space:pre-wrap; word-break:break-word}
.authfield{text-align:left; margin-bottom:2px}
.authfield label{margin-top:14px}
.secretbox{
  text-align:left; background:var(--bg); border:1px solid var(--line);
  border-radius:var(--radius-sm); padding:15px 17px; margin:14px 0 8px;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:15.5px; letter-spacing:.07em; word-break:break-all; line-height:2}

/* ---------------------------------------------------------------- shell */
/* ------------------------------------------------------------------ shell
   A deep NEUTRAL bar, not a slab of brand colour. Orange appears on it as the
   active page and nothing else, which is what makes it read as Petra rather than
   as "a blue SaaS app with a logo in the corner". */
.top{display:flex; align-items:center; gap:26px; padding:0 26px; height:62px;
  background:var(--shell); border-bottom:1px solid var(--shell-line);
  position:sticky; top:0; z-index:5}

/* The mark gets room and a baseline, rather than being a favicon in a navbar. */
/* The mark sits on its own white plate. The source logo is dark-on-white, so on a
   dark shell it needs the plate to be legible at all - and the plate is what makes it
   read as a deliberate identity rather than a favicon dropped into a navbar. */
.top .mark{padding:6px 9px; background:#fff; border:0; border-radius:8px;
  display:flex; align-items:center; line-height:0}
.top .mark img{height:26px; width:auto; display:block}
.top .product{color:var(--shell-mut); font-size:11px; letter-spacing:.22em;
  text-transform:uppercase; font-weight:700; padding-left:14px; margin-left:2px;
  border-left:1px solid var(--shell-line)}
.top .who{color:var(--shell-mut); font-size:13.5px}
.top-right{display:flex; align-items:center; gap:14px; margin-left:auto}

/* Navigation: quiet until it is the page you are on, then Petra orange. */
.top .tabs{gap:0; display:flex; align-items:center}
.top .tabs a{color:var(--shell-mut); background:transparent; border-radius:0;
  padding:0 2px; margin:0 16px; height:62px; display:inline-flex; align-items:center;
  box-shadow:none; font-weight:520; font-size:14px; position:relative}
.top .tabs a:hover{color:var(--shell-ink); background:transparent; box-shadow:none}
.top .tabs a.on{color:var(--shell-ink); background:transparent; box-shadow:none;
  font-weight:600}
.top .tabs a.on::after{content:''; position:absolute; left:0; right:0; bottom:0;
  height:2px; background:var(--orange); border-radius:2px 2px 0 0}

.top .ghost{border-color:var(--shell-line); color:var(--shell-ink);
  background:transparent; font-size:13.5px; padding:7px 13px}
.top .ghost:hover{background:rgba(255,255,255,.08);
  border-color:rgba(255,255,255,.24)}

.top-left{display:flex; align-items:center; gap:13px}

.brand{display:none}
.product{font-size:11.5px; letter-spacing:.2em; text-transform:uppercase;
  font-weight:700; color:var(--mut)}
.envtag{font-size:10.5px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--amber-ink); background:color-mix(in srgb, var(--amber) 16%, transparent);
  border:1px solid color-mix(in srgb, var(--amber) 34%, transparent);
  padding:3px 8px; border-radius:999px; font-weight:700}
.tabs{display:flex; gap:2px; margin-left:8px; flex:1}
.tabs a{padding:9px 14px; border-radius:var(--radius-sm); color:var(--mut);
  font-weight:560; font-size:14.5px}
.tabs a:hover{color:var(--ink); text-decoration:none; background:var(--line-2)}
.tabs a.on{color:var(--ink); background:var(--line-2); box-shadow:inset 0 -2px 0 var(--amber)}
.top-right{display:flex; align-items:center; gap:14px}
.who{color:var(--mut); font-size:14px}
main{max-width:1120px; margin:0 auto; padding:32px 26px 72px}
.foot{max-width:1120px; margin:0 auto; padding:0 26px 40px;
  color:var(--mut); font-size:12.5px; display:flex; gap:9px; flex-wrap:wrap;
  justify-content:flex-end}
.foot .credit{letter-spacing:.04em; opacity:.7}
.sep{opacity:.5}

/* ---------------------------------------------------------------- cards */
.card{background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  padding:22px 24px; margin-bottom:16px; box-shadow:var(--shadow-sm)}
.card > h2:first-child, .card > h3:first-child{margin-top:0}
.stats{display:grid; grid-template-columns:repeat(auto-fit,minmax(168px,1fr)); gap:12px;
  margin-bottom:16px}
.stat{background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  padding:15px 17px; font-size:12.5px; color:var(--mut); letter-spacing:.02em;
  box-shadow:var(--shadow-sm)}
.stat b{display:block; font-size:23px; color:var(--ink); font-weight:650;
  letter-spacing:-.02em; margin-top:5px}

/* ------------------------------------------------- the upload, redesigned
   One file, one line of identity, one bar. A 47 KB upload does not need four
   equal boxes reporting speed, time left, elapsed and reused - that is dashboard
   weight applied to a job that finishes in nine seconds. */
.up-file{display:flex; align-items:baseline; gap:12px; margin:2px 0 18px}
.up-file .n{font-size:19px; font-weight:600; color:var(--ink);
  overflow-wrap:anywhere}
.up-file .s{font-size:13px; color:var(--mut); flex:none}
.up-pct{display:flex; align-items:baseline; justify-content:space-between;
  margin-top:11px}
.up-pct .v{font-size:26px; font-weight:640; letter-spacing:-.02em; color:var(--ink)}
.up-pct .m{font-size:12.5px; color:var(--mut)}
.up-note{margin-top:14px; font-size:13.5px; color:var(--blue);
  display:flex; align-items:center; gap:9px}
.up-note .dot{width:7px; height:7px; border-radius:50%; background:var(--blue);
  flex:none; animation:pa-breathe 2s ease-in-out infinite}

/* The journey, instead of a developer log. */
.journey{list-style:none; margin:20px 0 0; padding:0}
.journey li{display:flex; align-items:center; gap:11px; padding:7px 0;
  font-size:14px; color:var(--mut)}
.journey li .ic{width:17px; text-align:center; flex:none; font-size:13px}
.journey li.done{color:var(--ink-2)}
.journey li.done .ic{color:var(--green)}
.journey li.now{color:var(--ink); font-weight:560}
.journey li.now .ic{color:var(--blue)}
.journey li.todo .ic{color:var(--line)}

details.more{margin-top:18px}
details.more > summary{cursor:pointer; font-size:13px; color:var(--mut);
  list-style:none; display:inline-flex; align-items:center; gap:6px}
details.more > summary::-webkit-details-marker{display:none}
details.more > summary::before{content:'\203A'; display:inline-block;
  transition:transform .15s ease}
details.more[open] > summary::before{transform:rotate(90deg)}
details.more > summary:hover{color:var(--ink-2)}
details.more .body{margin-top:12px}

/* ------------------------------------------------------------ archive card */
/* The one screen an employee looks at while waiting. It must answer "did it work"
   before it answers anything else, so the state line is the largest thing on it. */
.archive-card{background:var(--card); border:1px solid var(--line);
  border-radius:var(--radius); padding:26px 26px 22px; box-shadow:var(--shadow);
  margin-bottom:18px}
.archive-file{font-size:20px; font-weight:620; letter-spacing:-.015em; margin:0;
  word-break:break-word}
.archive-size{color:var(--mut); font-size:13.5px; margin:3px 0 20px}
.state{display:flex; align-items:flex-start; gap:12px; padding:15px 17px;
  border-radius:var(--radius-sm); border:1px solid var(--line); background:var(--bg)}
.state .dot{flex:0 0 auto; width:22px; height:22px; border-radius:999px;
  display:grid; place-items:center; font-size:13px; font-weight:700; margin-top:1px;
  color:#fff}
.state > span:last-child{display:block}
.state .t{display:block; font-weight:620; font-size:15.5px; letter-spacing:-.01em}
.state .d{display:block; color:var(--mut); font-size:13.5px; margin-top:3px}
.state.done{border-color:color-mix(in srgb, var(--green) 34%, transparent);
  background:color-mix(in srgb, var(--green) 7%, transparent)}
.state.done .dot{background:var(--green)}
.state.work{border-color:color-mix(in srgb, var(--amber) 38%, transparent);
  background:color-mix(in srgb, var(--amber) 9%, transparent)}
.state.work .dot{background:var(--amber)}
.state.wait .dot{background:var(--navy-300)}
.state.bad{border-color:color-mix(in srgb, var(--red) 34%, transparent);
  background:color-mix(in srgb, var(--red) 7%, transparent)}
.state.bad .dot{background:var(--red)}
.state + .state{margin-top:9px}

/* A quiet, non-hypnotic indication that work is genuinely happening. */
.pulse{position:relative}
.pulse::after{content:""; position:absolute; inset:0; border-radius:999px;
  background:var(--amber); opacity:.45; animation:pl 2.4s ease-out infinite}
@keyframes pl{0%{transform:scale(1); opacity:.45} 70%{transform:scale(2.1); opacity:0}
  100%{opacity:0}}
@media (prefers-reduced-motion: reduce){ .pulse::after{animation:none} }

/* ---------------------------------------------------------------- pills */
.pill{display:inline-block; padding:3px 10px; border-radius:999px; font-size:12px;
  font-weight:650; letter-spacing:.01em; border:1px solid transparent}
.pill.ok{color:var(--green); background:color-mix(in srgb, var(--green) 11%, transparent);
  border-color:color-mix(in srgb, var(--green) 30%, transparent)}
/* SECURING IS BLUE, not amber. Amber next to a finished upload reads as a warning,
   and nothing is wrong - the system is simply still working. */
.pill.busy{color:var(--blue); background:color-mix(in srgb, var(--blue) 13%, transparent);
  border-color:color-mix(in srgb, var(--blue) 32%, transparent)}
.pill.warn{color:var(--orange-soft);
  background:color-mix(in srgb, var(--orange) 15%, transparent);
  border-color:color-mix(in srgb, var(--orange) 34%, transparent)}
.pill.bad{color:var(--red); background:color-mix(in srgb, var(--red) 10%, transparent);
  border-color:color-mix(in srgb, var(--red) 30%, transparent)}
.pill.idle{color:var(--mut); background:var(--line-2); border-color:var(--line)}

/* ---------------------------------------------------------------- tables */
table{width:100%; border-collapse:collapse}
th{text-align:left; font-size:11.5px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--mut); font-weight:700; padding:0 12px 10px; border-bottom:1px solid var(--line)}
td{padding:13px 12px; border-bottom:1px solid var(--line-2); vertical-align:middle}
tr:last-child td{border-bottom:none}
tbody tr:hover{background:var(--line-2)}
th.right,td.right{text-align:right}

/* ---------------------------------------------------------------- progress */
.bar{height:9px; background:var(--line-2); border-radius:999px; overflow:hidden;
  position:relative; border:1px solid var(--line)}
/* PROGRESS IS BLUE. This is the clearest expression of the rule: orange is Petra,
   blue is Petra Cloud doing something. A moving highlight rides the filled part
   so the bar reads as active without a spinner anywhere near it. */
.bar > div{height:100%; background:var(--blue); border-radius:999px;
  transition:width .35s ease; position:relative; overflow:hidden}
.bar > div::after{content:''; position:absolute; inset:0;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.28), transparent);
  transform:translateX(-100%); animation:pa-sheen 1.9s ease-in-out infinite}
@keyframes pa-sheen{60%,100%{transform:translateX(100%)}}
.bar.done > div{background:var(--green)}
.bar.done > div::after{animation:none}
@media (prefers-reduced-motion: reduce){ .bar > div::after{animation:none} }
/* UI-02. This rule used to place status text INSIDE the 9px progress track, which
   has overflow:hidden - so "0 of 1 secured" was clipped to a sliver and was
   unreadable at any zoom. Status text is now its own line beneath the track.
   The old selector is kept ONLY to neutralise any span that slips through. */
.bar > span{display:none}
.barnote{margin-top:9px; font-size:13px; color:var(--ink-2); font-weight:560;
  letter-spacing:.01em}
.meter{height:7px; background:var(--line); border-radius:999px; overflow:hidden}
.meter > div{height:100%; background:var(--accent); border-radius:999px;
  transition:width .3s ease}
.meter.warn > div{background:var(--amber)}
.meter.bad > div{background:var(--red)}

/* ---------------------------------------------------------------- notices */
.warnbox,.badbox,.okbox,.linkbox{border-radius:var(--radius-sm); padding:13px 16px;
  margin:14px 0; font-size:14px; border:1px solid; border-left-width:3px}
.warnbox{background:color-mix(in srgb, var(--amber) 8%, transparent);
  border-color:color-mix(in srgb, var(--amber) 32%, transparent); color:var(--ink)}
.badbox{background:color-mix(in srgb, var(--red) 7%, transparent);
  border-color:color-mix(in srgb, var(--red) 30%, transparent); color:var(--ink)}
.okbox{background:color-mix(in srgb, var(--green) 8%, transparent);
  border-color:color-mix(in srgb, var(--green) 30%, transparent); color:var(--ink)}
.linkbox{background:var(--bg); border-color:var(--line); word-break:break-all}
.empty{padding:40px 20px; text-align:center; color:var(--mut); font-size:14px}
.receipt{background:var(--card);
  border:1px solid color-mix(in srgb, var(--green) 32%, transparent);
  border-left:3px solid var(--green); border-radius:var(--radius); padding:22px 24px;
  margin-bottom:16px; box-shadow:var(--shadow-sm)}

/* ---------------------------------------------------------------- pickers */
.pickers{display:flex; gap:10px; flex-wrap:wrap; margin:14px 0}
.drop{border:1.5px dashed var(--line); border-radius:var(--radius); padding:34px 22px;
  text-align:center; color:var(--mut); transition:border-color .15s, background .15s}
.drop.over{border-color:var(--accent);
  background:color-mix(in srgb, var(--accent) 6%, transparent); color:var(--ink)}

/* ---------------------------------------------------------------- conditions */
/* Advanced Diagnostics only. No employee screen renders these. */
.cond{display:flex; gap:11px; align-items:flex-start; padding:9px 0;
  border-bottom:1px solid var(--line-2); font-size:13.5px}
.cond:last-child{border-bottom:none}
.cond .m{flex:0 0 auto; width:19px; height:19px; border-radius:999px; display:grid;
  place-items:center; font-size:11px; font-weight:700; background:var(--red); color:#fff}
.cond.y .m{background:var(--green)}

/* ---------------------------------------------------------------- admin */
.subtabs{display:flex; gap:2px; flex-wrap:wrap; margin:20px 0 18px;
  border-bottom:1px solid var(--line)}
.subtabs a, .subtabs button{background:transparent; border:none;
  border-bottom:2px solid transparent; text-decoration:none;
  border-radius:0; color:var(--mut); font-weight:600; padding:10px 14px; font-size:14px}
.subtabs a:hover, .subtabs button:hover{color:var(--ink); text-decoration:none}
.subtabs a.on, .subtabs button.on{color:var(--ink);
  border-bottom-color:var(--amber)}
.rowform{display:flex; gap:12px; flex-wrap:wrap; align-items:flex-end; margin:10px 0 4px}
.rowform .authfield{margin:0; flex:1 1 190px}
.rowform label{margin-top:0}
.rowform button{margin-bottom:1px}

/* ---------------------------------------------------------------- modal */
.modal{position:fixed; inset:0; background:rgba(11,15,36,.52); display:flex;
  align-items:center; justify-content:center; padding:24px; z-index:50}
.modal-card{background:var(--card); border:1px solid var(--line); border-radius:16px;
  padding:26px 28px; max-width:520px; width:100%; max-height:86vh; overflow:auto;
  box-shadow:0 18px 60px rgba(11,15,36,.28)}
.modal-card h2{margin:0 0 10px; font-size:19px; text-transform:none;
  letter-spacing:-.012em; color:var(--ink)}
.modal-actions{display:flex; gap:10px; justify-content:flex-end; margin-top:22px}

/* Long names must not decide the layout. A survey deliverable is routinely called
   something like "Topographic Survey (rev 3) - Marina Plot 14.dwg", and an Arabic
   name can be longer still. */
td .name, .name{word-break:break-word; overflow-wrap:anywhere}

@media (max-width:780px){
  /* THE HEADER HAD A FIXED HEIGHT AND WAS ALLOWED TO WRAP.
     `.top{height:62px}` is right on a desktop, where the strip is one row. Below this
     width `flex-wrap:wrap` puts the navigation on its own line - but 62px was never
     lifted, so the header's BOX stayed 62px tall while its CONTENT grew to roughly
     250px, and everything past the first row was drawn on top of the page beneath it.
     On a 390px screen the word "Admin" sat directly over the navigation. The layout
     reported no horizontal overflow throughout, which is why a scroll-width contract
     alone is not enough and the sweep now checks for overlap too. */
  .top{flex-wrap:wrap; gap:12px; padding:12px 16px; height:auto; min-height:62px}

  /* AND THE NAVIGATION WRAPPED INSTEAD OF SCROLLING.
     `overflow-x:auto` can only scroll something that refuses to wrap; a flex row that
     is allowed to wrap simply gets taller and never overflows. Saying so explicitly is
     what turns the strip into the one-line scroller it was always meant to be. */
  .tabs{order:3; width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch;
    flex-wrap:nowrap}
  .tabs a{white-space:nowrap; flex:0 0 auto}
  .top .tabs a{margin:0 14px 0 0}
  main{padding:22px 16px 60px}
  h1{font-size:22px}
  .card{padding:18px 16px}
  .foot{padding:0 16px 32px; justify-content:center}
  .boot{padding:18px}
  .boot-card{padding:30px 22px 26px}

  /* TABLES GET AN INTENTIONAL STRATEGY rather than being left to push the page
     sideways. They scroll inside their own card, so the PAGE never scrolls
     horizontally - which is the thing that makes a site feel broken on a phone. */
  .card{overflow-x:auto}
  table{min-width:540px}

  /* Rows of controls stack instead of squeezing. */
  .row.spread{flex-direction:column; align-items:stretch; gap:12px}
  .row.spread .primary, .row.spread .ghost{width:100%}
}

@media (max-width:420px){
  .top .product{display:none}
  h1{font-size:20px}
}


/* ---------------------------------------------------- background protection
   A calm, finite-looking indicator. NOT an endless spinner: the upload is finished
   and a spinner says "still working, keep watching", which is exactly the impression
   the copy is trying to remove. */
.securing{display:flex; align-items:center; gap:10px; margin-bottom:12px}
.securing .dot{width:8px; height:8px; border-radius:50%; background:var(--amber);
  flex:none; animation:pa-breathe 2.2s ease-in-out infinite}
@keyframes pa-breathe{0%,100%{opacity:.35}50%{opacity:1}}
@media (prefers-reduced-motion: reduce){ .securing .dot{animation:none; opacity:.85} }


/* ---------------------------------------------------------------- page head
   Title, one quiet line, and the page's primary action - on one row. Premium UI
   carries hierarchy in type and space, not in a box around every group. */
.pagehead{display:flex; align-items:flex-start; justify-content:space-between;
  gap:24px; margin:0 0 26px}
.pagehead h1{margin:0 0 5px}
.pagehead .sub{margin:0}
.pagehead .primary{flex:none}

/* ------------------------------------------------------------- empty states
   Restrained: a statement, a sentence, and the one action that resolves it.
   No illustration, no card, no exclamation marks. */
.empty-state{text-align:center; padding:64px 24px 70px;
  border:1px dashed var(--line); border-radius:var(--radius)}
/* The global h2 is an uppercase tracked section label; an empty state is a sentence
   to a person, not a section heading, so it opts out. */
.empty-state h2{margin:0 0 7px; font-size:17px; color:var(--ink); font-weight:600;
  text-transform:none; letter-spacing:-.01em}
.empty-state p{margin:0 0 20px; color:var(--mut); font-size:14px}

/* -------------------------------------------------------------- table polish
   Fewer rules, more air, and the name is allowed to dominate. */
th{padding:0 14px 12px}
td{padding:15px 14px}
tbody tr:hover{background:var(--line-2)}
td .name{font-weight:560; color:var(--ink)}
td.num, th.num{text-align:right; font-variant-numeric:tabular-nums}
.rowact{color:var(--mut); font-size:13px; white-space:nowrap}
tr:hover .rowact{color:var(--orange)}
thead th{position:sticky; top:62px; background:var(--card); z-index:1}


/* -------------------------------------------------------- audit / security
   The detail row sits under its event and is closed by default, so the table
   reads as a list of sentences rather than a wall of payloads. */
tr.detrow td{padding:0 14px 12px; border-bottom:1px solid var(--line-2)}
tr.detrow:hover{background:transparent}
pre.raw{margin:0; padding:12px 14px; background:var(--line-2);
  border:1px solid var(--line); border-radius:var(--radius-sm);
  font-size:12px; line-height:1.55; color:var(--ink-2);
  max-height:260px; overflow:auto; white-space:pre-wrap; word-break:break-word}

/* A form row that is not wrapped in a card. */
.rowform.bare{background:transparent; border:0; padding:0; margin-bottom:20px}


/* ------------------------------------------------------- admin sub-navigation
   Business administration on the left, system administration set apart on the
   right. Grouping by SPACE rather than by a hidden menu: every control stays one
   click away, which is the whole point. */
.subtabs{display:flex; align-items:center; gap:28px; flex-wrap:wrap}
.subtabs .grp{display:flex; align-items:center; gap:4px; flex-wrap:wrap}
.subtabs .grp.sys{padding-left:26px; border-left:1px solid var(--line); gap:2px}
.subtabs .grp.sys a, .subtabs .grp.sys button{font-size:13px; color:var(--mut)}
.subtabs .grp.sys a:hover, .subtabs .grp.sys button:hover{color:var(--ink-2)}
.subtabs .grp.sys a.on, .subtabs .grp.sys button.on{color:var(--ink)}
.subtabs .glab{font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--mut); opacity:.7; margin-right:8px; font-weight:700}
@media (max-width:900px){
  .subtabs{gap:12px}
  .subtabs .grp.sys{padding-left:0; border-left:0; width:100%}
}

/* ON A PHONE THE TWO GROUPS BECAME FOUR ROWS.
   The two-row shape above is deliberate - business administration, then system
   administration on its own line - but each GROUP was also allowed to wrap, so at
   390px "Overview Employees Archives" broke across two lines and "Uploads Client
   Links" across another, and the sub-navigation took a third of the screen before any
   content appeared.

   The design keeps its two rows. Each row simply becomes one scrollable line, which is
   the same treatment the main navigation gets at this width. Declared here rather than
   with the other phone rules because `.subtabs{flex-wrap:wrap}` is defined further
   down the file and would otherwise win. */
@media (max-width:780px){
  .subtabs{flex-wrap:wrap; gap:10px; margin:14px 0 16px}
  .subtabs .grp{flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch;
    width:100%; gap:6px; padding-bottom:2px}
  .subtabs .grp a, .subtabs .grp button{white-space:nowrap; flex:0 0 auto}
  .subtabs .glab{flex:0 0 auto}

  /* NO SCROLLBAR ON A NAVIGATION STRIP. A phone draws an overlay scrollbar that
     disappears on its own; a desktop browser at this width draws the classic one, and
     two grey troughs with arrow buttons across the top of the console look like a
     rendering fault rather than a design. The affordance is kept by the content itself:
     the strip is clipped mid-item, which is what tells a person it continues.
     Deliberately scoped to the two navigation strips - a table inside a card keeps its
     scrollbar, where the affordance is the only thing that says the columns continue. */
  .tabs::-webkit-scrollbar, .subtabs .grp::-webkit-scrollbar{display:none}
  .tabs, .subtabs .grp{scrollbar-width:none; -ms-overflow-style:none}
}

#bootMsg:empty{display:none}

/* =========================================================================
   ADMIN LAYOUT - width, and what to do when there is not enough of it
   =========================================================================

   THE DEFECT THIS FIXES, measured rather than guessed. At 1366x768 @100% the
   Admin Employees page does NOT overflow the document - scrollWidth 1333 against
   a 1348 viewport, zero offending elements. It is SQUEEZED, not clipped:

     * `main` caps every page at 1120px, so a 1366 viewport leaves 246px unused
       and a 1920 one leaves 800px unused;
     * the Employees table gets ~1068px for six columns, the last of which holds
       up to six action buttons;
     * `.actions{flex-wrap:wrap}` then wraps those buttons over two and three
       rows, the header labels wrap too, and rows grow past 100px tall, so the
       actions of later rows fall below the fold.

   Zooming the browser out hands `main` more CSS pixels, the buttons stop
   wrapping, and it looks fixed - which is exactly the symptom reported. The
   answer is therefore not `overflow-x:auto`; it is to use the width the screen
   already has, and to stop pretending six buttons per row is a column. */

main.wide{max-width:1560px}
.foot.wide{max-width:1560px}

/* A flex child refuses to shrink below its content unless told it may. Without
   this the header's tab strip can push the shell wider than the viewport on a
   narrow laptop - the classic min-width:auto trap. */
.top{min-width:0}
.top .tabs{min-width:0; flex:1 1 auto; overflow:hidden}
main{min-width:0}
.card{min-width:0}

/* ---------------------------------------------------- admin data tables */
/* One table class, three column priorities. `pri-1` always shows, `pri-2` goes
   first, `pri-3` goes next. Nothing is lost - everything hidden here is also on
   the row's own detail line, which is why hiding it is honest rather than a
   trick to make a screenshot pass. */
.tbl{width:100%; table-layout:auto}
.tbl th, .tbl td{vertical-align:top}
.tbl .grow{width:100%}                    /* the name column absorbs the slack */
.tbl .tight{white-space:nowrap; width:1%}  /* pills and dates never wrap */
.tbl td.tight, .tbl th.tight{padding-right:18px}

/* ------------------------------------------------------------ row actions */
/* Two actions inline, the rest one click away. Six ghost buttons in a cell is
   not a column, it is a paragraph of buttons - and it was the direct cause of
   the 100px rows. */
.rowacts{display:flex; align-items:flex-start; justify-content:flex-end; gap:6px;
  white-space:nowrap}
.rowmenu{position:relative; display:inline-block}
.rowmenu > summary{list-style:none; cursor:pointer; padding:5px 10px; font-size:13px;
  border:1px solid var(--line); border-radius:var(--radius-sm); color:var(--mut);
  background:transparent; user-select:none}
.rowmenu > summary::-webkit-details-marker{display:none}
.rowmenu > summary:hover{color:var(--ink); border-color:var(--line-2)}
.rowmenu[open] > summary{color:var(--ink); border-color:var(--amber)}
.rowmenu .menu{position:absolute; right:0; top:calc(100% + 6px); z-index:40;
  min-width:212px; padding:6px; background:var(--card);
  border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:0 18px 40px rgba(0,0,0,.34); display:flex; flex-direction:column;
  gap:2px; white-space:nowrap}
.rowmenu .menu button{width:100%; text-align:left; background:transparent;
  border:0; padding:8px 10px; border-radius:var(--radius-sm); font-size:13.5px;
  color:var(--ink-2)}
.rowmenu .menu button:hover{background:var(--line-2); color:var(--ink)}
.rowmenu .menu .sep{height:1px; background:var(--line); margin:4px 2px; opacity:1}
.rowmenu .menu button.danger{color:var(--red)}
.rowmenu .menu button.danger:hover{background:color-mix(in srgb,var(--red) 14%,transparent);
  color:var(--red)}
.rowmenu .menu button[disabled]{opacity:.45; cursor:default}
.rowmenu .menu button[disabled]:hover{background:transparent; color:var(--ink-2)}
.rowmenu .why{font-size:11.5px; color:var(--mut); padding:2px 10px 6px;
  white-space:normal; max-width:230px; line-height:1.45}

/* ----------------------------------------------------------- narrowing */
@media (max-width:1180px){
  .tbl .pri-3{display:none}
}
@media (max-width:980px){
  .tbl .pri-2{display:none}
}
/* Below this a six-column table is a worse answer than a stacked row. The card
   mode keeps every field and every action; it stops pretending to be a grid. */
@media (max-width:860px){
  .tbl, .tbl tbody, .tbl tr, .tbl td{display:block; width:auto}
  .tbl thead{display:none}
  .tbl tr{border:1px solid var(--line); border-radius:var(--radius);
    padding:14px 16px; margin-bottom:12px}
  .tbl td{border:0; padding:4px 0}
  .tbl td.pri-2, .tbl td.pri-3{display:block}
  .tbl td::before{content:attr(data-label); display:block; font-size:11px;
    letter-spacing:.07em; text-transform:uppercase; color:var(--mut);
    margin-bottom:3px}
  .tbl td:first-child::before{content:none}
  .rowacts{justify-content:flex-start; flex-wrap:wrap; white-space:normal;
    margin-top:10px}
  .rowmenu .menu{right:auto; left:0}
}

/* A row menu on the last visible row opened downward into the fold and was clipped.
   It flips when there is not room below - decided from the live rectangle rather than
   from a media query, because "room below" depends on scroll position, not width. */
.rowmenu.up .menu{top:auto; bottom:calc(100% + 6px)}

/* AND ON A PHONE IT OPENED OFF THE SIDE OF THE SCREEN.
   `left:0` anchors the 198px menu to the LEFT edge of its trigger, and the trigger
   sits wherever the row put it - measured at x=136 on a 320px screen and x=280 on a
   430px one, so the menu ran 14px and 48px past the right edge and its actions could
   not be reached at all. `right:0` is no better: it just fails at the other end.

   A menu cannot be positioned correctly from the trigger alone without knowing where
   the trigger is, and CSS anchor positioning is not something this product can rely on
   yet. So below 560px the menu stops being a dropdown and becomes what a phone
   actually wants: a sheet pinned to the bottom of the VIEWPORT, always fully on
   screen, whatever the trigger did. It scrolls if the list is long. */
/* AND ON A PHONE THE CARD WAS MOSTLY LABELS.
   Each field taking its own two lines is right where the value needs the room - a
   name, a status pill, a list of actions. It is wrong for a date or a size, which are
   short enough to sit beside their own label, and there are two or three of them per
   card. Nine lines became four, and a phone shows three archives instead of one.

   Scoped to `.tight.small`, which is what the tables mark a date or a size with. The
   label is kept: "52.9 MB" explains itself, "Sep 21, 2026 10:27 PM" does not. */
@media (max-width:560px){
  /* The cell stays a block - a <td> forced to inline-block shrink-to-fits to 3px
     here, whatever Blink is doing with a table cell inside a block row, and the two
     values drew on top of each other. Only the LABEL comes onto the value's line,
     which is where the wasted line was. */
  .tbl td.tight.small{padding:3px 0}
  .tbl td.tight.small::before{display:inline; margin:0 6px 0 0}
}

@media (max-width:560px){
  .rowmenu[open] .menu{
    position:fixed; left:12px; right:12px; top:auto; bottom:12px;
    width:auto; min-width:0; max-width:none; max-height:62vh; overflow-y:auto;
    box-shadow:0 -6px 32px rgba(0,0,0,.34)}
  .rowmenu[open] .menu button{padding:12px 12px; font-size:14.5px}
  .rowmenu.up .menu{bottom:12px}
}

/* =========================================================================
   FOOTER, AND THE SHAPE OF A SHORT PAGE
   ========================================================================= */
/* The credit floated wherever the content stopped - halfway up an empty Settings
   page, which reads as a layout accident rather than a signature. The shell becomes
   a column, `main` takes the slack, and the footer sits on the bottom of the
   viewport when the page is short and after the content when it is long. */
#shell{display:flex; flex-direction:column; min-height:100vh}
#shell > main{flex:1 0 auto}
#shell > .foot{flex:0 0 auto; margin-top:28px}

/* 7A - one width rule per audience, stated once.
   Employee reading column stays comfortable; the admin console uses the screen. */
main{max-width:1240px}
.foot{max-width:1240px}

/* =========================================================================
   POLICY SWITCHES  (Part 11)
   =========================================================================
   A boolean policy rendered as a 220px Yes/No select is both oversized and mute:
   the screen said "Administrators must use an authenticator", showed "No", and
   then advised "Leave this on." Three things, disagreeing. A switch states the
   effective value, and the sentence beneath it is derived from that value. */
.policy{display:flex; gap:16px; align-items:flex-start; padding:16px 0;
  border-bottom:1px solid var(--line-2)}
.policy:last-of-type{border-bottom:0}
.policy-text b{display:block; font-size:14.5px; color:var(--ink); font-weight:620}
.policy .state{font-size:12.5px; color:var(--mut); margin-top:3px}
.policy .state .v{display:inline; font-weight:700; color:var(--mut)}
.policy.on .state .v{color:var(--green)}
.policy .hint{margin-top:6px}
.switch{position:relative; display:inline-block; flex:0 0 auto; margin-top:2px}
.switch input{position:absolute; opacity:0; width:44px; height:26px; margin:0;
  cursor:pointer}
.switch .track{display:block; width:44px; height:26px; border-radius:999px;
  background:var(--line-2); border:1px solid var(--line); transition:background .15s}
.switch .knob{display:block; width:20px; height:20px; border-radius:50%;
  background:var(--mut); margin:2px; transition:transform .15s, background .15s}
.policy.on .switch .track{background:color-mix(in srgb,var(--green) 34%,transparent);
  border-color:var(--green)}
.policy.on .switch .knob{transform:translateX(18px); background:var(--green)}
.switch input:focus-visible + .track{outline:2px solid var(--focus);
  outline-offset:2px}
@media (prefers-reduced-motion: reduce){
  .switch .track, .switch .knob{transition:none}
}

/* =========================================================================
   RESPONSIVE ROOT CAUSE  (measured, CDP, true viewports)
   =========================================================================

   At 390x844 the document was 606px wide against a 390px viewport. Two causes,
   and the first one was mine:

   1. `#shell{display:flex; flex-direction:column}` was added to pin the footer.
      A COLUMN flex container with `width:auto` is shrink-to-fit: it sizes to the
      MAX-CONTENT of its items. `main` therefore stretched to the widest thing
      inside it instead of to the viewport, and the whole page grew with it.
      A flex container used for vertical layout must still be told it is full
      width.

   2. `@media (max-width:780px){ table{min-width:540px} }` predates `.tbl`. Once
      `.tbl` switches to stacked cards it is `display:block`, but the 540px floor
      still applied - so a "responsive" table carried a 540px minimum on a 320px
      phone. The floor belongs to the tables that still scroll inside their card,
      not to the one that stopped being a table. */

#shell{width:100%; max-width:100%}
#shell > main, #shell > .foot{width:100%; max-width:100%}
main.wide{max-width:1560px}

@media (max-width:860px){
  /* `.tbl` is not a table any more here; it must not keep a table's floor. */
  table.tbl{min-width:0}
  .card:has(> table.tbl), .card:has(table.tbl){overflow-x:visible}
}

/* 3. AND THE FLOOR ONLY EVER WORKED FOR TABLES THAT HAD A CARD AROUND THEM.
      `.card{overflow-x:auto}` plus `table{min-width:540px}` is a sound strategy -
      the table scrolls, the page does not - but it silently depends on every table
      being wrapped, and nine of them are not. The audit log is emitted straight
      into `main`, so at 320px it carried its 540px floor with nothing to contain
      it and pushed the document to 556px.

      Rather than add a wrapper at nine call sites and trust the tenth to be
      remembered, the table becomes its own scroll container. `display:block` makes
      it a block box that can scroll; the row groups go back to `table` so the
      columns still line up, and the floor moves onto them so columns keep their
      width INSIDE the scroller. A table that already has a card simply scrolls at
      the inner level instead of the outer one, which looks identical. */
@media (max-width:780px){
  table:not(.tbl){display:block; overflow-x:auto; min-width:0;
                  -webkit-overflow-scrolling:touch}
  table:not(.tbl) > thead, table:not(.tbl) > tbody, table:not(.tbl) > tfoot{
    display:table; width:100%; min-width:540px}
}

/* Nothing in the product may set its own width in a way the viewport cannot
   contain. Stated once, at the top, rather than chased element by element. */
img, svg, video, canvas, table, pre, input, select, textarea, button{max-width:100%}
input, select, textarea{box-sizing:border-box}

/* =========================================================================
   A BUTTON THAT IS WAITING

   A disabled button is not feedback. Sign-in went grey and stayed grey while
   Argon2id did its 130 ms and the request went to an origin that was measured
   stalling for over five seconds on about 40% of calls - so a person could look
   at an unchanged screen for six seconds and reasonably conclude it was broken.

   The label now says what is happening. This mark says it is still happening.
   It is a quiet pulse rather than a spinner, for the same reason the upload
   indicator is: a spinner implies an unknown wait, and these waits are short.
   ========================================================================= */
/* `opacity:1` because the button IS disabled while it waits, and the 45% fade that
   normally signals "you cannot press this" would here read as "this is broken". The
   colour is deliberately left alone: a working `.primary` keeps its dark ink on amber,
   and the mark inherits it through `currentColor`. */
button.working{position:relative; cursor:default; opacity:1}
button.working::after{content:""; display:inline-block; width:6px; height:6px;
  border-radius:50%; background:currentColor; margin-left:8px;
  vertical-align:middle; opacity:.75;
  animation:pa-breathe 1.1s ease-in-out infinite}
@media (prefers-reduced-motion: reduce){
  button.working::after{animation:none; opacity:.8}
}

/* AN IPv6 ADDRESS DOES NOT WRAP, AND PRODUCTION HAS THEM.
   Admin > Sessions renders `clientIp` into a `td.mono`. Every local fixture used a
   short IPv4 literal, so the local sweep passed at 320px; the first real measurement
   against production - where a client address is
   `2001:8f8:1577:184f:1147:7fbf:1531:139e`, thirty-nine characters with no break
   opportunity - pushed the document to 331px against a 320px viewport.

   Monospace cells are exactly where opaque machine values land: addresses, ids,
   digests. They are allowed to break anywhere, because a wrapped address is readable
   and a horizontally scrolling page is not. Prose is untouched - this is scoped to
   cells that opted into `.mono`. */
td.mono, .tbl td.mono, td .mono{overflow-wrap:anywhere; word-break:break-word}

/* The open hint and the row menu share the last cell in My Archive. Without this the
   hint and the menu sit on separate lines and the row doubles in height. */
.rowacts .open-hint{color:var(--mut); font-size:13px; white-space:nowrap;
  align-self:center}

/* Two tokens for the folder glyph, derived from Petra's accent so the filesystem does
   not introduce a colour the design system has not already agreed to. */
:root {
  --fs-fold: color-mix(in srgb, var(--accent) 18%, var(--card));
  --fs-fold-line: color-mix(in srgb, var(--accent) 42%, var(--card));
}

/* ============================================================ FILESYSTEM
 *
 * Browse is a storage product now, so it gets storage-product furniture: a breadcrumb,
 * a + New action, a search box, a sort control, and rows with an overflow menu.
 *
 * WRITTEN PHONE-FIRST ON PURPOSE. Everything below the media query is the small-screen
 * composition; the desktop table is the enhancement, not the other way round. The
 * previous mobile experience was a desktop table made narrower, which is how a product
 * ends up with a four-column grid and a kebab button pressed against the edge of the
 * screen.
 */

/* ------------------------------------------------------------- breadcrumb */
.crumbs { margin: 4px 0 0; font-size: 13px; line-height: 1.7; }
.crumbs a { color: var(--mut); text-decoration: none; }
.crumbs a:hover { color: var(--ink); text-decoration: underline; }
.crumbs b { color: var(--ink); font-weight: 600; }
.crumbs .sep { color: var(--line); margin: 0 7px; }

/* ----------------------------------------------------------------- + New */
.headacts { gap: 10px; align-items: center; }
.newwrap { position: relative; }
.newmenu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: 0 14px 34px rgba(0,0,0,.16);
  min-width: 196px; padding: 6px; display: flex; flex-direction: column;
}
.newmenu button {
  all: unset; padding: 11px 14px; border-radius: 8px; cursor: pointer;
  font-size: 14px; color: var(--ink); min-height: 44px; box-sizing: border-box;
  display: flex; align-items: center;
}
.newmenu button:hover { background: var(--line-2); }

/* -------------------------------------------------------- search and sort */
.fsbar { display: flex; gap: 10px; align-items: center; margin: 14px 0 12px; }
.fsbar input[type=search] {
  flex: 1 1 auto; min-width: 0; padding: 10px 13px; font-size: 14px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--ink); min-height: 44px;
  box-sizing: border-box;
}
.sortsel { display: flex; align-items: center; gap: 7px; flex: 0 0 auto; }
.sortsel select {
  padding: 9px 11px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--ink); font-size: 14px; min-height: 44px;
}

/* ------------------------------------------------------------------ icons */
.ic { display: inline-block; width: 19px; height: 16px; margin-right: 11px;
      vertical-align: -3px; flex: 0 0 auto; }
.ic.fold {
  background: var(--fs-fold); border-radius: 3px 5px 5px 5px;
  box-shadow: inset 0 0 0 1px var(--fs-fold-line);
  position: relative;
}
.ic.fold::before {
  content: ''; position: absolute; left: 0; top: -3px; width: 8px; height: 4px;
  background: var(--fs-fold); border-radius: 2px 3px 0 0;
  box-shadow: inset 0 0 0 1px var(--fs-fold-line); border-bottom: 0;
}
/* A file, not a checkbox. The plain square read as an unticked control on a dark
   card; the clipped corner is the only thing that makes a small rectangle say
   "document", so it is cut out of the shape rather than drawn on top of it. */
.ic.doc {
  width: 14px; height: 17px; background: var(--line-2);
  border: 1px solid var(--line); border-radius: 2px 0 2px 2px;
  box-shadow: none;
  clip-path: polygon(0 0, 62% 0, 100% 27%, 100% 100%, 0 100%);
  position: relative;
}
.ic.doc::after {
  content: ''; position: absolute; right: 0; top: 0;
  border-width: 0 0 6px 6px; border-style: solid;
  border-color: transparent transparent var(--line) transparent;
}

/* ------------------------------------------------------------- the list */
.fs .c-name .nm { font-weight: 500; }
.fs .c-name { display: flex; align-items: center; min-width: 0; }
.fs .c-name .nm { overflow-wrap: anywhere; word-break: break-word; }
.fs .c-when { white-space: nowrap; color: var(--mut); }
.fs .c-size { white-space: nowrap; color: var(--mut); }
.fs .c-act { width: 52px; text-align: right; }
.fs tr[data-folder] { cursor: pointer; }
.fs tr[data-folder]:hover, .fs tr[data-go]:hover, .fs tr[data-goto]:hover {
  background: var(--line-2);
}
.fs .more {
  min-width: 40px; min-height: 40px; line-height: 1; font-size: 17px;
  border-radius: 9px; padding: 0;
}

/* -------------------------------------------------------- the filesystem action menu */
.actmenu {
  position: absolute; z-index: 60; background: var(--card);
  border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 16px 40px rgba(0,0,0,.2); padding: 6px; min-width: 190px;
  display: flex; flex-direction: column;
}
.actmenu button {
  all: unset; padding: 12px 14px; border-radius: 8px; cursor: pointer;
  font-size: 14px; color: var(--ink); min-height: 44px; box-sizing: border-box;
  display: flex; align-items: center;
}
.actmenu button:hover { background: var(--line-2); }

/* THE SAME MENU, AS A SHEET, on a phone. A popover anchored to a button 8px from the
   edge of a 390px screen is unusable; the actions come up from the bottom instead,
   where a thumb already is. */
.actmenu.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; top: auto;
  border-radius: 18px 18px 0 0; padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
  min-width: 0; box-shadow: 0 -12px 40px rgba(0,0,0,.26);
  animation: sheetUp .16s ease-out;
}
.actmenu.sheet button { min-height: 52px; font-size: 15px; }
.actmenu.sheet::before {
  content: ''; display: block; width: 38px; height: 4px; border-radius: 2px;
  background: var(--line); margin: 4px auto 6px;
}
.actmenu .sheethead {
  padding: 4px 14px 11px; font-size: 13px; color: var(--mut); font-weight: 600;
  border-bottom: 1px solid var(--line); margin-bottom: 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.actmenu .sheetcancel {
  margin-top: 6px; border-top: 1px solid var(--line);
  justify-content: center; color: var(--mut); font-weight: 600;
}
/* The sheet is modal in effect, so it should be modal in appearance. Without this the
   actions floated over a fully lit page and read as part of it. */
.scrim {
  position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.45);
  animation: scrimIn .14s ease-out;
}
@keyframes scrimIn { from { opacity: 0 } to { opacity: 1 } }
@keyframes sheetUp { from { transform: translateY(14px); opacity: .6 } to { transform: none; opacity: 1 } }

/* ------------------------------------------------------- dialog furniture */
.fld { display: block; margin: 6px 0 2px; }
.fld > span { display: block; font-size: 13px; color: var(--mut); margin-bottom: 6px; }
.fld input {
  width: 100%; box-sizing: border-box; padding: 12px 13px; font-size: 15px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--ink); min-height: 46px;
}
.destlist {
  max-height: min(56vh, 420px); overflow-y: auto; margin-top: 8px;
  border: 1px solid var(--line); border-radius: 12px; padding: 6px;
}
.destlist .dest {
  all: unset; display: flex; align-items: center; width: 100%;
  box-sizing: border-box; padding: 12px 14px; border-radius: 8px;
  cursor: pointer; font-size: 14px; color: var(--ink); min-height: 46px;
}
.destlist .dest:hover { background: var(--line-2); }
.destnote {
  margin: 12px 0 0; padding: 11px 13px; border-radius: 10px;
  background: var(--fs-fold); border: 1px solid var(--fs-fold-line);
  font-size: 14px;
}

/* ====================================================== PHONE COMPOSITION
 *
 * Not the desktop table made smaller. The table stops being a table: each row becomes
 * a full-width card with the name on its own line, the details underneath, and a
 * 44px action target that is nowhere near the edge of the screen.
 */
@media (max-width: 720px) {
  .pagehead { flex-direction: column; align-items: stretch; gap: 12px; }
  .headacts { width: 100%; }
  /* The flex CHILD is .newwrap - the button lives inside it so the menu has something
     to anchor to - so stretching .primary alone stretched nothing, and "+ New" stayed
     a 90px pill on the left of a 390px screen. */
  .headacts .newwrap { flex: 1 1 auto; min-width: 0; }
  .headacts .newwrap .primary { width: 100%; min-height: 48px; font-size: 15px; }

  /* A folder name three lines long pushed its glyph to the vertical middle, where it
     read as unrelated to the first line of the name it belongs to. */
  .fs .c-name { align-items: flex-start; }
  .fs .c-name .ic { margin-top: 2px; }

  /* ONE LINK: UP. A full path across a 390px screen is either an ellipsis or a
     horizontal scrollbar, and neither is navigation. The current folder is already the
     h1 directly above, so repeating it costs a line and says nothing; at the root
     there is nowhere to go up to, so the whole strip goes.
     The marker comes from the markup (.up) rather than from counting children - the
     separators are children too, so the old :nth-last-child rule kept the FIRST link
     and drew a back arrow that jumped to the root from any depth. */
  .crumbs { font-size: 13px; }
  .crumbs .sep, .crumbs .cur, .crumbs .cr:not(.up) { display: none; }
  .crumbs.atroot { display: none; }
  .crumbs .up { font-size: 14px; }
  .crumbs .up::before { content: '\2190\00a0\00a0'; }

  .fsbar { flex-wrap: wrap; }
  .fsbar input[type=search] { flex: 1 1 100%; }
  .sortsel { flex: 1 1 auto; justify-content: flex-end; }

  .fslist { padding: 0; overflow: visible; }
  .fs, .fs tbody, .fs td { display: block; width: auto; }
  .fs thead { display: none; }
  /* THE ROW STATES ITS OWN LAYOUT.
     Letting the three cells fall back to inline-level boxes inside a block row looked
     right and measured wrong: `.c-when` (77px) and `.c-size` (38px) inside a 271px row
     still came out on line boxes 27px apart, which is what left "Sep 22, 2026 -" alone
     on one line with "8.3 MB" beneath it. Two lines, declared: the name takes the
     first, the two meta cells share the second. `.c-act` is positioned out of flow. */
  .fs tr {
    display: flex; flex-wrap: wrap; align-items: baseline; row-gap: 3px;
    position: relative; padding: 13px 56px 13px 14px;
    border-bottom: 1px solid var(--line); min-height: 64px; box-sizing: border-box;
    width: auto;
  }
  .fs .c-name { flex: 1 1 100%; }
  /* SHRINKABLE. `flex: 0 0 auto` sized these to their max-content and refused to give
     any of it back, so a search result carrying a long location pushed the row - and
     with it the page - 25px past the edge of a 390px screen. */
  .fs .c-when, .fs .c-size { flex: 0 1 auto; min-width: 0; }
  /* And in a RESULT the location is a sentence, not a value, so it takes its own
     line rather than competing with the size for what is left of one. */
  .fs.results .c-size { flex: 1 1 100%; }
  .fs tr:last-child { border-bottom: 0; }
  .fs td { padding: 0; border: 0; }
  .fs .c-name { font-size: 15px; margin-bottom: 3px; }
  .fs .c-when, .fs .c-size {
    display: inline; font-size: 12.5px; color: var(--mut); white-space: normal;
  }
  /* The clock time is what pushed "Sep 20, 2026 10:06 PM - 1 folder" onto a second
     line and left the separator hanging off the end of the first. */
  .fs .c-when .tm { display: none; }
  /* Real spaces around the separator: HTML whitespace processing collapses the
     literal ones inside generated content, so it rendered as "2026 -1 folder". */
  .fs .c-when::after { content: '\00a0\00a0\00b7\00a0\00a0'; }
  .fs .c-act {
    position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
    width: auto; padding: 0;
  }
  .fs .more { min-width: 44px; min-height: 44px; }
}


/* ============================================== PHONE BOTTOM NAVIGATION (V5)
 *
 * The top strip becomes a hidden-scrollbar horizontal scroller below 780px. That is a
 * reasonable treatment for the admin sub-navigation, where the clipped item is the
 * affordance - but not for the PRODUCT's four destinations: on a 390px screen "My
 * Account" and "Admin" were off the right edge with nothing on screen to suggest they
 * existed at all. A phone puts destinations along the bottom instead.
 *
 * Declared at the end of the file so it wins over the .tabs rules above by order,
 * without any !important.
 */
.botnav { display: none; }

@media (max-width: 720px) {
  .top .tabs { display: none; }

  .botnav {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    background: var(--card); border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 -6px 20px rgba(0,0,0,.18);
  }
  .botnav a {
    flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 3px;
    padding: 8px 2px 7px; min-height: 56px; font-size: 11px;
    color: var(--mut); text-decoration: none; -webkit-tap-highlight-color: transparent;
  }
  .botnav a span {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .botnav a svg { width: 21px; height: 21px; display: block; }
  .botnav a.on { color: var(--amber); }
  .botnav a:active { background: var(--line-2); }
  .botnav a:hover { text-decoration: none; }

  /* Content must not end underneath the bar, and neither must the footer credit. */
  main { padding-bottom: calc(76px + env(safe-area-inset-bottom)); }
  .foot { padding-bottom: calc(86px + env(safe-area-inset-bottom)); }

  /* THE ADMIN STRIP CLIPS MID-ITEM ON PURPOSE (V6) - that is what says the row
     continues, now that its scrollbar is hidden. A clipped word still reads as
     "cut off" more than as "scroll me", so the edge fades instead. */
  .subtabs .grp { position: relative; }
  .subtabs { -webkit-mask-image: none; }
  .subtabs .grp::after {
    content: ''; position: sticky; right: 0; flex: 0 0 22px; align-self: stretch;
    margin-left: -22px; pointer-events: none;
    background: linear-gradient(to right, transparent, var(--bg));
  }
}


/* THE DROP ZONE ON A TOUCH DEVICE.
   It keeps its shape - it is still the one place on the screen where choosing happens
   - but it stops looking like a target for a gesture that device cannot make: no
   dashed "drop here" border, and the buttons get the room the invitation was using. */
.drop.touch { border-style: solid; }
.drop.touch .pickers { margin-top: 14px; }
@media (max-width: 720px) {
  .drop .pickers { display: flex; flex-direction: column; gap: 10px; }
  .drop .pickers button { width: 100%; min-height: 48px; font-size: 15px; }
}

/* ===================================================== SEARCH RESULTS + SCOPE
 *
 * A search result is a row in the same table as the listing, so it is read the same
 * way and carries the same menu. The one thing it has that a listing row does not is
 * WHERE the item is, which is the answer somebody searching is usually after.
 */
.scopebar { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; }
.chip {
  all: unset; padding: 7px 13px; border-radius: 999px; cursor: pointer;
  font-size: 13px; color: var(--mut); border: 1px solid var(--line);
  min-height: 36px; box-sizing: border-box; display: inline-flex;
  align-items: center;
}
.chip:hover { color: var(--ink); }
.chip.on {
  color: var(--ink); border-color: var(--amber);
  background: color-mix(in srgb, var(--amber) 12%, transparent);
}
.scopebar #fsCount { margin-left: auto; }

/* The location sits under the size on a desktop, where the size column has room
   below it, and inline on a phone where nothing has room below it. */
.fs.results .where { display: block; color: var(--mut); font-size: 12px; }
.fs.results .c-size { white-space: normal; }

@media (max-width: 720px) {
  .scopebar { flex-wrap: wrap; }
  .scopebar #fsCount { margin-left: 0; width: 100%; }
  .chip { min-height: 40px; }
  .fs.results .where { display: block; margin-top: 2px; }
}

/* ============================================ ADMINISTRATOR DELETE DIALOGS
 *
 * The four facts a person needs before removing somebody else's work, laid out so
 * they are read rather than skimmed: label on the left, value on the right, one per
 * line. A paragraph would be skimmed.
 */
.delfacts {
  margin: 4px 0 14px; border: 1px solid var(--line); border-radius: 12px;
  overflow: hidden;
}
.delfacts > div {
  display: flex; gap: 14px; padding: 10px 14px; font-size: 14px;
  border-bottom: 1px solid var(--line);
}
.delfacts > div:last-child { border-bottom: 0; }
.delfacts span { flex: 0 0 82px; color: var(--mut); }
.delfacts b { min-width: 0; overflow-wrap: anywhere; font-weight: 600; }

.delcounts { margin: 6px 0 14px; padding-left: 20px; }
.delcounts li { margin: 3px 0; }

/* A destructive action does not sit in the same visual register as Open. */
.rowacts .ghost.danger { color: var(--red); border-color: var(--line); }
.rowacts .ghost.danger:hover {
  border-color: var(--red);
  background: color-mix(in srgb, var(--red) 12%, transparent);
}

@media (max-width: 720px) {
  /* ON A PHONE THE DESTRUCTIVE BUTTON IS SEPARATED, not just coloured. The admin
     tables are already cards at this width and `.rowacts` wraps, so Open and Delete
     were landing side by side at thumb width with nothing between them. */
  .delfacts span { flex: 0 0 74px; }
  .rowacts { gap: 10px; }
  .rowacts .ghost.danger {
    margin-left: auto; min-height: 44px; padding-left: 16px; padding-right: 16px;
  }
}

/* A LOCATION IS A SENTENCE, NOT A VALUE.
   `.tight` means "this column should not wrap" and is right for a date or a size.
   The administrator's Location column is a path that can be sixty characters long,
   and nowrap on it pushed one row to 516px inside a 324px card. */
.tbl td.path { white-space: normal; overflow-wrap: anywhere; }

/* The administrator's find/open tables give their columns a share that matches what
   is in them. Without this the Name column took every pixel of slack and the
   Location column - the one carrying a sixty-character path - was squeezed to five
   wrapped lines, which made every row 130px tall and the list unreadable. */
.tbl.admfind td.path, .tbl.admfind th:nth-child(3) { min-width: 230px; }
.tbl.admfind td.tight:not(.path) { white-space: nowrap; }
.tbl.admfind .grow { width: 42%; }
.tbl.admfind .rowacts { white-space: nowrap; }

@media (max-width: 860px) {
  /* Card mode: the widths stop meaning anything and must not linger. */
  .tbl.admfind td.path, .tbl.admfind th:nth-child(3),
  .tbl.admfind .grow { min-width: 0; width: auto; }
}

/* ============================================== TOUCH TARGETS, STATED ONCE
 *
 * A responsive sweep measured every clickable thing at 320, 390 and 430 and found a
 * consistent floor of 33-38px: the header logo, Sign out, the row action buttons on
 * My Archive, the admin ghost buttons, and - worst, because it is the primary way
 * back on a phone - the breadcrumb's up link at 19px tall.
 *
 * None of that showed up as overflow or as a broken layout. It is only visible if you
 * measure the targets or try to hit one, which is why it survived several passes of
 * looking at screenshots.
 *
 * 44px is the floor. Stated in one place rather than chased per component, so the
 * next control added inherits it instead of repeating the bug.
 */
@media (max-width: 720px) {
  button,
  .subtabs a, .subtabs button,
  .rowacts .ghost, .rowacts .primary,
  .rowmenu > summary,
  .top-right .ghost {
    min-height: 44px;
  }

  /* An anchor is not a button and does not take min-height on its own. */
  .top .mark,
  .crumbs .up {
    display: inline-flex; align-items: center; min-height: 44px;
  }

  /* The logo keeps its drawn size; only the hit area grows around it. */
  .top .mark img { height: 38px; width: auto; }
}


@media (max-width: 720px) {
  /* A DESTRUCTIVE ACTION GETS ITS OWN LINE ON A PHONE.
     Colour alone is not separation when two 44px targets sit ten pixels apart under
     a thumb. Delete drops below a rule; Open stays where it was. */
  .tbl.admfind .rowacts {
    display: flex; flex-wrap: wrap; white-space: normal; margin-top: 12px;
  }
  .tbl.admfind .rowacts .ghost.danger {
    width: 100%; margin: 10px 0 0; justify-content: center;
    border-top: 1px solid var(--line); border-radius: 10px;
  }
}


@media (max-width: 720px) {
  /* In a RESULT the size and location take their own line, so the separator after
     the date is left hanging off the end of the line above it - the same defect the
     listing had, reintroduced by a different layout. It has nothing to separate
     here, so it goes. */
  .fs.results .c-when::after { content: none; }
}


/* =============================== THE FILESYSTEM KEEPS ITS TABLE ON A TABLET
 *
 * Two breakpoints were disagreeing. The generic table-to-cards switch happens at
 * 860px; the filesystem's own phone composition starts at 720px. Between the two -
 * 768px, which is an iPad in portrait - Browse got the generic card mode without any
 * of the tuning that makes it work: 180px-tall rows holding three short lines, the
 * action button stranded at the bottom left of each card, and three and a half items
 * on a screen that comfortably fits nine.
 *
 * A four-column table is the right answer at 768px - the name column still gets
 * around 450px - so the filesystem stays a table until its own breakpoint takes over.
 */
@media (min-width: 721px) and (max-width: 860px) {
  .tbl.fs { display: table; width: 100%; min-width: 0; }
  .tbl.fs thead { display: table-header-group; }
  .tbl.fs tbody { display: table-row-group; }
  .tbl.fs tr {
    display: table-row; border: 0; padding: 0; margin: 0; min-height: 0;
    position: static;
  }
  .tbl.fs td {
    display: table-cell; padding: 12px 10px; border: 0;
    border-bottom: 1px solid var(--line); vertical-align: middle;
  }
  .tbl.fs td::before { content: none; }
  .tbl.fs .c-act {
    position: static; transform: none; width: 52px; text-align: right;
  }
  .tbl.fs .c-when .tm { display: inline; }
}
