/* Layer Digital customer portal — calm, branded, mobile-first.
   A shop owner opens this on a phone; it must be simple and reassuring.

   The token names and values here mirror assets/app.css deliberately. They
   used to be a separate set that shared nine names with the admin sheet and
   gave nine different values, so the two products drifted apart every time
   either was touched. Same three tiers, same semantic layer, same dark
   remap — this is the surface a customer judges us on, so it should not be
   the one that is a little bit off. */
:root{
  color-scheme: light dark;

  /* 1 · primitives */
  --blue-700:#1D4ED8; --blue-600:#3D7BFF; --blue-300:#6E9BFF;
  --blue-50:#EAF1FF;
  --green-800:#07784A; --green-600:#12B76A; --green-400:#35D08A;
  --green-50:#E7F8F0;
  --red-700:#C62F2F; --red-600:#E5484D; --red-400:#F0736F;
  --red-50:#FDECEC;
  --grey-900:#0E1526; --grey-600:#5B6472;
  --grey-200:#E4E8EF; --grey-100:#F2F5FA; --grey-50:#F6F8FB; --white:#FFFFFF;

  /* 2 · semantic */
  --surface-page:var(--grey-50); --surface-card:var(--white);
  --surface-sunken:var(--grey-100);
  --border:var(--grey-200); --border-strong:#CBD3E1;
  --text-primary:var(--grey-900); --text-secondary:var(--grey-600);
  --text-on-accent:var(--white);
  --accent:var(--blue-600); --accent-strong:var(--blue-700); --accent-bg:var(--blue-50);
  --success:var(--green-600); --success-strong:var(--green-800); --success-bg:var(--green-50);
  --danger:var(--red-600); --danger-strong:var(--red-700); --danger-bg:var(--red-50);
  --shadow-rgb: 14 21 38;

  /* 3 · scales */
  --radius:16px; --r-md:11px; --r-sm:9px;
  --shadow-1:0 1px 3px rgb(var(--shadow-rgb) / .04);
  --shadow-2:0 4px 14px rgb(var(--shadow-rgb) / .10);

  --ff:'Plus Jakarta Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;

  /* legacy aliases — the rules below still use these names */
  --text:var(--text-primary); --dim:var(--text-secondary);
  --line:var(--border); --bg:var(--surface-page);
  --panel:var(--surface-card); --panel2:var(--surface-sunken);
  --accent-d:var(--accent-strong); --navy:var(--text-primary);
  --green:var(--success); --green-d:var(--success-strong); --green-tint:var(--success-bg);
  --red:var(--danger); --red-tint:var(--danger-bg);
}

@media (prefers-color-scheme: dark){
  :root{
    --surface-page:#0B1220; --surface-card:#131C2E; --surface-sunken:#1B2437;
    --border:#26314A; --border-strong:#3A4762;
    --text-primary:#E8ECF4; --text-secondary:#A6B0C3;
    /* Bright fill, dark text — white on these hues fails on a dark page. */
    --text-on-accent:#0B1220;
    --accent:var(--blue-300); --accent-strong:var(--blue-300); --accent-bg:#16233D;
    --success:var(--green-400); --success-strong:var(--green-400); --success-bg:#0E2A1F;
    --danger:var(--red-400); --danger-strong:var(--red-400); --danger-bg:#2C1719;
    --shadow-rgb: 0 0 0;
    --shadow-1:0 1px 3px rgb(var(--shadow-rgb) / .35);
    --shadow-2:0 4px 14px rgb(var(--shadow-rgb) / .5);
  }
}
*{box-sizing:border-box}
/* [hidden] MUST WIN. The attribute is only a UA rule of `display:none`, so any
   class in this file that sets a display beats it: `.cp-btn{display:inline-flex}`
   has meant the header's Sign out button was visible on the signed-out screen
   for as long as both have existed, and `.cp-panel{display:flex}` would have
   put all six screens of the sub-menu on the page at once. Every hidden/unhidden
   toggle in portal.js depends on this one line. */
[hidden]{display:none !important}
body{margin:0;background:var(--bg);color:var(--text);font-family:var(--ff);
  font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}
h1{font-size:1.5rem;font-weight:800;margin:0 0 4px;letter-spacing:-0.01em}
h2{font-size:1.05rem;font-weight:800;margin:0 0 10px}
a{color:var(--accent-d)}

/* --cp-gutter is the one side gutter for the header, the page and the foot,
   so the logo stays in line with the cards under it. 22px rather than the
   old 18: all three bars of the mark touch x=0, it has no left bearing of
   its own, and at 18 it read as flush against the edge of the screen.
   max() keeps it clear of the notch in landscape when installed. */
:root{--cp-gutter:22px}
.cp-wrap{max-width:640px;margin:0 auto;
  padding-left:max(var(--cp-gutter),env(safe-area-inset-left,0px));
  padding-right:max(var(--cp-gutter),env(safe-area-inset-right,0px))}

.cp-header{background:var(--panel);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5}
.cp-hd{display:flex;align-items:center;justify-content:space-between;height:64px}
.ld-logo{height:32px;width:auto}

/* padding-block only - a `24px 18px 40px` shorthand here would reset the side
   gutter .cp-wrap sets on this same element back to its own value. */
.cp-main{padding-top:24px;padding-bottom:40px;display:flex;flex-direction:column;gap:16px}

.cp-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  padding:22px;box-shadow:var(--shadow-1)}
.cp-center{text-align:center;color:var(--dim)}
.cp-sub{color:var(--dim);font-size:.92rem;margin:0 0 14px}
/* "Keep me signed in" on the sign-in card. Big hit area, because this is
   tapped on a phone as often as clicked. */
.cp-check{display:flex;align-items:center;gap:10px;margin:2px 0 16px;cursor:pointer;
  font-size:.9rem;color:var(--dim);user-select:none}
.cp-check input{width:18px;height:18px;margin:0;flex:0 0 auto;cursor:pointer;accent-color:var(--accent,#3D7BFF)}
.cp-check span{line-height:1.4}
.cp-check:hover span{color:var(--fg,inherit)}

/* buttons */
.cp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-family:var(--ff);font-weight:700;font-size:.95rem;border-radius:11px;
  padding:13px 18px;border:1.5px solid transparent;cursor:pointer;text-decoration:none;
  transition:transform .05s,box-shadow .12s,background .12s}
.cp-btn:active{transform:translateY(1px)}
.cp-btn.primary{background:var(--accent-strong);color:var(--text-on-accent)}
.cp-btn.primary:hover{background:var(--accent);box-shadow:var(--shadow-2)}
.cp-btn.ghost{background:transparent;border-color:var(--line);color:var(--text)}
.cp-btn.ghost:hover{border-color:var(--accent);color:var(--accent-d)}
.cp-btn.danger{background:var(--danger-strong);color:var(--text-on-accent)}
.cp-btn.danger:hover{background:var(--danger)}
.cp-btn:disabled{opacity:.6;cursor:default}
.cp-btn.primary,.cp-btn.danger{width:100%}

/* forms */
label{display:block;font-size:.82rem;font-weight:700;color:var(--dim);margin:0 0 6px}
input[type=email],input[type=text],input[type=tel],textarea{width:100%;font-family:var(--ff);font-size:1rem;color:var(--text);
  background:var(--panel2);border:1.5px solid var(--line);border-radius:11px;padding:13px 14px;margin-bottom:14px}
input[type=email]:focus,input[type=text]:focus,input[type=tel]:focus,textarea:focus{border-color:var(--accent);background:var(--surface-card)}
input[type=email]:focus:not(:focus-visible),input[type=text]:focus:not(:focus-visible),
textarea:focus:not(:focus-visible){outline:none}
textarea{min-height:110px;resize:vertical;line-height:1.5}

.cp-err{color:var(--red);font-size:.88rem;margin:2px 0 0;background:var(--red-tint);
  border-radius:9px;padding:9px 12px}
.cp-ok{color:var(--green-d);font-size:.9rem;margin:2px 0 0;background:var(--green-tint);
  border-radius:9px;padding:9px 12px}
.cp-note{background:var(--green-tint);color:var(--green-d);border-radius:11px;padding:14px 16px;font-size:.92rem;line-height:1.5}

/* site card */
.cp-site-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:16px}
.cp-status{font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;
  border-radius:999px;padding:5px 11px;white-space:nowrap}
.cp-status.on{background:var(--green-tint);color:var(--green-d)}
.cp-status.off{background:var(--panel2);color:var(--dim)}
.cp-shot{display:block;border-radius:12px;overflow:hidden;border:1px solid var(--line);
  background:var(--panel2);aspect-ratio:16/10;position:relative}
.cp-shot img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
.cp-shot-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--dim);font-size:.9rem}
.cp-actions{margin-top:16px}

/* plan */
.cp-plan-row{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  padding:10px 0;border-bottom:1px solid var(--line);font-size:.95rem}
.cp-plan-row:last-child{border-bottom:0}
.cp-plan-row span{color:var(--dim);flex:0 0 auto}
/* The value can be a business's full trading name, which is longer than the
   label beside it. Right-aligned and allowed to wrap, or it runs into the
   label — these rows now carry names as well as prices. */
.cp-plan-row b{text-align:right;min-width:0;overflow-wrap:anywhere}

/* attach control on the change-request form */
.cp-attach{margin:12px 0 4px}
.cp-attach-btn{display:inline-flex;align-items:center;gap:8px;cursor:pointer;
  font-size:.88rem;font-weight:700;color:var(--accent);padding:8px 12px;border-radius:9px;
  border:1px dashed var(--line);background:var(--panel2);transition:border-color .15s,color .15s}
.cp-attach-btn:hover{border-color:var(--accent)}
.cp-attach-btn svg{flex:none}
.cp-attach-hint{margin:7px 2px 0;font-size:.78rem;color:var(--dim);line-height:1.5}
.cp-attach-list{list-style:none;margin:10px 0 0;padding:0;display:flex;flex-direction:column;gap:6px}
.cp-attach-item{display:flex;align-items:center;gap:9px;font-size:.84rem;
  background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:7px 9px}
.cp-attach-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-attach-size{flex:none;color:var(--dim);font-size:.78rem}
.cp-attach-x{flex:none;border:0;background:none;cursor:pointer;color:var(--dim);
  font-size:1.15rem;line-height:1;padding:0 3px;border-radius:5px}
.cp-attach-x:hover{color:var(--red)}

/* requests */
.cp-requests{margin-top:16px}
.cp-req-title{font-size:.8rem;font-weight:700;color:var(--dim);margin:0 0 8px;text-transform:uppercase;letter-spacing:.05em}
.cp-req{padding:11px 0;border-top:1px solid var(--line);font-size:.9rem}
.cp-req-head{display:flex;align-items:baseline;gap:10px;margin-bottom:4px}
.cp-req-kind{font-weight:700;flex:none}
.cp-req-date{flex:1;color:var(--dim);font-size:.78rem}
.cp-req-body{color:var(--text);line-height:1.5;overflow-wrap:anywhere}
.cp-req-stat{flex:none;color:var(--dim);font-size:.8rem}

/* attachments shown back in the request log */
.cp-att-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
.cp-att{display:block;border-radius:9px;border:1px solid var(--line);overflow:hidden;
  text-decoration:none;transition:border-color .15s}
.cp-att:hover{border-color:var(--accent)}
.cp-att-img{width:64px;height:64px;flex:none;background:var(--panel2)}
.cp-att-img img{width:100%;height:100%;object-fit:cover;display:block}
.cp-att-file{display:inline-flex;align-items:center;gap:7px;padding:8px 10px;
  background:var(--panel2);color:var(--text);font-size:.8rem;max-width:100%}
.cp-att-file svg{flex:none;color:var(--dim)}
.cp-att-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:150px}
.cp-att-size{flex:none;color:var(--dim);font-size:.74rem}

/* danger zone */
.cp-danger h2{color:var(--red)}
.cp-cancel-box{margin-top:14px}
.cp-cancel-actions{display:flex;gap:10px;flex-wrap:wrap}
.cp-cancel-actions .cp-btn{width:auto;flex:1;min-width:140px}

/* misc */
.cp-spinner{width:26px;height:26px;border:3px solid var(--line);border-top-color:var(--accent);
  border-radius:50%;margin:0 auto 10px;animation:cp-spin .8s linear infinite}
@keyframes cp-spin{to{transform:rotate(360deg)}}
.cp-foot{padding:26px 18px 40px;text-align:center;color:var(--dim);font-size:.85rem}
.cp-foot a{color:var(--dim)}

@media(min-width:480px){
  .cp-btn.primary,.cp-btn.danger{width:auto}
  .cp-btn.primary#cp-send,.cp-btn.primary#cp-change-send{width:100%}
}


/* ===========================================================================
   Focus, motion, empty state. The portal had no :focus-visible rule at all and
   removed the outline on plain :focus, so a customer navigating by keyboard
   had no idea where they were.
   =========================================================================== */
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible,
label.cp-attach-btn:focus-within{
  outline:3px solid var(--accent); outline-offset:2px}

/* Nothing below the form on a first visit read as a broken page rather than a
   new account. */
.cp-req-empty{color:var(--text-secondary);font-size:.9rem;padding:14px 0 2px}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important}
  .cp-btn:active{transform:none}
}

/* ---------------------------------------------------------------------------
   Owner preview banner.

   Shown only when a member of staff is looking at a customer's portal. It has
   to be impossible to miss: the whole risk of a preview is someone forgetting
   whose screen they are on. Informational blue rather than red — nothing is
   wrong, it is just not yours.
   --------------------------------------------------------------------------- */
.cp-preview{
  background:var(--accent-bg);
  border:1px solid var(--accent);
  border-left:4px solid var(--accent-strong);
  border-radius:var(--r-md);
  padding:12px 14px;
  margin-bottom:16px;
  font-size:.86rem;
  line-height:1.45;
  color:var(--text-primary);
}
.cp-preview b{display:block;margin-bottom:2px;color:var(--accent-strong)}
.cp-preview span{color:var(--text-secondary)}

/* Everything that writes is dead on a preview session — the server refuses it
   regardless, so this stops an owner clicking a button that cannot work and
   wondering why nothing happened. */
body.is-preview button[type="submit"],
body.is-preview .cp-btn.danger{
  opacity:.5;
  pointer-events:none;
}

/* ---------------------------------------------------------------------------
   How your site is doing, and who has been in touch.

   Four numbers and a list. No charts: for a walk-in trade "47 calls, up from
   31" is the whole story, and anything more is decoration a barber did not ask
   for. Big figures because this is read on a phone, often one-handed.
   --------------------------------------------------------------------------- */
.cp-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:10px}
.cp-stat{background:var(--surface-sunken);border:1px solid var(--border);border-radius:var(--r-md);
  padding:12px 10px;text-align:center;min-width:0}
.cp-stat b{display:block;font-size:1.6rem;font-weight:800;line-height:1.1;color:var(--text-primary)}
.cp-stat-l{display:block;font-size:.76rem;font-weight:700;color:var(--text-secondary);margin-top:2px}
/* The comparison is the point — it is what tells them it is working. */
.cp-stat-t{display:block;font-size:.68rem;color:var(--text-secondary);margin-top:3px;
  overflow-wrap:anywhere}

.cp-enquiries{display:flex;flex-direction:column;gap:10px}
.cp-enq{background:var(--surface-sunken);border:1px solid var(--border);
  border-left:3px solid var(--accent);border-radius:var(--r-md);padding:11px 13px}
/* Read ones stay, but stop competing with the ones still needing a call back. */
.cp-enq.read{border-left-color:var(--border-strong);opacity:.62}
.cp-enq-hd{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:5px}
.cp-enq-hd b{font-size:.92rem}
.cp-enq-hd a{color:var(--accent);font-weight:700;text-decoration:none;font-size:.86rem}
.cp-enq-hd a:hover{text-decoration:underline}
.cp-enq-hd span{margin-left:auto;font-size:.76rem;color:var(--text-secondary)}
.cp-enq p{font-size:.88rem;line-height:1.5;margin:0;overflow-wrap:anywhere}
/* Which door the lead came in through. Only on the ones that are not the
   contact form, so the ordinary case carries no chip at all. */
.cp-enq-src{margin-left:0;background:var(--accent-bg);color:var(--accent-strong);
  border-radius:999px;padding:1px 8px;font-size:.72rem;font-weight:700;
  letter-spacing:.01em;white-space:nowrap}
/* What they asked before they left their number. Folded away: the message is
   the point, this is the context. */
.cp-enq-tr{margin-top:8px}
.cp-enq-tr summary{font-size:.78rem;font-weight:700;color:var(--text-secondary);
  cursor:pointer;padding-block:2px}
.cp-enq-tr[open] summary{margin-bottom:6px}
.cp-enq-tr p{font-size:.83rem;line-height:1.45;margin:0 0 4px;
  color:var(--text-secondary)}
.cp-enq-tr p b{color:var(--text);font-weight:700}
.cp-empty{font-size:.86rem;color:var(--text-secondary);margin:0}

/* One-handed on a phone: two across rather than four squeezed. */
@media (max-width:520px){
  .cp-stats{grid-template-columns:repeat(2,1fr)}
  .cp-enq-hd span{margin-left:0;width:100%}
}

/* ===================================================================
   Installed to a phone's home screen
   =================================================================== */

/* Offline. Grey, not red: their website has not gone down — this app just
   cannot reach us. An alarming colour would say the opposite. */
.cp-offline{
  background:var(--panel2);
  border:1px solid var(--line);
  border-left:4px solid var(--border-strong);
  border-radius:var(--r-md);
  padding:12px 14px;
  margin-bottom:16px;
  font-size:.86rem;
  line-height:1.45;
}
.cp-offline b{display:block;margin-bottom:2px;color:var(--text)}
.cp-offline span{color:var(--text-secondary)}

/* "I already have a code" — a plain text button, because it is a way out for
   the few and should not compete with "Email me a sign-in link". */
.cp-linkbtn{
  display:block;
  margin:14px auto 0;
  background:none;border:none;padding:4px;
  font-family:var(--ff);font-size:.86rem;font-weight:700;
  color:var(--text-secondary);text-decoration:underline;cursor:pointer;
}
.cp-linkbtn:hover{color:var(--accent-d)}

.cp-code-block{
  margin-top:16px;padding-top:16px;
  border-top:1px solid var(--line);
}
/* Six digits, spaced and centred — it is read off an email and typed, and
   character spacing is what makes a mistyped digit visible. */
#cp-code{
  text-align:center;
  font-size:1.5rem;
  font-weight:800;
  letter-spacing:.35em;
  /* The letter-spacing adds a trailing gap after the last digit, which throws
     the centring off by half a space. */
  text-indent:.35em;
}
.cp-code-block .cp-btn{width:100%}

/* Add to home screen. */
.cp-install{
  display:grid;
  grid-template-columns:auto 1fr;
  gap:14px 16px;
  align-items:start;
  border-color:var(--accent);
  background:var(--accent-bg);
}
.cp-install-icon{border-radius:12px;flex:none}
.cp-install h2{margin-top:0}
.cp-install .cp-sub{margin-bottom:0}
.cp-install-actions{
  grid-column:1 / -1;
  display:flex;gap:10px;flex-wrap:wrap;
}
/* .cp-btn.primary is width:100% by default, which would push "Not now" onto
   its own line for no reason. */
.cp-install-actions .cp-btn{width:auto;flex:1 1 auto}

.cp-install-steps{
  margin:12px 0 0;
  padding-left:20px;
  color:var(--text-secondary);
  font-size:.9rem;
  line-height:1.7;
}
.cp-install-steps b{color:var(--text)}

@media (max-width:520px){
  .cp-install{grid-template-columns:1fr}
  .cp-install-icon{width:44px;height:44px}
}

/* Running as an installed app.
   There is no browser chrome to sit under, so the header has to keep itself
   clear of the notch and the status bar, and the page clear of the home
   indicator. Without this the sign-out button sits under the clock. */
@media (display-mode:standalone),(display-mode:minimal-ui){
  .cp-header{padding-top:env(safe-area-inset-top,0px)}
  .cp-main{
    padding-bottom:calc(40px + env(safe-area-inset-bottom,0px));
  }
  /* An installed app cannot be "added to the home screen" again. The script
     already refuses to show this; the rule is here so a race on a slow phone
     cannot flash it. */
  .cp-install{display:none}
}

/* The details form.

   Almost nothing new: input[type=text] and textarea are already styled above.
   What was genuinely missing is type=tel, which that selector does not cover —
   a phone field that looked different from every other field on the page would
   read as broken, and it is the field most likely to be wrong. It is folded into
   the shared input rule above rather than given one of its own. */
#cp-profile-form label{margin-top:14px}
#cp-profile-form label:first-of-type{margin-top:0}
.cp-gaps{margin:6px 0 14px 18px;padding:0}
.cp-gaps li{margin-bottom:6px;font-size:.88rem;color:var(--dim)}

/* ── Where your website is up to ──────────────────────────────────────────
   A ladder rather than a percentage. Five named rungs a business owner can
   read at a glance, all of them derived from columns we already keep (see
   stageOf in routes/portal.ts) so the picture cannot disagree with the CRM.
   Colour is never the only signal: the current rung is also the only one in
   bold, and finished ones carry a tick. */
.cp-ladder{list-style:none;margin:0 0 10px;padding:0}
.cp-rung{display:flex;align-items:flex-start;gap:10px;padding:7px 0;font-size:.94rem}
.cp-rung-dot{flex:0 0 auto;width:16px;height:16px;margin-top:3px;border-radius:50%;
  border:2px solid var(--line);background:var(--surface-card)}
.cp-rung.done .cp-rung-dot{background:var(--green);border-color:var(--green);
  position:relative}
.cp-rung.done .cp-rung-dot::after{content:"";position:absolute;left:4px;top:1px;
  width:4px;height:8px;border:solid var(--surface-card);border-width:0 2px 2px 0;
  transform:rotate(45deg)}
.cp-rung.now .cp-rung-dot{background:var(--accent);border-color:var(--accent);
  box-shadow:0 0 0 4px var(--accent-bg)}
.cp-rung.done .cp-rung-label{color:var(--dim)}
.cp-rung.now .cp-rung-label{font-weight:700;color:var(--text)}
.cp-rung.todo .cp-rung-label{color:var(--dim);opacity:.75}
.cp-stage .cp-sub{margin:0}

/* The brief. Tinted, because until it is in nothing else can happen and it
   should be the thing the eye lands on. */
.cp-brief{border-color:var(--accent);background:var(--accent-bg)}
.cp-brief h2{margin-bottom:4px}

/* Several businesses on one sign-in. Hidden entirely at one. */
.cp-switch{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding-top:14px;
  padding-bottom:14px}
.cp-switch label{margin:0;white-space:nowrap}
.cp-switch select{flex:1 1 12rem;min-height:44px;padding:0 10px;font:inherit;
  color:var(--text);background:var(--surface-card);
  border:1px solid var(--border-strong);border-radius:11px}

/* ------------------------------------------------------------ reviews card */
/* Sold as a separate service at ADDON_REVIEWS_PENCE. Three states, three
   layouts: the offer, the "we owe you a job" line, and the working form. */
.cp-price{margin:14px 0 6px;font-size:1.05rem}
.cp-price b{font-size:1.3rem}
.cp-price-sub{display:block;font-size:.82rem;color:var(--text-secondary);font-weight:400}

.cp-review-form{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.cp-review-form input{
  flex:1 1 150px;min-width:0;width:auto;margin-bottom:0;padding:11px 12px;font:inherit;font-size:.9rem;
  border:1px solid var(--border);border-radius:10px;
  background:var(--surface-sunken);color:var(--text)}
.cp-review-form input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.cp-review-form .cp-btn{flex:0 0 auto}

.cp-review-msg{margin:10px 0 0;font-size:.86rem;font-weight:700}
.cp-review-msg:empty{display:none}
.cp-review-msg.ok{color:var(--success-strong)}
.cp-review-msg.bad{color:var(--danger-strong)}

.cp-reviews-log{display:flex;flex-direction:column;gap:6px;margin-top:16px}
.cp-review-row{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  padding:8px 10px;border-radius:8px;background:var(--surface-sunken);
  font-size:.85rem}
.cp-review-row b{font-size:.88rem}
.cp-review-row span:last-child{margin-left:auto;color:var(--text-secondary);font-size:.78rem}
/* "opened" is the only good news here, and it still does not mean "reviewed". */
.cp-review-state{
  border-radius:999px;padding:1px 8px;font-size:.72rem;font-weight:700;
  background:var(--accent-bg);color:var(--accent-strong)}
.cp-review-state.is-not-sent{background:var(--danger-bg);color:var(--danger-strong)}

/* ---- what people search for --------------------------------------------
   Three tiles rather than four, so the grid is overridden rather than a
   fourth number being invented to fill it. */
.cp-stats-3{grid-template-columns:repeat(3,1fr)}
.cp-h3{font-size:.95rem;font-weight:700;margin:18px 0 10px;color:var(--text-primary)}

/* One suggestion. The reason and the action are visually separated because
   they are different things: the reason is evidence about what happened, the
   action is what to do about it, and blurring them is how a finding turns
   into an opinion. */
.cp-opp{background:var(--surface-sunken);border:1px solid var(--border);
  border-left:3px solid var(--accent);border-radius:var(--r-md);
  padding:12px 14px;margin-bottom:10px}
.cp-opp b{display:block;font-size:1rem;margin:4px 0 6px;color:var(--text-primary)}
.cp-opp p{margin:0;font-size:.85rem;color:var(--text-secondary);line-height:1.55}
.cp-opp-kind{font-size:.66rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--accent-strong)}
.cp-opp-action{margin-top:8px!important;font-weight:600;color:var(--text-primary)!important}

/* Collapsed by default. The full list is the evidence and has to be reachable,
   but it is a hundred rows and must not be the first thing on the card. */
.cp-terms{margin-top:16px}
.cp-terms summary{cursor:pointer;font-size:.84rem;font-weight:700;color:var(--accent-strong)}
.cp-terms-table{width:100%;border-collapse:collapse;margin-top:10px;font-size:.82rem}
.cp-terms-table th,.cp-terms-table td{text-align:left;padding:6px 8px;
  border-bottom:1px solid var(--border);vertical-align:top}
.cp-terms-table th{color:var(--text-secondary);font-weight:700}
.cp-terms-table td:nth-child(n+2),.cp-terms-table th:nth-child(n+2){
  text-align:right;white-space:nowrap}

/* ------------------------------------------------------------------ diary
   The owner's appointments, and the screens they edit the diary from.
   Tokens only — nothing here names a colour. */

.cp-book-totals{margin:0 0 14px;color:var(--text)}

/* Standing appointments that could not be placed.
   Deliberately the loudest thing on the card: the customer believes they are
   booked and they are not, and only a phone call fixes it. */
.cp-book-misses{border:1px solid var(--danger);border-radius:var(--r-md);
  padding-block:12px;padding-inline:14px;margin-bottom:16px;background:var(--danger-bg)}
.cp-book-misses .cp-book-h{margin-top:0;color:var(--danger)}
.cp-book-miss{padding-block:8px;border-top:1px solid var(--line)}
.cp-book-miss:first-of-type{border-top:0}
.cp-book-miss .cp-btn{width:auto;padding-block:4px;padding-inline:10px;font-size:.85rem;margin-top:6px}

/* Move a fortnight at a time. Wraps on a phone rather than squashing. */
.cp-book-nav{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:16px}
.cp-book-nav .cp-btn{width:auto;padding-block:5px;padding-inline:12px;font-size:.85rem}
.cp-book-nav .cp-sub{margin:0}

/* A day with nothing in it. Quiet, but present — a gap you cannot see is a gap
   you cannot fill. */
.cp-book-free{margin:0 0 4px;color:var(--dim);font-size:.9rem}
.cp-book-day{font-size:.95rem;margin:18px 0 8px;color:var(--dim);
  text-transform:none;font-weight:700}
.cp-book-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}

.cp-book-row{display:flex;gap:12px;align-items:flex-start;
  padding-block:12px;padding-inline:14px;
  background:var(--surface-sunken);border:1px solid var(--line);
  border-radius:var(--r-md)}
/* The time is the thing the eye runs down. Fixed width and tabular figures so
   the column lines up whatever the hour. */
.cp-book-time{flex:0 0 3.4rem;font-weight:700;font-variant-numeric:tabular-nums}
.cp-book-body{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.cp-book-price{font-variant-numeric:tabular-nums;color:var(--dim)}
/* Struck through, not removed. An owner looking at Saturday needs to see the
   10:15 that cancelled last night — that is a gap they could still fill. */
.cp-book-row.cancelled{opacity:.6}
.cp-book-row.cancelled .cp-book-time{text-decoration:line-through}
/* An appointment nobody was ever told about. The failure that puts a person
   outside a closed shop, so it does not whisper. */
.cp-book-warn{color:var(--danger);font-weight:600;font-size:.86rem}
/* Compact, and deliberately quiet. Cancelling somebody's appointment is the
   destructive action on this card and it should not be the biggest thing on
   the row — the customer's name and number are what the owner came for. */
.cp-book-actions{flex:0 0 auto;display:flex;gap:6px;align-self:center}
.cp-book-actions .cp-btn{width:auto;padding-block:5px;padding-inline:12px;font-size:.85rem}

/* ------------------------------------------------------------------ setup */

#cp-booking-setup-card summary{cursor:pointer;list-style:none}
#cp-booking-setup-card summary::-webkit-details-marker{display:none}
#cp-booking-setup-card summary h2{display:inline}
#cp-booking-setup-card summary::after{content:"  ▾";color:var(--dim)}
#cp-booking-setup-card[open] summary::after{content:"  ▴"}

.cp-book-switch{margin-bottom:18px}
.cp-book-h{font-size:.95rem;margin:22px 0 8px}
.cp-book-items{display:grid;gap:6px;margin-bottom:10px}
.cp-book-item{padding-block:10px;padding-inline:12px;
  background:var(--surface-sunken);border:1px solid var(--line);
  border-radius:var(--r-sm);overflow-wrap:anywhere}
/* Retired, not deleted: a past booking still names them. */
.cp-book-item.retired{opacity:.55}
.cp-book-item .cp-btn{width:auto;padding-block:4px;padding-inline:10px;font-size:.85rem}

/* Set up from a Fresha page.
   Two states, because the empty diary is the one that loses a shop: loud and
   boxed when they have nothing, a quiet line once they do. */
.cp-book-import{margin-bottom:18px}
.cp-book-import.bare{padding-block:16px;padding-inline:16px;
  background:var(--accent-bg);border-radius:var(--r-md)}
.cp-book-import.bare .cp-book-h{margin-top:0}
/* The field and its button sit side by side, and stack rather than squash. */
.cp-import-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.cp-import-row input{flex:1 1 15rem;min-width:0}
.cp-import-row .cp-btn{width:auto;flex:0 0 auto}
.cp-import-found{margin-top:14px;padding-block:14px;padding-inline:14px;
  background:var(--surface-sunken);border:1px solid var(--line);border-radius:var(--r-md)}
.cp-import-found .cp-btn{width:auto;margin-top:6px}

.cp-book-form,.cp-book-settings{display:grid;gap:12px;margin-block:14px;
  padding-block:14px;padding-inline:14px;
  background:var(--accent-bg);border-radius:var(--r-md)}
.cp-book-form .cp-btn,.cp-book-settings .cp-btn{width:auto;justify-self:start}
.cp-field-num{display:grid;grid-template-columns:1fr auto auto;gap:8px;align-items:center}
.cp-field-num input{width:6rem}
.cp-check{display:flex;gap:8px;align-items:center;font-size:.92rem}

.cp-hours-rows{display:grid;gap:8px}
.cp-hours-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.cp-hours-row .cp-btn{width:auto;padding-block:4px;padding-inline:10px;font-size:.85rem}

.cp-book-msg{margin:0;font-size:.9rem;color:var(--success-strong)}
.cp-book-msg.bad{color:var(--danger)}

@media (max-width:560px){
  /* The cancel button drops under the row rather than squeezing the name. */
  .cp-book-row{flex-wrap:wrap}
  .cp-book-actions{margin-inline-start:3.4rem;align-self:flex-start}
  .cp-field-num{grid-template-columns:1fr;align-items:start}
  .cp-field-num input{width:100%}
}

@media (max-width:560px){
  .cp-stats-3{grid-template-columns:1fr 1fr}
  /* A hundred rows of search terms is the one table here that can outgrow a
     phone. Scroll it inside its own box rather than the page. */
  .cp-terms{overflow-x:auto}
}

/* ===========================================================================
   THE SUB-MENU, and the screens under it.

   The portal was one scroll of thirteen cards. On a phone that is roughly four
   thumb-lengths before an owner reaches the diary, which is the thing they
   opened it for. Six screens now, addressed by the URL hash.

   The strip lives in the sticky header so the way out of a screen is always on
   screen. It scrolls sideways rather than wrapping to two rows: a nav that
   changes height as you move through it makes the page under it jump, and the
   jump is worse than the scroll. `scroll-snap` so a half-cut tab settles.
   =========================================================================== */
/* ---------------------------------------------------------------------------
   The menu.

   Was a horizontal strip of six tabs with a scroll fade. It did not fit: the
   sixth tab sat off-screen on a phone, and at the width the preview banner is
   shown at, "Packages" was clipped mid-word. Replaced 2026-08-29 with a
   disclosure menu, which is <details>/<summary> so that it still opens with
   the script dead.
   ------------------------------------------------------------------------ */
.cp-nav{
  position:relative;
  border-top:1px solid var(--line);
  background:var(--panel);
}
.cp-nav-wrap{position:relative}

.cp-menu{position:relative}
/* The dropdown positions against the nav, not the disclosure, now that the
   list is a sibling rather than a child. */
.cp-nav{position:relative}

/* The row you tap. Full width so the target is the whole strip rather than a
   40px icon, which on a phone is the difference between one tap and three. */
.cp-menu-btn{
  display:flex;align-items:center;gap:10px;
  width:100%;
  padding:12px 2px;
  font-size:.92rem;font-weight:800;
  color:var(--text-primary);
  cursor:pointer;
  list-style:none;
  user-select:none;
}
/* Safari still paints its own triangle without this, alongside ours. */
.cp-menu-btn::-webkit-details-marker{display:none}
.cp-menu-btn::marker{content:""}
.cp-menu-btn:focus-visible{outline:2px solid var(--accent-strong);outline-offset:2px;border-radius:var(--r-sm)}

/* Three bars, drawn rather than shipped as an asset: the CSP is 'self' and one
   icon is not worth a file. The middle bar is the element, the other two are
   its pseudo-elements. */
.cp-burger{
  position:relative;
  flex:0 0 auto;
  display:block;width:18px;height:2px;border-radius:2px;
  background:var(--text-primary);
  transition:background .12s;
}
.cp-burger::before,.cp-burger::after{
  content:"";position:absolute;left:0;
  width:18px;height:2px;border-radius:2px;
  background:var(--text-primary);
  transition:transform .14s;
}
.cp-burger::before{top:-6px}
.cp-burger::after{top:6px}

/* Open: the bars become a cross. The middle one goes transparent rather than
   scaling to nothing, so the two that rotate keep their centre. */
.cp-menu[open] .cp-burger{background:transparent}
.cp-menu[open] .cp-burger::before{transform:translateY(6px) rotate(45deg)}
.cp-menu[open] .cp-burger::after{transform:translateY(-6px) rotate(-45deg)}

.cp-menu-label{flex:1 1 auto;text-align:left}

/* The panel. Absolute, so opening the menu does not push the whole page down —
   a nav that reflows the content under it makes the screen jump on every tap. */
.cp-menu-list{
  position:absolute;z-index:40;
  top:100%;left:0;right:0;
  display:flex;flex-direction:column;
  padding:6px;
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:var(--r-sm);
  box-shadow:0 12px 28px rgba(0,0,0,.16);
}
/* Shut unless the disclosure beside it is open. The sibling combinator is what
   lets the list live outside the <details> — see the comment in index.html for
   why it has to. */
.cp-menu:not([open]) + .cp-menu-list{display:none}
@media (min-width:640px){
  .cp-menu-list{right:auto;min-width:260px}
}

.cp-tab{
  display:flex;align-items:center;gap:8px;
  padding:11px 12px;
  border-radius:var(--r-sm);
  font-size:.92rem;font-weight:700;
  color:var(--text-secondary);
  text-decoration:none;
  transition:color .12s,background .12s;
}
.cp-tab:hover{color:var(--text-primary);background:var(--panel2)}
.cp-tab.is-on{color:var(--accent-strong);background:var(--panel2)}

/* Unread leads, on the Overview tab. The whole reason a sub-menu is safe: an
   owner three screens away must still be able to see that somebody is waiting
   for a call back. */
.cp-tab-badge{
  flex:0 0 auto;
  display:inline-flex;align-items:center;justify-content:center;
  min-width:19px;height:19px;padding:0 5px;
  border-radius:999px;
  background:var(--accent-strong);color:var(--text-on-accent);
  font-size:.7rem;font-weight:800;line-height:1;
}

/* A screen. The gap that .cp-main used to give the card list now lives here,
   because .cp-main holds six children of which five are hidden and a flex gap
   would space the hidden ones too. */
.cp-panel{display:flex;flex-direction:column;gap:16px}

/* The analytics tiles vary in number: three for a business with no diary,
   four with one. A fixed four-column grid leaves a visible hole in the first
   case, which reads as a tile that failed to load. */
.cp-stats-auto{grid-template-columns:repeat(auto-fit,minmax(128px,1fr))}

/* The card header, where a card carries a control as well as a title. */
.cp-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}
.cp-card-head h2{margin:0}
.cp-select{
  flex:0 0 auto;
  font-family:var(--ff);font-size:.85rem;font-weight:700;
  color:var(--text-primary);background:var(--panel2);
  border:1.5px solid var(--line);border-radius:var(--r-sm);
  padding:7px 9px;
}

/* ---------------------------------------------------------------------------
   The chart.

   Bars, not a library. The whole portal is one script and one stylesheet from
   our own origin, which is what lets the CSP stay at 'self', and thirty divs
   with a height are not worth 40KB of dependency. The numbers are in the
   caption and in each bar's title, so this is decoration over a fact rather
   than the only place the fact exists.
   --------------------------------------------------------------------------- */
.cp-chart{
  display:flex;align-items:flex-end;gap:2px;
  height:110px;
  padding:8px 10px;
  background:var(--panel2);
  border:1px solid var(--line);
  border-radius:var(--r-md);
}
.cp-bar{
  flex:1 1 0;min-width:2px;
  background:var(--accent);
  border-radius:2px 2px 0 0;
  /* A day with no traffic is still a day. A hairline at the axis says "we were
     counting and it was nothing", which is different from a gap. */
  min-height:2px;
}
.cp-bar.zero{background:var(--border-strong);opacity:.5}
.cp-chart-axis{
  display:flex;justify-content:space-between;gap:8px;
  margin:6px 2px 0;
  font-size:.74rem;color:var(--text-secondary);
}

/* Money booked. One figure, and the sentence that keeps it honest — "booked,
   not taken" is the difference between a true number and a promise. */
.cp-money{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  margin:14px 0 4px;padding:13px 15px;
  background:var(--success-bg);
  border:1px solid var(--success);
  border-radius:var(--r-md);
}
.cp-money b{font-size:1.5rem;font-weight:800;color:var(--success-strong);line-height:1.1}
.cp-money span{flex:1 1 200px;font-size:.83rem;line-height:1.45;color:var(--text-secondary)}

/* Which door they came through. A label, a proportional bar, a number. */
.cp-doors{display:flex;flex-direction:column;gap:8px}
/* Four columns, not three. The row gained a "was 31" and a three-column
   grid simply wrapped it onto its own line under the bar. */
.cp-door{display:grid;grid-template-columns:minmax(0,9.5rem) 1fr minmax(2.2rem,auto) minmax(3.4rem,auto);
  align-items:center;gap:10px;font-size:.86rem}
.cp-door-l{color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-door-track{height:8px;background:var(--panel2);border-radius:999px;overflow:hidden}
.cp-door-fill{display:block;height:100%;background:var(--accent);border-radius:999px}
.cp-door b{font-variant-numeric:tabular-nums}

/* The funnel: visit, contact, booking. Bars behind the words rather than a
   cone — a cone is a shape a marketer likes and a barber has to decode. */
.cp-funnel{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.cp-funnel-step{
  position:relative;
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:11px 13px;border-radius:var(--r-md);
  background:var(--panel2);border:1px solid var(--line);
  font-size:.9rem;overflow:hidden;
}
.cp-funnel-bar{
  position:absolute;inset:0 auto 0 0;
  background:var(--accent-bg);
  border-right:2px solid var(--accent);
}
.cp-funnel-l,.cp-funnel-step b{position:relative}
.cp-funnel-step b{font-variant-numeric:tabular-nums}

/* The monthly total, under the itemised rows. */
.cp-plan-total{border-top:2px solid var(--border-strong);border-bottom:0;margin-top:4px;padding-top:12px}
.cp-plan-total b{font-size:1.15rem}

@media (max-width:520px){
  .cp-door{grid-template-columns:1fr auto;}
  /* The bar drops to its own row rather than squeezing the label to nothing. */
  .cp-door-track{grid-column:1 / -1;order:3}
  .cp-card-head{flex-wrap:wrap}
}

/* ===========================================================================
   The performance card, second pass.

   Enquiries lead, page views do not. A shop owner does not care how many
   people looked; they care how many rang, and the tile sizes say so before a
   word is read.
   =========================================================================== */

/* The hero tile. Spans two columns where there is room, so "Enquiries" is the
   thing the eye lands on rather than whichever number happens to be biggest. */
.cp-stat.hero{
  grid-column:span 2;
  background:var(--accent-bg);
  border-color:var(--accent);
}
.cp-stat.hero b{font-size:2.1rem;color:var(--accent-strong)}

/* The change on the previous period. Colour carries meaning here, so it is
   never the only carrier — every chip also says up, down or the same in
   words, for a colour-blind reader and for anyone reading a printout. */
.cp-delta{
  display:block;margin-top:4px;
  font-size:.7rem;font-weight:700;line-height:1.35;
  overflow-wrap:anywhere;
}
.cp-delta.up{color:var(--success-strong)}
.cp-delta.down{color:var(--danger-strong)}
.cp-delta.flat{color:var(--text-secondary);font-weight:400}

/* Chart heading and its series toggles on one line. */
.cp-chart-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
.cp-chart-head .cp-h3{margin-bottom:10px}
.cp-toggle{display:inline-flex;gap:2px;background:var(--panel2);border:1px solid var(--line);
  border-radius:999px;padding:3px}
.cp-toggle-b{
  border:0;background:none;cursor:pointer;
  font-family:var(--ff);font-size:.76rem;font-weight:700;
  color:var(--text-secondary);
  padding:5px 11px;border-radius:999px;
  white-space:nowrap;
}
.cp-toggle-b:hover{color:var(--text-primary)}
.cp-toggle-b.is-on{background:var(--surface-card);color:var(--accent-strong);box-shadow:var(--shadow-1)}

/* The conversion rate sentence under the conversions table. */
.cp-rate{margin:12px 0 0;font-size:.88rem;line-height:1.5;color:var(--text-secondary)}
.cp-rate b{color:var(--text-primary);font-size:1rem}

/* "was 31" beside each conversion row. Quiet: the current number is the
   figure, this is only the reason to feel good or ring us. */
.cp-door-d{font-size:.72rem;color:var(--text-secondary);text-align:right;min-width:3.6rem}

/* ---------------------------------------------------------------------------
   "Your website this month".

   The one part of this screen that is prose, and the part that turns a
   dashboard into advice. Tinted rather than plain so it does not read as
   another card of numbers, and never red: it can carry a "worth a look at your
   contact section", which is a suggestion and not an alarm.
   --------------------------------------------------------------------------- */
.cp-insight-card{border-left:4px solid var(--accent)}
#cp-insight p{margin:0 0 10px;font-size:.95rem;line-height:1.6}
#cp-insight p:last-child{margin-bottom:0}
.cp-insight-do{
  background:var(--panel2);
  border-radius:var(--r-md);
  padding:12px 14px;
  font-size:.9rem !important;
}
.cp-insight-do b{display:block;margin-bottom:3px;color:var(--accent-strong)}

@media (max-width:520px){
  /* Two columns on a phone, so the hero spanning two is the full width and
     everything else pairs up under it. */
  .cp-stat.hero{grid-column:1 / -1}
  .cp-door-d{display:none}
  .cp-chart-head{align-items:flex-start}
  .cp-toggle{width:100%;justify-content:space-between}
}


/* ===========================================================================
   THE SIDEBAR, and the same markup as a phone menu.

   Thirteen screens in two groups. One <details> element, rendered two
   completely different ways: a disclosure menu under the header on a phone,
   and a fixed rail down the left on anything wide enough to spare 232px.

   ONE MARKUP, TWO LAYOUTS, and that is the whole design. Two navigations kept
   in sync by script are two navigations that disagree the first time somebody
   edits one, and this file has already paid for a second copy of a fact once
   today (the plan price, in portal.js). So the wide layout forces the details
   open and takes the summary out of the flow, rather than duplicating links.
   =========================================================================== */

/* Group headings inside the menu. Small, quiet, and not links. */
.cp-nav-group{
  margin:14px 0 4px;padding:0 12px;
  font-size:.68rem;font-weight:800;letter-spacing:.09em;text-transform:uppercase;
  color:var(--text-secondary);
}
.cp-nav-group:first-child{margin-top:2px}

/* ---------------------------------------------------------------------------
   Wide: a fixed rail. Fixed rather than sticky because the content column
   scrolls a long way on the Leads screen and a rail that scrolls away takes
   the way out of the screen with it.
   --------------------------------------------------------------------------- */
@media (min-width:900px){
  .cp-nav{
    position:fixed;
    inset:0 auto 0 0;
    width:232px;
    border-top:0;
    border-right:1px solid var(--line);
    background:var(--panel);
    overflow-y:auto;
    z-index:20;
    padding:76px 10px 20px;   /* clears the logo, which stays in the header */
  }
  /* No fade: nothing is clipped in a vertical list. */
  .cp-nav::after{content:none}

  /* The disclosure is a phone control. On a rail it is always open and the
     button is gone -- but `open` is toggled by the browser, not by us, so the
     rules below must not depend on it. */
  .cp-menu{display:none}
  /* Always open on a rail, whatever the <details> beside it says — and it says
     nothing here, because it is not rendered. */
  .cp-menu-list,
  .cp-menu:not([open]) + .cp-menu-list{
    position:static;display:block;
    border:0;background:none;box-shadow:none;
    min-width:0;padding:0;margin:0;
  }

  .cp-tab{
    display:flex;align-items:center;gap:8px;
    width:100%;
    padding:9px 12px;
    border-bottom:0;
    border-radius:var(--r-sm);
    font-size:.9rem;
    white-space:normal;
  }
  .cp-tab:hover{background:var(--panel2);color:var(--text-primary)}
  .cp-tab.is-on{
    background:var(--accent-bg);
    color:var(--accent-strong);
    border-bottom:0;
  }
  .cp-tab .cp-tab-badge{margin-left:auto}

  /* Everything else moves over to make room for the rail. */
  .cp-header,.cp-main,.cp-foot{margin-left:232px}
  /* The logo sits above the rail rather than beside it, so the header's inner
     wrap does not need its own offset. */
  .cp-header{border-bottom:1px solid var(--line)}
}

/* A little more room for the content once there is plenty of it. */
@media (min-width:1180px){
  .cp-main{max-width:760px}
}

/* ---------------------------------------------------------------------------
   The review count over time.

   A LINE, WHERE THE REST OF THIS FILE DRAWS BARS, and the difference is not
   decoration. The bar chart above shows a count per day against a zero
   baseline. This series is cumulative — 41, 41, 42, 43 — and drawn as bars
   from zero those are four identical columns with the growth invisible. The
   line is scaled to the range it covers, so a month that added six reviews
   looks like a month that added six reviews.

   `vector-effect:non-scaling-stroke` in the markup is what keeps the stroke
   even after `preserveAspectRatio="none"` stretches the box; without it the
   line is fat horizontally and hairline vertically.
   --------------------------------------------------------------------------- */
.cp-line{
  display:block;width:100%;height:64px;
  padding:6px 8px;box-sizing:border-box;
  background:var(--panel2);
  border:1px solid var(--line);
  border-radius:var(--r-md);
}
.cp-line polyline{stroke:var(--accent);stroke-width:2;stroke-linejoin:round;stroke-linecap:round}

/* What changed since we started counting. Colour is never the only carrier:
   the sentence says "+6 since 1 June" in words, the same rule as .cp-delta. */
.cp-change{
  margin:10px 0;font-size:.88rem;font-weight:600;
  color:var(--text-secondary);
}
.cp-change b{font-size:1rem}
.cp-change.is-up b{color:var(--success-strong)}
.cp-change.is-down b{color:var(--danger-strong)}

/* Two tiles, where the hero already spans two columns of the four-column grid.
   Without this the rating tile is stranded on its own row. */
.cp-stats-2{grid-template-columns:repeat(3,1fr)}
@media (max-width:560px){
  .cp-stats-2{grid-template-columns:1fr 1fr}
}

/* Opening hours and prices — the owner-editable half of the record.
   Added 2026-09-06 with the content card (docs/SYSTEM.md §5a).

   A grid rather than stacked labels because these are LISTS of like things:
   seven days, then however many services. Stacking a label above every one of
   fourteen inputs makes a short form read as a long one, and the label on a
   day row is one word. */
.cp-row{display:grid;grid-template-columns:4.5rem 1fr;gap:10px;align-items:center;margin-bottom:8px}
.cp-row label{margin:0;font-weight:600;font-size:.9rem;color:var(--text-secondary)}
.cp-row input{
  width:100%;padding:9px 11px;border:1px solid var(--border);border-radius:8px;
  background:var(--surface-sunken);color:var(--text);font:inherit;font-size:.95rem;
}
.cp-row input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* Name, price, remove. The price column is narrow on purpose: it holds "£12",
   and a box sized for a sentence invites one. */
.cp-service{grid-template-columns:1fr 6.5rem auto;align-items:center}
.cp-service .cp-s-del{padding:8px 12px;font-size:.85rem}
/* Source order is name, Remove, price — so that on a phone, where these stack,
   Remove lands beside the SERVICE and not beside its price. The first render
   had it under the price, where it read as "remove this number". */
.cp-service .cp-s-price{order:2}
.cp-service .cp-s-del{order:3}

/* Under 560px the three columns become two rows rather than three squeezed
   columns — a £12 field 40px wide is unusable on a phone, and this screen is
   mostly opened on one. */
@media (max-width:560px){
  .cp-row{grid-template-columns:3.5rem 1fr}
  /* A service is ONE thing with two fields, and four unbounded boxes in a
     column read as four independent ones — you cannot tell which price
     belongs to which service. So each becomes a bordered block, and Remove
     sits on the same line as the name it removes. */
  .cp-service{
    grid-template-columns:1fr auto;row-gap:6px;
    border:1px solid var(--border);border-radius:10px;padding:10px;margin-bottom:10px;
  }
  .cp-service .cp-s-name{grid-column:1;grid-row:1}
  .cp-service .cp-s-del{grid-column:2;grid-row:1}
  .cp-service .cp-s-price{grid-column:1 / -1;grid-row:2}
}

/* The sign-off ask. Prominent because editing no longer implies confirming:
   the two were one button until 2026-09-10, and an owner who saves a
   correction and leaves is now un-signed-off, which is what stands between
   their page and Google. A footnote would not carry that. */
.cp-signoff-ask {
  margin: 1.1rem 0 0.9rem;
  padding: 0.8rem 0.9rem;
  border-left: 3px solid var(--accent, #3d7bff);
  background: var(--accent-bg, rgba(61, 123, 255, 0.06));
  border-radius: 0 8px 8px 0;
  font-size: 0.88rem;
  line-height: 1.5;
}
