/* portal.css — the app layer for /portal/, /portal/admin/ and /portal/phone/.
 *
 * Loaded AFTER assets/service.css, which is the site's own stylesheet: it
 * already carries the tokens, the sticky header, the nav, the language pill,
 * the icon buttons, .btn, .card and the footer. Nothing here redefines a
 * token — change one in index.html and service.css and it lands here too.
 *
 * What this file adds is only what a logged-in app needs and a marketing page
 * doesn't: a sign-in card, project and panel layouts, image thumbnails, the
 * message thread, the admin lists, and the softphone.
 *
 * Naming: a few app components would otherwise collide with a site class that
 * means something else (.chip, .dot, .pill, .grid, .row, .tag), so those carry
 * a portal-specific name. Everything that IS the same thing — .card, .btn,
 * .field, .field-row, .lang-switch, .icon-btn — reuses the site's class so the
 * two stay in step by construction.
 */

/* ── tokens this app needs and the marketing site doesn't ────────────────── */
:root{
  /* Nothing on the public site deletes anything, so it has no destructive
     colour. Kept out of the shared token set for that reason. */
  --red:#d64545;
  --amber:#f4820b;
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--red:#e26a6a;--amber:#ffa63d}}
:root[data-theme="dark"]{--red:#e26a6a;--amber:#ffa63d}

/* ── base carried over from the homepage ─────────────────────────────────── */
/* The homepage lifts every .btn on hover, which suits a page with three of
   them. An app is nearly all buttons, and a screen of buttons jumping under
   the pointer reads as a marketing page, so in here a hover is a state layer —
   the fill shifts, nothing moves — and a press gives under the finger. That is
   how every app tool people use daily behaves (Google's, Gemini, Claude). The
   hover tint leans towards violet, which is what violet is for. */
.btn{cursor:pointer;border:1px solid transparent;white-space:nowrap;
  transition:scale .15s var(--ease),box-shadow .25s var(--ease),
             background-color .2s var(--ease),border-color .2s var(--ease),color .2s,opacity .2s}
.btn--primary:hover{background:color-mix(in oklab,var(--accent) 86%,var(--violet))}
.btn:active{scale:.97}
.btn[disabled]{opacity:.55;cursor:default;scale:1;box-shadow:none}
/* restated after .btn above, which would otherwise blank the ghost outline */
.btn--ghost{border-color:var(--line-strong)}
/* A <button> otherwise keeps the browser's own grey fill (and a dark one in
   dark mode), so an outline button looked filled everywhere in the portal. */
button.btn--ghost{background:transparent}
.btn--ghost:hover{background:var(--surface-2);border-color:color-mix(in oklab,var(--violet) 40%,var(--line-strong))}
/* service.css sizes .btn--sm inside the header only, because that is the only
   place the marketing pages use it. An app uses it everywhere, so lift the
   header's own values out to the rest of the page — same numbers. */
.btn--sm{padding:.55rem 1rem;font-size:.875rem;font-weight:580;letter-spacing:-.01em;gap:.55rem}
.btn--danger{background:var(--red);color:#fff;box-shadow:0 8px 24px -10px color-mix(in oklab,var(--red) 70%,transparent)}
.btn--danger:hover{background:color-mix(in oklab,var(--red) 88%,#000)}
.btn--mint{background:var(--mint);color:#04201c}
.btn--mint:hover{background:color-mix(in oklab,var(--mint) 88%,#000)}
.btn svg{width:1em;height:1em;flex:none}
button.btn{font:inherit}

h3{font-family:var(--display);letter-spacing:-.02em}
/* The homepage's inline link style; service.css never needed it. */
.link{display:inline-flex;align-items:center;gap:.4rem;font-weight:560;color:var(--accent)}
.link:hover{text-decoration:underline;text-underline-offset:4px}
:where(a,button,input,textarea,select,summary):focus-visible{
  outline:2px solid var(--accent);outline-offset:3px;border-radius:4px}
::selection{background:var(--accent);color:var(--accent-ink)}
[hidden]{display:none!important}
.hidden{display:none!important}
.muted{color:var(--text-muted);font-size:.9rem}

/* Forms, verbatim from the homepage's contact form. */
.field{display:grid;gap:.4rem;margin-block-end:1rem}
.field label,label{font-size:.8125rem;font-weight:600;color:var(--text-soft)}
.field :is(input,textarea,select),input,textarea,select{
  width:100%;font:inherit;padding:.75rem .9rem;
  border:1px solid var(--line-strong);border-radius:var(--r-sm);
  background:var(--bg);color:var(--text);font-size:.9375rem;
  transition:border-color .2s,box-shadow .2s}
:is(input,textarea,select):not(:focus,[type=checkbox],[type=color]):hover{border-color:var(--text-muted)}
.field :is(input,textarea,select):focus,input:focus,textarea:focus,select:focus{
  outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in oklab,var(--accent) 18%,transparent)}
.field textarea,textarea{resize:vertical;min-height:6.5rem}
.field-row{display:grid;gap:1rem;grid-template-columns:1fr 1fr}
@media (max-width:520px){.field-row{grid-template-columns:1fr}}
/* A line of help under an input. Small, quiet, and never between the label and
   the box — a hint that pushes the input away from its label reads as a second
   label. */
.hint{display:block;color:var(--text-muted);font-size:.78rem;line-height:1.45;font-weight:400}
.hint code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.95em;
  background:var(--surface-2);border-radius:4px;padding:.05em .3em}
.sub-h{font-size:.9rem;font-weight:650;letter-spacing:-.01em;
  margin:1.4rem 0 .8rem;padding-top:1rem;border-top:1px solid var(--line)}
.legend{display:flex;align-items:flex-start;gap:.5rem;margin-bottom:1rem;line-height:1.55}
.legend .live-dot{margin-top:.42rem}
.warn{color:var(--red);font-size:.875rem;line-height:1.55;margin-bottom:.5rem}
input[type=color]{padding:.25rem;height:2.9rem;cursor:pointer}
input[type=checkbox]{width:auto;min-height:0;padding:0;accent-color:var(--accent)}

/* The site's form confirmation, plus the error and neutral variants an app
   needs. Mint reads as "went through" here exactly as it does there. */
.note{margin-block-start:1rem;padding:.8rem 1rem;border-radius:var(--r-sm);font-size:.875rem;
  background:var(--surface-2);border:1px solid var(--line);color:var(--text-soft)}
.note.ok{background:color-mix(in oklab,var(--mint) 14%,transparent);
  border-color:color-mix(in oklab,var(--mint) 35%,transparent);color:var(--text)}
.note.err{background:color-mix(in oklab,var(--red) 12%,transparent);
  border-color:color-mix(in oklab,var(--red) 35%,transparent);color:var(--text)}
.note:empty{display:none}

/* ── header, app flavour ─────────────────────────────────────────────────── */
/* The signed-in name sits where the homepage puts its "Start a project" CTA. */
.whoami{font-size:.85rem;color:var(--text-muted);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;max-width:12rem}
@media (max-width:880px){.whoami{display:none}}
.bar .tag{font-size:.7rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--text-muted);border:1px solid var(--line);border-radius:100px;padding:.2rem .55rem}
@media (max-width:560px){.bar .tag{display:none}}
/* No /search/ on this host, so the nav's search row goes too. */
.nav .nav-search{display:none!important}

/* ── the burger is for handsets only ──────────────────────────────────────
   On the marketing site the burger appears below 880px, because below 880px
   the nav is the site. Here it is not: /portal/'s nav is five links back to
   the marketing site, and the burger exists only to open them. A laptop with
   a narrowed window is not a phone, so hiding the header behind a hamburger
   there costs a tap and finds nothing a signed-in client or an agent needs.

   So this keys off the device rather than the width — the same test the
   softphone uses for its phone/laptop mode — and the width half matches
   service.css's 880px exactly, or there would be a band where the burger is
   shown and the drawer it opens is still laid out as an inline row.

   Consequence, and the reason for the two rules under it: with no burger
   there is no way to open .nav, so nothing may live only inside it. The
   language pill therefore stays in the bar at every width, and moves into the
   menu only on a handset small enough that it would not otherwise fit. */
.burger{display:none}
.bar>.lang-switch{display:inline-flex}
.lang-switch--nav{display:none}
@media (pointer:coarse) and (max-width:880px){
  .burger{display:grid}
}
@media (pointer:coarse) and (max-width:560px){
  .bar>.lang-switch{display:none}
  .lang-switch--nav{display:inline-flex;align-self:flex-start;margin-block-start:.75rem}
}

main{min-height:60vh}

/* ── page heads ──────────────────────────────────────────────────────────── */
.page-head{padding-block:clamp(1.8rem,5vw,2.8rem) 1.2rem}
.page-head h1{font-size:clamp(1.9rem,1.4rem+2vw,2.6rem);line-height:1.08;letter-spacing:-.03em}
.page-head p{color:var(--text-soft);margin-top:.5rem;max-width:44rem}
.back{background:none;border:0;color:var(--text-muted);cursor:pointer;font:inherit;
  font-size:.875rem;padding:.4rem 0;margin-block:1.4rem .2rem;transition:color .2s}
.back:hover{color:var(--violet)}
.linkbtn{background:none;border:0;color:var(--accent);cursor:pointer;font:inherit;
  font-size:.85rem;padding:0;text-decoration:underline;text-underline-offset:3px}
.linkbtn:hover{color:var(--violet)}

/* ── sign in ─────────────────────────────────────────────────────────────── */
.login-wrap{min-height:calc(100svh - 4.5rem - 6rem);display:grid;place-items:center;
  padding-block:clamp(2rem,6vw,4rem)}
.login-card{width:100%;max-width:25rem;padding:clamp(1.6rem,4vw,2.2rem);
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-lg);box-shadow:var(--shadow-md)}
.login-card h1{font-size:1.6rem;line-height:1.1;letter-spacing:-.03em;margin-bottom:.4rem}
.login-card p.sub{color:var(--text-muted);font-size:.9rem;margin-bottom:1.5rem}
.login-card .btn{width:100%;justify-content:center;min-height:3rem}
.login-foot{margin-top:1rem;text-align:center;font-size:.85rem}
.login-foot.muted{margin-top:.8rem;font-size:.8125rem}
/* The signed-in half of the header: name, call, sign out. Hidden as a group on
   the login view, so one class covers all three. */
.bar-auth{display:inline-flex;align-items:center;gap:.6rem}
@media (max-width:560px){.bar-auth [data-voice-call]{display:none!important}}

/* ── client: the project grid ────────────────────────────────────────────── */
.proj-grid{display:grid;gap:1.25rem;
  grid-template-columns:repeat(auto-fit,minmax(min(16rem,100%),1fr))}
.proj{position:relative;cursor:pointer;
  transition:border-color .2s,translate .3s var(--ease),box-shadow .3s var(--ease)}
.proj:hover{border-color:color-mix(in oklab,var(--violet) 45%,var(--line));
  translate:0 -2px;box-shadow:0 6px 22px -12px color-mix(in oklab,var(--violet) 45%,transparent)}
.proj .kind{font-size:.75rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-muted)}
.proj h3{font-size:1.1rem;margin-block:.35rem .6rem}
.proj:hover h3{color:var(--violet)}

.status-pill{display:inline-flex;align-items:center;font-size:.75rem;font-weight:600;
  padding:.25rem .7rem;border-radius:100px;background:var(--surface-2);
  border:1px solid var(--line);color:var(--text-soft);white-space:nowrap}

/* An unread marker. Amber, not mint: mint means "done" everywhere else. */
.ping{position:absolute;top:1rem;right:1rem;width:9px;height:9px;border-radius:50%;
  background:var(--amber)}
.pdot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--amber);
  vertical-align:middle;flex:none}
@media (prefers-reduced-motion:no-preference){
  .ping,.pdot{animation:ping-pulse 2.6s var(--ease-io,cubic-bezier(.65,0,.35,1)) infinite}
  @keyframes ping-pulse{
    0%,100%{box-shadow:0 0 0 3px color-mix(in oklab,var(--amber) 26%,transparent)}
    50%{box-shadow:0 0 0 7px color-mix(in oklab,var(--amber) 0%,transparent)}}
}

/* ── panels ──────────────────────────────────────────────────────────────── */
.detail-grid{display:grid;gap:1.25rem;grid-template-columns:1fr;margin-bottom:1.25rem}
@media (min-width:880px){.detail-grid{grid-template-columns:1.45fr 1fr}}
.panel{margin-bottom:1.25rem}
.panel h2{font-size:1.15rem;letter-spacing:-.02em;margin-bottom:.9rem;
  display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
.panel h2 .muted{font-weight:400;font-size:.78rem}
.panel>p.muted{margin-bottom:.8rem}
.panel-head{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;margin-bottom:.9rem}
.panel-head h2{margin-bottom:0}
.panel-head .btn{margin-inline-start:auto}
.panel h2 .perk{font-weight:400;font-size:.78rem}
.info-val{font-weight:560;margin-bottom:1rem}
.info-val:last-child{margin-bottom:0}
.picked{margin-top:.5rem;font-size:.85rem}
.panel.danger{border-color:color-mix(in oklab,var(--red) 35%,var(--line))}
.panel.danger h2{color:var(--red)}

/* A row of a text input and its button. */
.inline-row{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:.8rem}
.inline-row input{flex:1 1 12rem}
.inline-row .btn{flex:0 0 auto}
@media (max-width:560px){
  .inline-row input{flex:1 1 100%}
  .inline-row .btn{flex:1 1 auto;justify-content:center}
}

/* The "this is easier than it looks" panel and the photo tip, in the site's
   sunk-panel and mint-rule idioms. */
.easy{background:var(--bg-sunk);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:clamp(1.2rem,3vw,1.6rem);margin-bottom:1.5rem}
.easy h2{font-size:1.05rem;margin-bottom:.7rem}
.easy ol{margin:0;padding-inline-start:1.2rem;display:grid;gap:.5rem;
  color:var(--text-soft);font-size:.94rem}
.easy>p{color:var(--text-muted);font-size:.88rem;margin-top:.9rem}
.tip{border-inline-start:3px solid var(--mint);
  background:color-mix(in oklab,var(--mint) 8%,transparent);
  border-radius:var(--r-sm);padding:.75rem .9rem;margin-block:.8rem 0;
  color:var(--text-soft);font-size:.88rem}
.tip b{color:var(--text)}

/* ── your plan (client portal) ───────────────────────────────────────────── */
.plan-site+.plan-site{margin-top:1.1rem;padding-top:1rem;border-top:1px solid var(--line)}
.plan-site-h{margin-bottom:.4rem;font-size:.92rem}
.plan-row{display:flex;align-items:flex-start;gap:.8rem;padding:.8rem 0}
.plan-row+.plan-row{border-top:1px solid var(--line)}
/* A tick when it's on, an empty ring when it isn't — mint means "done" here as
   everywhere else on the site. */
.plan-state{flex:none;width:1.35rem;height:1.35rem;margin-top:.1rem;border-radius:50%;
  border:1.5px solid var(--line-strong);position:relative}
.plan-row.is-on .plan-state{background:var(--mint);border-color:var(--mint)}
.plan-row.is-on .plan-state::after{content:"";position:absolute;left:.45rem;top:.22rem;width:.3rem;height:.6rem;
  border:solid #04201c;border-width:0 2px 2px 0;rotate:45deg}
.plan-what{flex:1 1 auto;min-width:0;display:grid;gap:.2rem;line-height:1.45}
.plan-what>.muted{font-size:.86rem}
.plan-open{justify-self:start;margin-top:.4rem}
.plan-end{flex:none;display:grid;justify-items:end;gap:.3rem}
.plan-pill{color:var(--mint);border-color:color-mix(in oklab,var(--mint) 45%,transparent)}
@media (max-width:560px){
  .plan-row{flex-wrap:wrap}
  .plan-end{width:100%;justify-items:start;padding-inline-start:2.15rem;grid-auto-flow:column;justify-content:start;align-items:center;gap:.8rem}
}

/* ── theme picker ────────────────────────────────────────────────────────── */
.theme-presets{display:flex;flex-wrap:wrap;gap:.5rem;margin:.6rem 0 1.2rem}
.theme-chip{display:inline-flex;align-items:center;gap:.55rem;
  border:1px solid var(--line);background:var(--surface);border-radius:100px;
  padding:.4rem .85rem .4rem .45rem;cursor:pointer;font:inherit;font-size:.8125rem;
  font-weight:600;color:var(--text-soft);box-shadow:var(--shadow-sm);
  transition:color .2s,border-color .2s,box-shadow .2s}
.theme-chip:hover{color:var(--text);border-color:var(--line-strong)}
.theme-chip.sel{color:var(--text);border-color:var(--accent);
  box-shadow:0 0 0 2px color-mix(in oklab,var(--accent) 18%,transparent)}
.theme-chip .sw{display:inline-flex}
.theme-chip .sw i{width:16px;height:16px;border-radius:50%;display:inline-block;
  border:1px solid rgba(0,0,0,.15)}
.theme-chip .sw i+i{margin-left:-6px}
.theme-custom{display:grid;grid-template-columns:repeat(auto-fit,minmax(9rem,1fr));gap:.9rem}
.theme-custom .field{margin-block-end:0}
@media (max-width:640px){.theme-custom{grid-template-columns:1fr 1fr}}
.theme-preview{border:1px solid var(--line);border-radius:var(--r-lg);
  padding:1.3rem;margin-block:1.2rem}
.theme-preview .tp-h{font-size:1.3rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.25rem}
.theme-preview .tp-btn{display:inline-block;margin-top:.9rem;padding:.5rem 1rem;
  border-radius:100px;font-size:.8125rem;font-weight:600}
/* Anything Claude writes for you — a theme here, a mailshot on /portal/mail/ —
   opens from a box with a hairline violet-to-mint edge. That thin gradient is
   the one mark every AI product has converged on for "the model does this
   part" (Gemini's Help me write, Google's AI panels), and it is the only place
   the two hover/tick colours meet, so it can't be mistaken for anything else. */
.theme-ai{border:1px solid transparent;border-radius:var(--r-lg);padding:1rem 1.1rem;margin-bottom:1.2rem;
  background:linear-gradient(var(--surface),var(--surface)) padding-box,
    linear-gradient(110deg,color-mix(in oklab,var(--violet) 55%,var(--line)),color-mix(in oklab,var(--mint) 55%,var(--line))) border-box}
.theme-ai[open]{background:linear-gradient(var(--surface-2),var(--surface-2)) padding-box,
    linear-gradient(110deg,var(--violet),var(--mint)) border-box}
.theme-ai summary{cursor:pointer;font-weight:600;font-size:.94rem;list-style:none;
  display:flex;align-items:center;gap:.5rem}
.theme-ai summary::-webkit-details-marker{display:none}
.theme-ai summary::after{content:"";margin-inline-start:auto;width:.45rem;height:.45rem;
  border:solid var(--text-muted);border-width:0 2px 2px 0;rotate:45deg;translate:0 -2px;
  transition:rotate .25s var(--ease),translate .25s var(--ease)}
.theme-ai[open] summary::after{rotate:225deg;translate:0 2px}
.theme-ai-p{margin-block:.7rem}

/* ── images ──────────────────────────────────────────────────────────────── */
.thumbs{display:grid;grid-template-columns:repeat(auto-fill,minmax(7rem,1fr));
  gap:.6rem;margin-top:.9rem}
.thumbs:empty{display:none}
.thumb{position:relative;aspect-ratio:4/3;border-radius:var(--r-sm);overflow:hidden;
  border:1px solid var(--line);background:var(--surface-2)}
.thumb img{width:100%;height:100%;object-fit:cover}
.thumb button{position:absolute;top:5px;right:5px;width:24px;height:24px;border-radius:50%;
  border:0;background:rgba(12,12,12,.6);color:#fff;cursor:pointer;font-size:.8rem;
  line-height:1;display:grid;place-items:center;transition:background-color .2s}
.thumb button:hover{background:var(--red)}

/* One typography set, inside the Typography panel. */
.typo-set{margin-top:.9rem;border-radius:var(--r-sm)}
.typo-set .set-top{display:flex;justify-content:space-between;align-items:center;gap:.6rem}
.typo-set b{font-size:1rem}
.typo-delivered{margin-top:1rem;border-top:1px solid var(--line);padding-top:.8rem}
.typo-delivered .lbl{font-weight:600;color:var(--mint);font-size:.8125rem;
  letter-spacing:.04em;text-transform:uppercase}
.typo-delivered p{margin:.4rem 0;white-space:pre-wrap;color:var(--text-soft);font-size:.92rem}

/* ── message thread ──────────────────────────────────────────────────────── */
/* Bubbles, as in every chat surface people already know: soft 18px corners,
   no outline, and the corner nearest the sender tucked in so a run of messages
   reads as one voice. */
.thread{display:grid;gap:.1rem;margin-bottom:1rem}
.msg{padding:.7rem 1rem;border-radius:18px;margin-bottom:.5rem;line-height:1.55;
  font-size:.9375rem;max-width:min(85%,34rem);overflow-wrap:anywhere}
.msg .who{font-size:.74rem;font-weight:600;color:var(--text-muted);margin-bottom:.15rem}
/* The client's own words sit on the accent; the studio's sit on the surface.
   Admin flips which side is "mine", so each page says so on the element. */
.msg.studio{background:var(--surface-2);border-end-start-radius:6px}
.msg.client{background:color-mix(in oklab,var(--accent) 10%,var(--surface));margin-left:auto;
  border-end-end-radius:6px}
.admin .msg.client{background:var(--surface-2);margin-left:0;
  border-end-end-radius:18px;border-end-start-radius:6px}
.admin .msg.studio{background:color-mix(in oklab,var(--accent) 10%,var(--surface));margin-left:auto;
  border-end-start-radius:18px;border-end-end-radius:6px}

/* The reply box: one rounded field with its send button docked inside, the
   way a chat composer looks everywhere now. Focus rings the whole box, not
   just the input, because the whole box is the thing you are typing into. */
.composer{flex-wrap:nowrap;align-items:center;gap:.4rem;margin-top:.4rem;
  padding:.35rem .35rem .35rem .4rem;border:1px solid var(--line-strong);border-radius:100px;
  background:var(--bg);transition:border-color .2s,box-shadow .2s}
.composer:hover{border-color:var(--text-muted)}
.composer:focus-within{border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in oklab,var(--accent) 14%,transparent)}
.composer input,.composer input:hover,.composer input:focus{flex:1 1 auto;min-width:0;
  border:0;box-shadow:none;background:transparent;padding:.55rem .7rem}
.composer .btn{flex:none;min-height:2.4rem}

/* ── admin lists ─────────────────────────────────────────────────────────── */
.list{list-style:none;padding:0;display:grid;gap:.6rem}
.list li{position:relative;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);padding:.9rem 1.1rem;cursor:pointer;
  display:flex;justify-content:space-between;gap:1rem;align-items:center;
  transition:border-color .2s,background-color .2s}
.list li:hover{border-color:color-mix(in oklab,var(--violet) 45%,var(--line));background:var(--surface-2)}
.list li b{font-size:.98rem;font-weight:600}
.list li[data-static]{cursor:default}
.list li[data-static]:hover{border-color:var(--line);background:var(--surface)}
/* A list whose rows open something: a leading monogram, the way a contacts or
   inbox list lets you find a row by shape before reading it, and a trailing
   chevron so the row says it goes somewhere. */
.list--nav li{justify-content:flex-start}
.list--nav li::after{content:"";flex:none;margin-inline-start:auto;width:.5rem;height:.5rem;
  border:solid var(--text-muted);border-width:0 2px 2px 0;rotate:-45deg;
  transition:translate .2s var(--ease),border-color .2s}
.list--nav li:hover::after{translate:3px 0;border-color:var(--violet)}
.list--nav li .status-pill{margin-inline-start:auto}
.list--nav li:has(.status-pill)::after{margin-inline-start:0}
.row-main{min-width:0;line-height:1.4}
.row-main .muted{font-size:.84rem;overflow-wrap:anywhere}
.avatar{flex:none;width:2.5rem;height:2.5rem;border-radius:50%;display:grid;place-items:center;
  background:var(--surface-2);border:1px solid var(--line);color:var(--text-soft);
  font-family:var(--display);font-weight:600;font-size:.92rem;letter-spacing:-.01em}
.list--nav li:hover .avatar{background:var(--surface);border-color:var(--line-strong)}
/* A website row carries its own actions and, when you press Delete, its own
   confirmation — asked in the row it would remove rather than in a dialog that
   has lost track of which one you meant. */
.list li[data-site]{flex-wrap:wrap}
.site-info{flex:1 1 14rem;line-height:1.5}
.site-acts{display:flex;gap:.45rem;flex:0 0 auto}
/* A website's two plans, as switches under its name. Same switch as the
   softphone's profile menu (.pm-switch), so "on" looks the same everywhere. */
.site-plans{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.55rem}
.plan-toggle{display:inline-flex;align-items:center;gap:.5rem;padding:.3rem .75rem .3rem .35rem;
  border:1px solid var(--line);border-radius:100px;background:var(--surface-2);cursor:pointer;
  font:inherit;font-size:.8rem;font-weight:580;color:var(--text-muted);transition:border-color .2s,color .2s}
.plan-toggle:hover{border-color:color-mix(in oklab,var(--violet) 45%,var(--line))}
.plan-toggle[aria-checked="true"]{color:var(--text);border-color:color-mix(in oklab,var(--mint) 40%,var(--line))}
.plan-toggle[disabled]{opacity:.55;cursor:default}
.plan-picks{display:flex;flex-wrap:wrap;gap:.3rem 1.2rem}
.plan-picks .ml-check{margin:0}
.site-confirm{flex-basis:100%;border-top:1px solid var(--line);margin-top:.3rem;padding-top:.8rem}
.site-confirm .inline-row{margin-top:.6rem}
.list li.is-editing{border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in oklab,var(--accent) 12%,transparent)}
@media (max-width:560px){.site-acts{width:100%}.site-acts .btn{flex:1 1 auto;justify-content:center}}

/* The theme the client chose, read back to the studio. */
.theme-read{display:flex;flex-wrap:wrap;gap:1.4rem;align-items:flex-start}
.theme-read .sw-dot{display:inline-block;width:22px;height:22px;border-radius:50%;
  border:1px solid var(--line);vertical-align:middle}
.theme-read .hex{font-size:.8rem}
.w-auto{width:auto}
.push-end{margin-inline-start:auto}
.danger-link{color:var(--red)}
.danger-link:hover{border-color:color-mix(in oklab,var(--red) 45%,var(--line))}
.denied{padding-block:clamp(3rem,8vw,5rem);text-align:center}
.denied h1{font-size:clamp(1.6rem,1.2rem+1.6vw,2.1rem);margin-bottom:.5rem}
.denied-foot{margin-top:1.4rem}
.spaced{margin-top:1rem}
.brief-val{color:var(--text-soft);white-space:pre-wrap}
.back--fwd{margin-block:0 1rem}

.agent{border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);
  padding:1.1rem 1.2rem;margin-bottom:.8rem}
.agent-foot{font-size:.8125rem;margin-top:.6rem}
.agent .top{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}
.agent .top b{font-size:1rem}
.agent .top .muted{font-size:.85rem}
.agent .top .acts{margin-inline-start:auto;display:flex;gap:.45rem}
.live-dot{width:.55rem;height:.55rem;border-radius:50%;background:var(--text-muted);flex:none}
.live-dot.on{background:var(--mint);
  box-shadow:0 0 0 3px color-mix(in oklab,var(--mint) 22%,transparent)}
/* The dot alone needed a hover to explain itself, and a phone has no hover. */
.live-tag{font-size:.72rem;font-weight:600;letter-spacing:.03em;
  padding:.18rem .5rem;border-radius:999px;background:var(--surface-2);color:var(--text-muted)}
.live-tag.is-on{background:color-mix(in oklab,var(--mint) 18%,transparent);color:var(--mint)}
.sitepick-h{margin-top:.8rem;font-size:.8125rem}
.sitepick{display:grid;gap:.4rem;margin-top:.35rem;font-size:.875rem}
.sitepick .srow{display:flex;flex-wrap:wrap;align-items:center;gap:.8rem}
.sitepick .srow b{min-width:9rem;font-weight:560}
.sitepick label{display:inline-flex;align-items:center;gap:.4rem;margin:0;font-weight:400;
  font-size:.875rem;color:var(--text-soft)}
.agent details{margin-top:.7rem}
.agent summary{cursor:pointer;font-size:.85rem;font-weight:600;color:var(--text-muted);
  list-style:none}
.agent summary::-webkit-details-marker{display:none}
.agent summary::before{content:"▸ ";}
.agent details[open] summary::before{content:"▾ ";}
.hours{display:grid;grid-template-columns:3.4rem 1fr 1fr;gap:.4rem;align-items:center;
  margin-top:.6rem;font-size:.85rem}
.hours span{color:var(--text-muted)}
.hours input{padding:.45rem .6rem;font-size:.875rem}
/* Typing the same shift into ten boxes is how a wrong one gets saved. */
.quickfill{display:flex;align-items:center;gap:.45rem;flex-wrap:wrap;margin-top:.6rem;
  font-size:.8125rem;color:var(--text-muted)}
.quickfill input{width:auto;padding:.4rem .55rem;font-size:.8125rem}

.vm{display:flex;align-items:center;gap:.8rem;flex-wrap:wrap;
  border-bottom:1px solid var(--line);padding:.8rem 0;font-size:.9rem}
.vm:last-child{border-bottom:0}
.vm audio{height:2.2rem;max-width:16rem}
.vm .when{color:var(--text-muted);font-size:.8125rem;margin-inline-start:auto}

/* "New" until someone opens it, then the name of whoever did. The amber is the
   same one the call history uses for a voicemail outcome, so the two screens
   agree about what an unanswered call looks like. */
.vm-tag{font-size:.72rem;font-weight:600;letter-spacing:.03em;white-space:nowrap;
  padding:.2rem .55rem;border-radius:999px;background:var(--surface-2);color:var(--text-muted)}
.vm-tag.is-new{background:color-mix(in oklab,var(--amber) 20%,transparent);color:var(--amber)}
.vm-count{font-size:.72rem;font-weight:700;letter-spacing:.03em;vertical-align:middle;
  padding:.2rem .55rem;border-radius:999px;
  background:color-mix(in oklab,var(--amber) 20%,transparent);color:var(--amber)}
.vm.is-new,.hist-row.is-new{border-inline-start:3px solid var(--amber);padding-inline-start:.7rem}

/* ── softphone ───────────────────────────────────────────────────────────── */
.duty{padding:clamp(1.3rem,4vw,1.9rem);margin-block:.6rem 1.4rem;text-align:center;
  transition:border-color .4s var(--ease),background-color .4s var(--ease)}
/* On duty, the card itself says so — a soft mint wash from the top, the way a
   live assistant surface glows while it is listening — so the state reads from
   across the room, not only from a 7px dot. */
.duty[data-duty="online"]{border-color:color-mix(in oklab,var(--mint) 40%,var(--line));
  background:radial-gradient(120% 70% at 50% 0%,color-mix(in oklab,var(--mint) 12%,transparent),transparent 70%),var(--surface)}
/* The duty state, as the card's headline: a big pill with a big dot.
   Off duty is grey and hollow; connecting is amber and blinks; on duty is a
   filled mint pill whose dot breathes — three states that can't be mistaken
   for one another at a glance, even across a room. The same pill (smaller)
   says "In a call" on the call screen. */
.duty-status{display:inline-flex;align-items:center;gap:.5rem;font-size:.8125rem;
  font-weight:600;color:var(--text-muted);border:1px solid var(--line);
  background:var(--surface-2);border-radius:100px;padding:.3rem .85rem;margin-bottom:1rem}
.duty .duty-status{gap:.7rem;margin:0 0 1rem;padding:.7rem 1.35rem .7rem 1.1rem;
  font-family:var(--font);font-size:1.15rem;font-weight:650;letter-spacing:-.01em;line-height:1.2;
  border-width:1.5px;transition:background-color .3s,border-color .3s,color .3s}
.duty-dot{width:7px;height:7px;border-radius:50%;background:var(--text-muted);flex:none}
.duty .duty-dot{width:.85rem;height:.85rem;background:transparent;border:2px solid var(--text-muted)}
.duty[data-duty="online"] .duty-status{color:#04201c;background:var(--mint);border-color:var(--mint);
  box-shadow:0 8px 24px -10px color-mix(in oklab,var(--mint) 80%,transparent)}
.duty[data-duty="online"] .duty-dot{background:#04201c;border-color:#04201c}
.duty[data-duty="connecting"] .duty-status{color:var(--text);
  background:color-mix(in oklab,var(--amber) 14%,var(--surface));
  border-color:color-mix(in oklab,var(--amber) 55%,transparent)}
[data-duty="online"] .duty-dot{background:var(--mint);
  box-shadow:0 0 0 3px color-mix(in oklab,var(--mint) 22%,transparent)}
[data-duty="connecting"] .duty-dot{background:var(--amber);border-color:var(--amber)}
@media (prefers-reduced-motion:no-preference){
  [data-duty="connecting"] .duty-dot{animation:blink 1s infinite}
  @keyframes blink{50%{opacity:.25}}
  .duty[data-duty="online"] .duty-dot{animation:duty-breathe 2.4s var(--ease) infinite}
  @keyframes duty-breathe{
    0%,100%{box-shadow:0 0 0 0 color-mix(in oklab,#04201c 35%,transparent)}
    50%{box-shadow:0 0 0 .35rem color-mix(in oklab,#04201c 0%,transparent)}}
}
.duty>p{color:var(--text-muted);font-size:.94rem;margin-bottom:1.3rem;
  max-width:34rem;margin-inline:auto}
.sites{display:flex;flex-wrap:wrap;gap:.45rem;justify-content:center;margin-bottom:1.1rem}
.duty-chip{font-size:.8125rem;font-weight:500;border:1px solid var(--line);
  background:var(--surface-2);border-radius:100px;padding:.25rem .75rem;color:var(--text-soft)}
/* A list outside its own hours: still yours, just not ringing. */
.duty-chip--shut{opacity:.5;border-style:dashed}
.btn--big{padding:.9rem 1.7rem;font-size:1rem}

.ring{position:fixed;inset:0;z-index:150;display:grid;place-items:center;padding:1rem;
  background:color-mix(in oklab,var(--bg) 86%,transparent);
  backdrop-filter:blur(16px) saturate(1.6);-webkit-backdrop-filter:blur(16px) saturate(1.6)}
.ring-card{width:100%;max-width:24rem;padding:clamp(1.6rem,5vw,2.2rem);text-align:center;
  box-shadow:var(--shadow-lg)}
.ring-pulse{width:4.5rem;height:4.5rem;border-radius:50%;margin:0 auto 1.3rem;
  display:grid;place-items:center;background:var(--mint);color:#04201c}
.ring-pulse svg{width:2rem;height:2rem}
@media (prefers-reduced-motion:no-preference){
  .ring-pulse{animation:ringpulse 1.2s var(--ease-io,cubic-bezier(.65,0,.35,1)) infinite}
  @keyframes ringpulse{
    0%,100%{box-shadow:0 0 0 0 color-mix(in oklab,var(--mint) 45%,transparent)}
    70%{box-shadow:0 0 0 1.1rem color-mix(in oklab,var(--mint) 0%,transparent)}}
}
.ring-card{border-radius:28px}
.ring-card h2{font-size:1.3rem;letter-spacing:-.02em;margin-bottom:.25rem}
.ring-card .site{color:var(--text-muted);font-size:.9rem;margin-bottom:1.5rem}

.live{padding:clamp(1.6rem,4vw,2.2rem);margin-block:1.6rem;text-align:center}
.live .timer{font-family:var(--display);font-size:2.4rem;font-weight:600;
  font-variant-numeric:tabular-nums;letter-spacing:-.03em;margin:.4rem 0 1.3rem}
/* The caller's monogram, as a phone app shows a contact photo: the in-call
   screen should look like a call, not like a form. */
.call-avatar{width:4.5rem;height:4.5rem;border-radius:50%;margin:.9rem auto .8rem;
  display:grid;place-items:center;background:var(--surface-2);border:1px solid var(--line);
  font-family:var(--display);font-size:1.6rem;font-weight:600;letter-spacing:-.02em;color:var(--text)}

/* Both directions of the call, under the timer. Mint bars light with sound. */
.levels{display:flex;gap:1.4rem;justify-content:center;margin:-.6rem 0 1.3rem}
.mic-meter{display:inline-flex;align-items:center;gap:.5rem;font-size:.78rem;color:var(--text-muted)}
#live-hear{margin:0 auto 1.3rem}
.mic-bars{display:inline-flex;align-items:flex-end;gap:3px;height:14px}
.mic-bars i{width:4px;border-radius:2px;background:var(--line-strong);transition:background-color .1s}
.mic-bars i:nth-child(1){height:30%}.mic-bars i:nth-child(2){height:45%}
.mic-bars i:nth-child(3){height:60%}.mic-bars i:nth-child(4){height:80%}.mic-bars i:nth-child(5){height:100%}
.mic-bars i.on{background:var(--mint)}
:root[data-device="phone"] .levels{margin:-1.8rem 0 2.2rem}

/* ── call controls ───────────────────────────────────────────────────────────
   Round icon buttons with the word underneath, on the laptop as well as the
   phone — the control row of a phone dialler or a video call. The circle is
   the icon itself (padding + background on the svg), so the button stays a
   plain transparent column and its label sits outside the circle, where it
   can be read, instead of squeezed inside it. Hang up and Decline are red,
   Answer is mint, a pressed toggle (Mute, Speaker) fills mint. */
:is(.live-actions,.ring-actions){display:flex;gap:1.4rem;justify-content:center;flex-wrap:wrap}
:is(.live-actions,.ring-actions) .btn,
:is(.live-actions,.ring-actions) .btn:hover{flex-direction:column;justify-content:flex-start;
  gap:.45rem;padding:0;background:none;border:0;box-shadow:none;
  font-size:.8rem;font-weight:580;color:var(--text-soft);white-space:normal}
:is(.live-actions,.ring-actions) .btn svg{width:3.6rem;height:3.6rem;padding:1.05rem;border-radius:50%;
  background:var(--surface-2);border:1px solid var(--line);color:var(--text);
  transition:background-color .2s,border-color .2s,color .2s,scale .15s var(--ease)}
:is(.live-actions,.ring-actions) .btn:hover svg{border-color:color-mix(in oklab,var(--violet) 40%,var(--line-strong))}
:is(.live-actions,.ring-actions) .btn:active{scale:1}
:is(.live-actions,.ring-actions) .btn:active svg{scale:.94}
:is(.live-actions,.ring-actions) .btn--danger svg{background:var(--red);border-color:var(--red);color:#fff}
:is(.live-actions,.ring-actions) .btn--mint svg{background:var(--mint);border-color:var(--mint);color:#04201c}
:is(.live-actions,.ring-actions) .btn--danger:hover svg{background:color-mix(in oklab,var(--red) 88%,#000)}
:is(.live-actions,.ring-actions) .btn--mint:hover svg{background:color-mix(in oklab,var(--mint) 88%,#000)}
.live-actions .btn[aria-pressed="true"] svg{background:var(--mint);border-color:var(--mint);color:#04201c}
/* Answer is the one control that should be found without looking. */
@media (prefers-reduced-motion:no-preference){
  .ring-actions .btn--mint svg{animation:answer-nudge 1.6s var(--ease) infinite}
  @keyframes answer-nudge{0%,55%,100%{translate:0 0}65%{translate:0 -4px}75%{translate:0 0}85%{translate:0 -2px}}
}
/* Who you're talking to, kept on screen for the whole call — on a busy day the
   ring card is gone in a second and you still need to know whose site this is. */
.who{margin-bottom:.2rem}
.who .caller{font-family:var(--display);font-size:1.4rem;font-weight:600;letter-spacing:-.03em}
.who .where{color:var(--text-muted);font-size:.9rem}
.who .where b{color:var(--text-soft);font-weight:560}
.route-note{color:var(--text-muted);font-size:.8125rem;margin-top:1rem;
  max-width:26rem;margin-inline:auto}

.hist h2{font-size:1.15rem;letter-spacing:-.02em;margin-bottom:.8rem}
.hist-row{display:flex;align-items:center;gap:.8rem;flex-wrap:wrap;
  border-bottom:1px solid var(--line);padding:.8rem 0;font-size:.9rem}
.hist-row:last-child{border-bottom:0}
.hist-row audio{height:2.2rem;max-width:15rem}
.hist-row .when{color:var(--text-muted);font-size:.8125rem;margin-inline-start:auto}
.out-tag{font-size:.72rem;font-weight:600;letter-spacing:.03em;text-transform:uppercase;
  padding:.15rem .6rem;border-radius:100px;background:var(--surface-2);
  color:var(--text-muted);flex:none}
.out-tag.answered{background:color-mix(in oklab,var(--mint) 18%,transparent);color:var(--mint)}
.out-tag.voicemail{background:color-mix(in oklab,var(--amber) 20%,transparent);color:var(--amber)}
.out-tag.abandoned,.out-tag.no_agents,.out-tag.missed{background:var(--surface-2);color:var(--text-muted)}
.empty{color:var(--text-muted);font-size:.9rem;padding:.9rem 0}
@media (max-width:560px){.hist-row{flex-wrap:wrap}.hist-row .when{margin-inline-start:0}}

/* ── the softphone's header: one profile button and its menu ──────────────
   The phone keeps nothing else in its header. Every control an agent needs
   mid-shift is in here, and none of it navigates. */
.profile{position:relative}
.profile-btn{position:relative;width:2.5rem;height:2.5rem;padding:0;border-radius:50%;
  border:1px solid var(--line-strong);background:var(--surface);cursor:pointer;
  display:grid;place-items:center;transition:border-color .2s,box-shadow .2s}
.profile-btn:hover,.profile-btn[aria-expanded="true"]{border-color:var(--violet);
  box-shadow:0 0 0 3px color-mix(in oklab,var(--violet) 18%,transparent)}
.profile-btn svg{width:100%;height:100%;border-radius:50%;overflow:hidden}
.profile-btn .pf-bg{fill:var(--surface-2)}
.profile-btn .pf-fg{fill:var(--text-muted)}
/* Duty at a glance, without opening anything. */
.profile-dot{position:absolute;right:-1px;bottom:-1px;width:.72rem;height:.72rem;border-radius:50%;
  background:var(--text-muted);border:2px solid var(--bg)}
.profile[data-duty="online"] .profile-dot{background:var(--mint)}
.profile[data-duty="connecting"] .profile-dot{background:var(--amber)}
.profile-menu{position:absolute;right:0;top:calc(100% + .55rem);z-index:120;min-width:15.5rem;
  padding:.4rem;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--shadow-lg);display:grid;gap:.1rem}
.pm-who{padding:.45rem .75rem .65rem;margin-bottom:.25rem;border-bottom:1px solid var(--line);
  font-size:.8125rem;font-weight:600;color:var(--text-soft);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pm-item{display:flex;align-items:center;justify-content:space-between;gap:1rem;width:100%;
  padding:.62rem .75rem;border:0;border-radius:var(--r-sm);background:transparent;
  font:inherit;font-size:.9rem;font-weight:520;color:var(--text);text-align:start;cursor:pointer}
button.pm-item:hover{background:var(--surface-2)}
.pm-lang{cursor:default}
.pm-danger{color:var(--red)}
.pm-switch{position:relative;flex:none;width:2.15rem;height:1.25rem;border-radius:100px;
  background:var(--line-strong);transition:background-color .2s}
.pm-switch::after{content:"";position:absolute;top:.15rem;left:.15rem;width:.95rem;height:.95rem;
  border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:translate .2s var(--ease)}
[aria-checked="true"]>.pm-switch{background:var(--mint)}
[aria-checked="true"]>.pm-switch::after{translate:.9rem 0}

/* "Add the phone to your device" — Chrome only, asked once. */
.install{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;margin-top:.6rem;padding:1rem 1.2rem}
.install img{border-radius:11px;flex:none}
.install-body{flex:1 1 14rem;display:grid;gap:.15rem;font-size:.9rem}
.install-acts{display:flex;gap:.5rem}
.install:not(.hidden)+.duty{margin-top:.8rem}
@media (max-width:560px){.install-acts{width:100%}.install-acts .btn{flex:1 1 auto;justify-content:center}}

/* New messages · Messages · Recent calls — closed until pressed. */
/* ── segmented controls ──────────────────────────────────────────────────────
   One idiom for every set of tabs in the app: a sunk pill track with the
   chosen option raised on the surface. It is the language pill's own look
   (.lang-switch / .lang-opt in service.css), grown to tab size, so the
   header and the page speak the same visual language — and it is the
   segmented button people know from Google's apps and every AI chat's model
   picker. Used by the softphone's log tabs, the mailer's tabs and admin's
   section switch. */
:is(.log-tabs,.ml-tabs,.app-tabs){display:flex;gap:.2rem;padding:.25rem;width:fit-content;max-width:100%;
  overflow-x:auto;scrollbar-width:none;border:1px solid var(--line);border-radius:100px;
  background:var(--surface-2)}
:is(.log-tabs,.ml-tabs,.app-tabs)::-webkit-scrollbar{display:none}
:is(.log-tab,.ml-tabs button,.app-tabs button){appearance:none;display:inline-flex;align-items:center;
  gap:.45rem;flex:none;padding:.5rem 1rem;border:0;border-radius:100px;background:transparent;
  font:inherit;font-size:.875rem;font-weight:580;color:var(--text-muted);cursor:pointer;
  white-space:nowrap;transition:color .2s,background-color .2s,box-shadow .2s}
:is(.log-tab,.ml-tabs button,.app-tabs button):hover{color:var(--text)}
:is(.log-tab[aria-pressed="true"],.ml-tabs button.is-active,.app-tabs button[aria-current="page"]){
  background:var(--surface);color:var(--text);box-shadow:var(--shadow-sm)}
.log-tabs{margin-bottom:1rem}
.app-tabs{margin-block:1.4rem .2rem}
.hist{margin-bottom:1.6rem}
.hist>.muted{margin-bottom:.8rem}
#vmail[data-filter="new"] .hist-row:not(.is-new){display:none}
@media (max-width:560px){
  .log-tabs{width:100%}
  .log-tab{flex:1 1 auto;justify-content:center;padding-inline:.6rem}
}

/* Everyone else calling while the ring card is up. */
.ring-more{margin-top:1.4rem;padding-top:1rem;border-top:1px solid var(--line);text-align:start;
  width:100%;max-width:24rem;margin-inline:auto}
.ring-more-h{font-size:.8125rem;font-weight:600;color:var(--text-muted);margin-bottom:.5rem}
.ring-row{display:flex;align-items:center;gap:.5rem;padding:.55rem 0;font-size:.85rem;color:var(--text-muted)}
.ring-row+.ring-row{border-top:1px solid var(--line)}
.ring-row>span{flex:1 1 auto;min-width:0;line-height:1.35}
.ring-row b{color:var(--text);font-weight:600}

/* ── the softphone's guide ───────────────────────────────────────────────────
   An overlay, never a page of its own: leaving /portal/phone/ drops the duty
   socket, so help that you can only read by navigating away is help that takes
   the agent off shift to deliver it. */
.duty-help{margin-top:.9rem;font-size:.85rem}
.guide{position:fixed;inset:0;z-index:170;display:grid;place-items:center;padding:1rem;
  background:color-mix(in oklab,#000 55%,transparent);backdrop-filter:blur(3px)}
.guide-card{width:100%;max-width:38rem;max-height:min(84svh,52rem);padding:0;
  display:flex;flex-direction:column;overflow:hidden}
.guide-top{display:flex;align-items:center;gap:.8rem;padding:1.1rem 1.3rem;
  border-bottom:1px solid var(--line)}
.guide-top h2{font-size:1.15rem;letter-spacing:-.02em;margin:0;flex:1 1 auto}
.guide-body{overflow-y:auto;padding:1.1rem 1.3rem;font-size:.92rem;line-height:1.65;
  color:var(--text-soft);-webkit-overflow-scrolling:touch}
.guide-body h3{font-size:.95rem;font-weight:650;color:var(--text);letter-spacing:-.01em;
  margin:1.4rem 0 .35rem}
.guide-body h3:first-child{margin-top:0}
.guide-body p{margin:0 0 .3rem}
.guide-body ul{margin:.2rem 0 .3rem;padding-inline-start:1.1rem;display:grid;gap:.35rem}
.guide-body b{color:var(--text);font-weight:600}
.guide-foot{padding:.9rem 1.3rem;border-top:1px solid var(--line);text-align:end}
@media (max-width:560px){
  .guide{padding:0}
  .guide-card{max-width:none;max-height:none;height:100svh;border-radius:0;border:0}
}

/* ── phone mode ──────────────────────────────────────────────────────────────
   A touch device is held like a phone, so it gets phone controls: the in-call
   screen takes the whole viewport and the actions become thumb-sized circles.
   A laptop keeps the compact card it already had. */
:root[data-device="phone"] .live{position:fixed;inset:0;z-index:160;margin:0;border:0;
  border-radius:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:.2rem;background:var(--bg);box-shadow:none}
:root[data-device="phone"] .live .timer{font-size:3.2rem;margin:.6rem 0 2.4rem}
:root[data-device="phone"] .who .caller{font-size:1.8rem}
:root[data-device="phone"] .call-avatar{width:6.5rem;height:6.5rem;font-size:2.3rem;margin-bottom:1.2rem}
:root[data-device="phone"] .live-actions{gap:1.8rem}
:root[data-device="phone"] .live-actions .btn svg{width:4.6rem;height:4.6rem;padding:1.4rem}
:root[data-device="phone"] .ring-card{max-width:none;width:100%;height:100%;border-radius:0;
  box-shadow:none;display:flex;flex-direction:column;align-items:center;justify-content:center}
:root[data-device="phone"] .ring-pulse{width:6.5rem;height:6.5rem;margin-bottom:2rem}
:root[data-device="phone"] .ring-pulse svg{width:3rem;height:3rem}
:root[data-device="phone"] .ring-actions{gap:3.5rem;margin-top:1rem}
:root[data-device="phone"] .ring-actions .btn svg{width:5rem;height:5rem;padding:1.5rem}
/* Each button carries its label twice: the full word for the laptop and a
   short one for the phone, where "Hang up" becomes "End". Only one shows. */
:root:not([data-device="phone"]) span.btn-glyph{display:none}
.btn-glyph{display:block}
:root[data-device="phone"] .btn-word{display:none}
/* The megaphone is crossed out while the call is on the earpiece. */
#live-speaker[aria-pressed="true"] .spk-cross{display:none}

/* ── mailing list (/portal/mail/) ────────────────────────────────────────── */
/* Everything is .ml-… so nothing here can mean something else on the site.

   Density: the mailer is a working tool that people sit in — reading a list,
   ticking rows, writing a mailshot — so under .ml-app it runs tighter than the
   rest of the portal: smaller controls and table rows, less air in the cards,
   and the header (name, address, allowance) on a single line. Everything is
   scoped to .ml-app, so the other portal pages keep their own spacing. */
.ml-app{padding-bottom:2rem}
.ml-app .card.panel{padding:1rem 1.1rem;margin-bottom:.9rem}
.ml-app .panel h2{font-size:1rem;margin-bottom:.35rem}
.ml-app .panel>p.muted,.ml-app .ml-add p.muted{font-size:.84rem;margin-bottom:.7rem;line-height:1.5}
.ml-app .field{margin-block-end:.7rem;gap:.3rem}
.ml-app .field-row{gap:.7rem;align-items:start}
/* A field beside a taller one (a neighbour with a hint under it) used to
   stretch to match — the input grew and a gap opened under its label. */
.ml-app .field{align-content:start}
.ml-app :is(input:not([type=checkbox]):not([type=color]),select,textarea){
  padding:.48rem .7rem;font-size:.875rem;border-radius:8px}
.ml-app .btn--sm{padding:.42rem .85rem;font-size:.8125rem}
.ml-app .inline-row{margin-top:.55rem;gap:.45rem}
.ml-app .hint{font-size:.74rem}
.ml-app .note{margin-block-start:.6rem;padding:.55rem .8rem;font-size:.84rem}
.ml-app .back{margin:0 0 .15rem;font-size:.8125rem;padding:0;text-align:start}

/* One header line: back link, name and address on the left, the two
   allowances on the right. */
.ml-top{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;
  gap:.8rem 2rem;padding-block:1.1rem .9rem}
.ml-title{min-width:0}
.ml-title h1{font-size:clamp(1.35rem,1.1rem+1vw,1.75rem);line-height:1.1;letter-spacing:-.03em}
.ml-title .muted{font-size:.84rem;margin-top:.15rem;overflow-wrap:anywhere}
.ml-usage{display:flex;flex-wrap:wrap;gap:.6rem 1.6rem;font-size:.8125rem;color:var(--text-soft)}
.ml-usage>div{min-width:10.5rem}
.ml-usage b{font-family:var(--display);font-size:1.05rem;font-weight:600;color:var(--text);
  font-variant-numeric:tabular-nums}
.ml-bar{display:block;height:5px;border-radius:99px;background:var(--surface-2);
  margin-top:.35rem;overflow:hidden}
.ml-bar i{display:block;height:100%;background:var(--accent);border-radius:inherit;
  transition:width .6s var(--ease)}
.ml-tabs{margin:0 0 .9rem}
.ml-app .ml-tabs button{padding:.42rem .9rem;font-size:.8125rem}

/* The list's toolbar: filters on the left, actions on the right, one row. */
.ml-filters{display:flex;flex-wrap:wrap;gap:.45rem;align-items:center;margin-bottom:.6rem}
.ml-filters input{flex:1 1 12rem}
/* same specificity as the .ml-app field rule above and later, so it wins */
.ml-app .ml-filters input[type=search]{border-radius:100px;padding-inline:.95rem}
.ml-filters select{flex:0 1 10.5rem;width:auto}
.ml-tools{display:flex;flex-wrap:wrap;gap:.4rem;margin-inline-start:auto}

/* Filter chips: the chosen one gets a tick rather than an inverted fill, so a
   row of them stays calm and the choice still reads at a glance. */
.ml-counts{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.6rem}
.ml-counts:empty{display:none}
.ml-count{appearance:none;display:inline-flex;align-items:center;gap:.35rem;font:inherit;font-size:.76rem;
  padding:.25rem .65rem;border-radius:8px;cursor:pointer;
  background:var(--surface);border:1px solid var(--line-strong);color:var(--text-soft);
  transition:background-color .2s,border-color .2s,color .2s}
.ml-count:hover{border-color:color-mix(in oklab,var(--violet) 45%,var(--line-strong));background:var(--surface-2)}
.ml-count.is-active{background:color-mix(in oklab,var(--mint) 14%,var(--surface));
  border-color:color-mix(in oklab,var(--mint) 45%,transparent);color:var(--text)}
.ml-count.is-active::before{content:"";width:.28rem;height:.55rem;margin:0 .15rem .15rem .05rem;
  border:solid var(--mint);border-width:0 2px 2px 0;rotate:45deg}
/* With rows ticked, the actions for them ride along at the top of the screen
   — an inbox's selection bar — so they are still there after scrolling down
   a long page of addresses to tick more. */
.ml-bulk{position:sticky;top:5rem;z-index:5;display:flex;flex-wrap:wrap;gap:.4rem;align-items:center;
  padding:.4rem .45rem .4rem .9rem;margin-bottom:.6rem;border-radius:1.2rem;
  background:var(--accent);color:var(--accent-ink);box-shadow:var(--shadow-md)}
.ml-bulk>span{font-weight:600;font-size:.8125rem;margin-inline-end:auto}
.ml-app .ml-bulk select:not([multiple]){width:auto;padding:.35rem .7rem;border-radius:100px;font-size:.8rem}
.ml-bulk .btn--ghost{background:transparent;color:var(--accent-ink);border-color:color-mix(in oklab,var(--accent-ink) 35%,transparent)}
.ml-bulk .btn--ghost:hover{background:color-mix(in oklab,var(--accent-ink) 14%,transparent);
  border-color:color-mix(in oklab,var(--accent-ink) 55%,transparent)}
.ml-bulk .danger-link{color:color-mix(in oklab,var(--red) 70%,var(--accent-ink))}
@media (max-width:560px){.ml-bulk{border-radius:var(--r-lg)}}

.ml-table-wrap{overflow-x:auto;margin:0 0 .6rem;border:1px solid var(--line);border-radius:var(--r-sm)}
/* No rows yet (the DNS records before a domain is set up): no empty frame. */
.ml-table-wrap:has(>table:empty){display:none}
.ml-table{width:100%;border-collapse:collapse;font-size:.84rem}
.ml-table th{text-align:start;font-weight:600;color:var(--text-muted);font-size:.7rem;
  letter-spacing:.04em;text-transform:uppercase;padding:.45rem .65rem;background:var(--surface-2);
  border-bottom:1px solid var(--line);white-space:nowrap}
.ml-table td{padding:.42rem .65rem;border-bottom:1px solid var(--line);vertical-align:middle;
  transition:background-color .15s}
.ml-table tbody tr:hover td{background:color-mix(in oklab,var(--surface-2) 60%,transparent)}
.ml-table tr:last-child td{border-bottom:0}
.ml-table .status-pill{font-size:.7rem;padding:.12rem .5rem}
.ml-email{word-break:break-all}
.ml-dns td{font-family:ui-monospace,"Cascadia Code",Consolas,monospace;font-size:.76rem}
.ml-tag{display:inline-block;font-size:.7rem;padding:.05rem .45rem;border-radius:99px;margin:.1rem 0;
  border:1px solid var(--line-strong);color:var(--text-soft)}
/* The Checked column: what the last mailbox check said, and how long ago. */
.ml-verdict{font-size:.78rem;white-space:nowrap}
.ml-v-ok{color:var(--mint)}
.ml-v-bad{color:var(--red)}
.ml-checking{color:var(--amber)}
@media (prefers-reduced-motion:no-preference){
  .ml-checking{animation:blink 1.4s infinite}
}
@media (max-width:720px){.ml-hide-sm{display:none}}
.status-pill.ml-st-active,.status-pill.ml-ss-sent{color:var(--mint);border-color:color-mix(in oklab,var(--mint) 45%,transparent)}
.status-pill:is(.ml-st-bounced,.ml-st-complained,.ml-st-invalid,.ml-ss-failed){color:var(--red);
  border-color:color-mix(in oklab,var(--red) 45%,transparent)}
.status-pill:is(.ml-st-pending,.ml-st-unvalidated,.ml-ss-scheduled,.ml-ss-sending){color:var(--amber);
  border-color:color-mix(in oklab,var(--amber) 45%,transparent)}
.ml-pager{display:flex;align-items:center;justify-content:space-between;gap:.6rem;font-size:.8125rem}

/* Add / import, opened from the toolbar, with the one-address checker beside it. */
.ml-add-grid{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);gap:1.4rem}
.ml-checker{border-inline-start:1px solid var(--line);padding-inline-start:1.4rem}
.ml-checker .inline-row input{flex:1 1 10rem}
.ml-c-out{margin-top:.6rem;font-size:.84rem;line-height:1.45;overflow-wrap:anywhere}
@media (max-width:760px){
  .ml-add-grid{grid-template-columns:1fr}
  .ml-checker{border-inline-start:0;padding-inline-start:0;border-top:1px solid var(--line);padding-top:1rem}
}
.ml-lbl{margin:.6rem 0 .35rem}
.ml-chips{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.6rem}
.ml-app .theme-chip{padding:.28rem .7rem;font-size:.78rem}
.ml-check{display:flex;gap:.55rem;align-items:flex-start;margin:.4rem 0 .2rem;font-weight:500;font-size:.84rem;line-height:1.45}
/* A fixed-size box, so the note under it can indent by exactly box + gap. */
.ml-check input{flex:none;width:1rem;height:1rem;margin:.1rem 0 0}
/* The line under a checkbox belongs to its label, so it starts where the
   label's words start, not under the box. */
.ml-check+.hint{padding-inline-start:1.55rem;margin-bottom:.9rem}

/* One rhythm down the Add panel: blocks .9rem apart, so the gaps read as
   deliberate instead of four different sizes. */
.ml-add h2{margin-bottom:.3rem}
.ml-add .field{margin-block-end:.5rem}
.ml-add .field+.inline-row{margin-top:0}
.ml-add .ml-lbl{margin:.9rem 0 .4rem}
.ml-add .ml-chips{margin-bottom:.9rem}
.ml-add .ml-check{margin-top:0}
.ml-checker .inline-row{margin-top:0}

/* Mailshots: a compact list, then the composer. */
.ml-head{display:flex;flex-wrap:wrap;gap:.6rem;align-items:center;justify-content:space-between;margin-bottom:.7rem}
.ml-head p{font-size:.84rem;margin:0}
.ml-head h2{margin:0}
.ml-shots{display:grid;gap:.4rem}
/* "No mailshots yet" as a real empty state, not a stray line of text. */
.ml-shots>p.muted{padding:2rem 1rem;text-align:center;font-size:.875rem;
  border:1px dashed var(--line-strong);border-radius:var(--r-lg)}
.ml-shot{display:flex;gap:1rem;align-items:center;justify-content:space-between;padding:.7rem 1rem;
  margin:0;cursor:pointer;font-size:.875rem;transition:border-color .2s,background-color .2s}
.ml-shot:hover{border-color:color-mix(in oklab,var(--violet) 45%,var(--line));background:var(--surface-2)}
.ml-shot b{font-weight:600}
.ml-shot .muted{font-size:.8rem}
.ml-shot-top{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .7rem;margin-bottom:.7rem}
.ml-shot-top .back{flex-basis:100%}
.ml-shot-top h2{font-size:1.15rem;letter-spacing:-.02em;margin:0}
.ml-compose{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:.9rem;align-items:start}
.ml-code{font-family:ui-monospace,"Cascadia Code",Consolas,monospace;line-height:1.5}
.ml-app .field textarea.ml-code{font-size:.8rem}
.ml-aud{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .6rem}
.ml-aud>.muted{font-size:.8125rem}
.ml-aud .ml-chips{margin:0}
.ml-count-line{font-size:.8rem;margin-top:.4rem}
.ml-send{display:flex;flex-wrap:wrap;gap:.6rem;align-items:flex-end;margin-top:.8rem;padding-top:.8rem;
  border-top:1px solid var(--line)}
.ml-send .field{margin:0;flex:1 1 12rem}
.ml-shot-acts{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.7rem}
.ml-preview-card{position:sticky;top:5.5rem}
.ml-preview{width:100%;height:600px;border:1px solid var(--line);border-radius:var(--r-sm);background:#f4f3f0}
.ml-stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(7.5rem,1fr));gap:.5rem;margin:.6rem 0}
.ml-stat{display:flex;flex-direction:column;gap:.1rem;padding:.6rem .8rem;border:1px solid var(--line);
  border-radius:var(--r-sm);background:var(--surface-2)}
.ml-stat b{font-family:var(--display);font-size:1.25rem;line-height:1.1}
.ml-stat span{font-size:.78rem;color:var(--text-soft)}

/* Labels and settings. */
.ml-label-add{margin:0 0 .7rem}
.ml-labels li{padding:.6rem .9rem}
.ml-settings{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(24rem,100%),1fr));gap:.9rem;align-items:start}
.ml-settings>.card.panel{margin:0}
.ml-embed .sub-h{margin-top:1.1rem;padding-top:.9rem}
.ml-embed .sub-h+p.muted{margin-bottom:.5rem}
.ml-snippet{white-space:pre-wrap;word-break:break-all;font-size:.76rem;padding:.7rem .85rem;margin:0;
  border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface-2);
  font-family:ui-monospace,"Cascadia Code",Consolas,monospace}
@media (max-width:900px){
  .ml-compose{grid-template-columns:1fr}
  .ml-preview-card{position:static}
  .ml-preview{height:500px}
}

/* ── app polish ──────────────────────────────────────────────────────────────
   Small things the app tools people use every day all do, and the portal
   didn't. */

/* A view arrives rather than appears. Every screen here is shown by removing
   .hidden, which restarts the animation, so this fires on each switch — short
   enough not to slow anyone down, and off for reduced motion. */
@media (prefers-reduced-motion:no-preference){
  :is(#dashboard,#detail,#v-clients,#v-client,#v-voice,#v-project,.ml-pane,#shot-view,.hist,.login-card){
    animation:view-in .32s var(--ease) backwards}
  @keyframes view-in{from{opacity:0;translate:0 6px}to{opacity:1;translate:0 0}}
}

/* Empty lists: centred and roomy, so "nothing yet" reads as a state, not as
   a line that failed to load. */
.empty{text-align:center;padding:1.6rem 1rem}

/* Dialog-sized surfaces get the larger corner dialogs have everywhere now. */
.profile-menu,.guide-card{border-radius:28px}
.profile-menu{padding:.5rem}
.pm-item{border-radius:100px;padding-inline:1rem}
.pm-who{padding-inline:1rem}

/* Call and voicemail rows answer the pointer like the lists above do. */
.hist .card:not(.empty){padding-block:.4rem}
:is(.hist-row,.vm){transition:background-color .15s;border-radius:var(--r-sm);padding-inline:.6rem}
@media (hover:hover){:is(.hist-row,.vm):hover{background:color-mix(in oklab,var(--surface-2) 60%,transparent)}}

/* ── narrow screens ──────────────────────────────────────────────────────── */
@media (max-width:640px){
  .msg{max-width:100%}
  .detail-grid{gap:1rem}
  .panel{margin-bottom:1rem}
  .login-card .btn{min-height:3rem}
  .hours{grid-template-columns:2.8rem 1fr 1fr}
  .agent{padding:1rem}
  .agent .top .acts{margin-inline-start:0;width:100%}
  .agent .top .acts .btn{flex:1 1 auto;justify-content:center}
}

/* ── statistics (/portal/admin/ → Statistics) ─────────────────────────────── */
/* Two bar charts and some ranked lists. Hand-drawn in CSS rather than pulled
   from a chart library: this would be the project's first such dependency, on
   a login-gated page, for shapes this simple. The palette is monochrome by
   design, so a bar is --accent and never a colour that has to mean something. */

/* Two panels side by side on a wide screen, stacked on a narrow one. */
.st-two{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(22rem,100%),1fr));gap:.9rem;align-items:start}
.st-two>.card.panel{margin-bottom:0}
.ml-app .st-two{margin-bottom:.9rem}

/* Column chart. Height is on the <i>, as a percentage of the tallest column,
   so nothing here needs to know the scale. */
.st-chart{display:flex;align-items:flex-end;gap:2px;height:8.5rem;margin-top:.4rem}
.st-col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;justify-content:flex-end;
  align-items:center;gap:.25rem;height:100%}
.st-col i{display:block;width:100%;height:var(--h);min-height:2px;
  background:var(--accent);border-radius:3px 3px 0 0;transition:height .45s var(--ease)}
.st-col:hover i{background:var(--violet)}
.st-col b{font-size:.62rem;font-weight:500;color:var(--text-muted);white-space:nowrap;
  overflow:hidden;text-overflow:clip;max-width:100%}
@media (prefers-reduced-motion:reduce){.st-col i{transition:none}}

/* Ranked list: label, proportion bar, count. The bar is a share of the biggest
   row rather than of the total, so a long tail still reads. */
.st-rank{list-style:none;display:grid;gap:.32rem;margin:.4rem 0 0;padding:0}
.st-rank+.st-rank{margin-top:.7rem;padding-top:.7rem;border-top:1px solid var(--line)}
.st-rank li{display:grid;grid-template-columns:minmax(4rem,11rem) 1fr auto;align-items:center;
  gap:.6rem;font-size:.82rem}
.st-rank .st-k{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-soft)}
.st-rank .st-bar{height:.45rem;border-radius:999px;position:relative;
  background:color-mix(in oklab,var(--accent) 12%,transparent)}
.st-rank .st-bar::after{content:"";position:absolute;inset-block:0;inset-inline-start:0;
  width:var(--w);border-radius:999px;background:var(--accent)}
.st-rank b{font-variant-numeric:tabular-nums;font-weight:650}

/* Web Vitals verdicts are the one place a colour carries meaning here. */
.st-good{color:var(--mint)}
.st-bad{color:var(--amber)}

@media (max-width:560px){
  .st-rank li{grid-template-columns:minmax(4rem,1fr) 2.5rem auto}
  .st-chart{height:6.5rem}
}

/* Every action, by page: one label and three tallies. A grid rather than a
   table so it shares .st-k's truncation with the ranked lists beside it. */
.st-acts{display:grid;gap:.25rem;margin-top:.4rem}
.st-act{display:grid;grid-template-columns:1fr 3.2rem 3.2rem 3.2rem;align-items:center;
  gap:.5rem;font-size:.82rem}
.st-act b{text-align:end;font-variant-numeric:tabular-nums;font-weight:650}
.st-act-h b{font-weight:500;font-size:.72rem;color:var(--text-muted)}
.st-act-h{padding-bottom:.2rem;border-bottom:1px solid var(--line)}

/* ── enquiries (/portal/admin/ → Enquiries) ───────────────────────────────── */
/* A worked-through list rather than a read-through one, so each row carries
   its state and the actions that move it along. */
.ml-app .q-row{margin-bottom:.7rem}
.q-top{display:flex;gap:.8rem;align-items:flex-start;justify-content:space-between}
.q-top .row-main{min-width:0;font-size:.9rem;line-height:1.45}
.q-svc{font-size:.8rem;margin:.5rem 0 0}
/* The message is the whole point of the row, so it keeps the sender's own line
   breaks instead of being collapsed into a paragraph. */
.q-body{white-space:pre-wrap;overflow-wrap:anywhere;margin:.55rem 0 0;font-size:.88rem;
  line-height:1.6;padding:.7rem .85rem;border-radius:var(--r-sm);
  background:var(--surface-2);border:1px solid var(--line)}
.q-st-new{background:color-mix(in oklab,var(--amber) 16%,transparent);
  border-color:color-mix(in oklab,var(--amber) 40%,transparent)}
.q-st-replied{background:color-mix(in oklab,var(--mint) 16%,transparent);
  border-color:color-mix(in oklab,var(--mint) 40%,transparent)}
.q-st-archived{opacity:.65}
.q-msg:empty{display:none}
@media (max-width:560px){
  .q-top{flex-direction:column;gap:.4rem}
}

/* Invitation state on a client row. "Not invited" is the one that matters —
   an admin-made account nobody has been sent a link for cannot be signed
   into, and nothing else on the row would tell you. */
.list--nav li .status-pill{flex:0 0 auto;margin-inline-start:auto;align-self:center}
.inv-none{background:color-mix(in oklab,var(--amber) 16%,transparent);
  border-color:color-mix(in oklab,var(--amber) 40%,transparent)}
.inv-used{background:color-mix(in oklab,var(--mint) 16%,transparent);
  border-color:color-mix(in oklab,var(--mint) 40%,transparent)}
.inv-old{background:color-mix(in oklab,var(--red) 14%,transparent);
  border-color:color-mix(in oklab,var(--red) 38%,transparent)}
@media (max-width:560px){ .list--nav li .status-pill{font-size:.68rem} }
