/* ============================================================
   parts.css
   The component vocabulary. Containers are told apart by tone
   and radius, never by an outline. Rules survive in one job
   only: page furniture between sections of a document.
   Needs tokens.css.
   ============================================================ */

/* ---------- links ---------- */
.link{font-family:var(--f-ui);font-size:var(--t-ui);text-decoration:none;
  transition:color var(--dur) var(--ease)}
.link:hover{color:var(--ink)}

/* ---------- buttons ----------
   Three of them. Mark is the one thing to do, quiet is a real
   alternative, bare is a way out. Never two marks on a screen. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  font-family:var(--f-ui);font-size:var(--t-ui);font-weight:600;line-height:1;
  padding:var(--btn-pad-y) var(--btn-pad-x);border:0;border-radius:var(--r-md);
  text-decoration:none;cursor:pointer;white-space:nowrap;
  transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease);
}
.btn--mark{background:var(--mark);color:var(--on-mark)}
.btn--mark:hover{background:var(--mark-hover)}
.btn--quiet{background:var(--well);color:var(--ink)}
.btn--quiet:hover{background:var(--well-hover)}
/* The system's only red, as a filled button. Used only on the
   button confirming an action that CANNOT BE UNDONE; if it shows up
   often it loses the warning it carries. */
.btn--miss{background:var(--miss);color:var(--on-mark)}
.btn--miss:hover{background:var(--miss-hover)}
.btn--bare{background:transparent;color:var(--muted);
  padding-left:var(--s3);padding-right:var(--s3)}
.btn--bare:hover{color:var(--ink)}
/* A drawn outline: the second action beside a filled one, where a
   well-coloured button would sit too close to it in weight. */
.btn--line{background:transparent;color:var(--ink);
  box-shadow:inset 0 0 0 var(--hairline) var(--rule-firm)}
.btn--line:hover{background:var(--well)}
.btn--sm{padding:var(--pad-compact-y) var(--btn-pad-x-sm);font-size:var(--t-small)}
.btn--wide{width:100%}
/* Continue with Google (templates/_google.html): Google's mark beside
   the words, on the outlined button. */
.btn--google{gap:var(--s3)}
.btn--google svg{flex:none}
/* "or", between two ways of doing the same thing. */
.or-rule{display:flex;align-items:center;gap:var(--s3);font-family:var(--f-ui);
  font-size:var(--t-small);color:var(--muted)}
.or-rule::before,.or-rule::after{content:"";flex:1;height:var(--hairline);background:var(--rule)}
.btn[aria-disabled="true"],.btn:disabled{background:var(--well);color:var(--muted);
  pointer-events:none}

/* ---------- fields ----------
   An input is a well, not a box. It fills on focus so the thing
   you are typing into is the lightest thing on screen. */
.field{display:flex;flex-direction:column;gap:var(--s2)}
/* A field that carries a side link next to its label ("Forgot it?").
   The link is written in the HTML AFTER the input, because focus
   order follows the DOM, and if it sat next to the label, Tab would
   go from the email field to the link instead of to the password.
   The grid puts it back on the first row visually: its place
   changes, not its order.

   There used to be a wrapping div called `.field-top` instead; that
   wrapper produced exactly the problem described above. */
.field--aside{display:grid;grid-template-columns:1fr auto;
  column-gap:var(--s3);row-gap:var(--s2);align-items:baseline}
.field--aside > .field-label{grid-column:1;grid-row:1}
.field--aside > .field-aside{grid-column:2;grid-row:1;justify-self:end}
.field--aside > .input{grid-column:1 / -1;grid-row:2}
.field--aside > .field-error,
.field--aside > .field-help{grid-column:1 / -1;grid-row:3}
.field-label{font-family:var(--f-ui);font-size:var(--t-small);font-weight:600}
.field-aside{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);
  text-decoration:none;transition:color var(--dur) var(--ease)}
.field-aside:hover{color:var(--ink)}
.input,.select,.textarea{
  font-family:var(--f-ui);font-size:var(--t-ui);width:100%;
  border:0;background:var(--well);padding:var(--field-pad-y) var(--field-pad-x);border-radius:var(--r-md);
  transition:background-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease)}
.textarea{min-height:var(--field-min-h);line-height:1.55;resize:none}
.input::placeholder,.textarea::placeholder{color:var(--muted)}
.input:focus,.select:focus,.textarea:focus{
  outline:none;background:var(--surface);box-shadow:inset 0 0 0 var(--ring-w) var(--ink)}
.is-bad .input,.is-bad .select,.is-bad .textarea{box-shadow:inset 0 0 0 var(--ring-w) var(--miss)}
.field-help{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);line-height:1.5}
/* An example inside help text. `.codeblock` is a BLOCK; this is its
   inline sibling, only for saying "write this exactly as shown". */
.code-inline{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:var(--t-mono-inline);background:var(--well);border-radius:var(--r-sm);
  padding:var(--inline-code-pad-y) var(--inline-code-pad-x);color:var(--ink)}
/* Two fields side by side -- ones that belong together, like a
   button's label and its address. Stacked on a narrow screen. */
.field--pair{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);
  gap:var(--s3);align-items:start}
@media (max-width:520px){.field--pair{grid-template-columns:1fr}}
/* The mail body is taller than the others: it is the field written
   in the most. */
.mail-body{min-height:var(--field-min-h-lg)}
.field-error{font-family:var(--f-ui);font-size:var(--t-small);color:var(--miss);line-height:1.5}
/* The checkbox is drawn in the system's own shape. Previously only
   `accent-color` was given, so when unchecked the browser's default
   box showed through, and it looked different in every browser.
   Alignment was also done with `margin-top`, which left the label
   sitting high. Now both are centred. */
.check-row{display:flex;align-items:center;gap:var(--s3)}
/* check-row and .row below draw two different checkmarks: different
   box sizes, different mark geometry, kept as two token pairs rather
   than forcing one number onto both. */
.check-row input{appearance:none;-webkit-appearance:none;margin:0;flex:none;
  width:var(--checkbox-size);height:var(--checkbox-size);border-radius:var(--r-sm);background:var(--paper);
  box-shadow:inset 0 0 0 var(--hairline) var(--rule-firm);cursor:pointer;position:relative;
  transition:box-shadow var(--dur) var(--ease),background-color var(--dur) var(--ease)}
.check-row input:hover{box-shadow:inset 0 0 0 var(--hairline) var(--ink)}
.check-row input:checked{background:var(--mark);box-shadow:none}
/* The checkmark is CENTRED, not placed with a hand-measured offset.
   The reasoning is below, next to .check::after -- the checkmark is
   drawn in two places and both use the same rule. */
.check-row input:checked::after{content:"";position:absolute;left:50%;top:50%;
  width:var(--checkmark-w);height:var(--checkmark-h-a);border:var(--checkmark-stroke) solid var(--on-mark);border-top:0;
  border-left:0;transform:translate(-50%,-58%) rotate(var(--rot-a))}
.check-row input:focus-visible{outline:var(--ring-w) solid var(--ink);outline-offset:var(--ring-offset-tight)}
.check-row label{font-family:var(--f-ui);font-size:var(--t-small);line-height:1.5;
  cursor:pointer}

/* ---------- badges ---------- */
.badge{font-family:var(--f-ui);font-size:var(--t-badge-sm);font-weight:600;letter-spacing:var(--tr-badge);
  text-transform:uppercase;line-height:1;padding:var(--badge-pad-y) var(--badge-pad-x);border-radius:var(--r-pill);
  background:var(--well);color:var(--badge-ink);display:inline-block}
.badge--on{background:var(--mark);color:var(--on-mark)}
.badge--warn{background:var(--miss-wash);color:var(--miss)}

/* ---------- chips ----------
   A ready made starting point. Used wherever a blank field would
   otherwise be the first thing a new account meets. */
.chips{display:flex;gap:var(--s2);flex-wrap:wrap}
.chip{font-family:var(--f-ui);font-size:var(--t-small);text-decoration:none;
  background:var(--well);border-radius:var(--r-pill);padding:var(--pad-pill-y) var(--chip-pad-x);
  transition:background-color var(--dur) var(--ease)}
.chip:hover{background:var(--well-hover)}
.chip.is-on{background:var(--mark);color:var(--on-mark)}

/* ---------- card ---------- */
.card{background:var(--surface);border-radius:var(--r-lg);padding:var(--s5);
  display:flex;flex-direction:column;gap:var(--s3)}
.card-top{display:flex;align-items:center;justify-content:space-between;gap:var(--s3)}
.card-name{font-family:var(--f-display);font-size:var(--t-title-sm);font-weight:600}
.card-text{font-size:var(--t-read);color:var(--muted);line-height:1.55}

/* ---------- list item ----------
   Not tied to any one product. An entity with a state, a name and
   one line of meta. Every product fills it differently. */
.row{display:flex;align-items:center;gap:var(--s4);padding:var(--s4);
  background:var(--surface);border-radius:var(--r-md)}
.check{flex:none;width:var(--check-size);height:var(--check-size);border-radius:var(--r-sm);
  background:var(--well);position:relative;
  transition:background-color var(--dur) var(--ease)}
.row.is-done .check{background:var(--mark)}
/* The checkmark is CENTRED. Previously both checkmarks were placed
   with hand-measured `left`/`top` values, and the values did not
   hold: because the boxes (20px / 18px), the checkmark sizes (5x10 /
   5x9) and the rotation angles (42deg / 40deg) differ, no single
   fixed number fits both. Since the shape is rotated, the correct
   offset is not a round number either; the checkbox's own checkmark
   sat at 6.5px, which is exactly (18-5)/2, i.e. the geometric centre
   itself.

   So instead of writing a number, we CENTRE it. The part of -58%
   that is not -50% is an optical allowance: because a rotated
   checkmark's bottom arm overshoots downward, the geometric centre
   sits a touch low to the eye. If the box size or the checkmark size
   changes, this value does not change -- there is no need to
   remeasure. */
.row.is-done .check::after{content:"";position:absolute;left:50%;top:50%;
  width:var(--checkmark-w);height:var(--checkmark-h-b);border:solid var(--on-mark);border-width:0 var(--checkmark-stroke) var(--checkmark-stroke) 0;
  transform:translate(-50%,-58%) rotate(var(--rot-b))}
.row-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--gap-tight)}
.row-name{font-family:var(--f-ui);font-size:var(--t-ui);font-weight:500;line-height:1.3}
.row-meta{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);line-height:1.3}
.row.is-miss .row-meta{color:var(--miss)}

/* ---------- conversation list ----------
   NOT a new component: the inbox row is built on top of `.row`.
   Status + name + one line of context is already `.row`'s brief,
   and inventing a second name for the same thing (`.convo`) would
   leave the system with two vocabularies.

   The unread dot takes the place of the `.check` slot: its width is
   fixed, so read and unread rows line up the same. */
a.row{text-decoration:none;transition:background-color var(--dur) var(--ease)}
a.row:hover{background:var(--surface-hover)}
.row-flag{flex:none;width:var(--row-dot-size);display:flex;justify-content:center;
  align-self:center}
.row-dot{width:var(--row-dot-size);height:var(--row-dot-size);border-radius:50%;background:var(--mark)}
/* The preview is ONE LINE. If not truncated, a long message pushes
   each row in the list to a different height and the list becomes
   unscannable. */
.row-prev{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);
  line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row-time{flex:none;align-self:flex-start;font-family:var(--f-ui);
  font-size:var(--t-small);color:var(--muted);white-space:nowrap}
/* The unread row is set apart by weight, not colour: the accent
   colour's only job is marking where you are, and this is not
   that. */
.row.is-unread .row-name{font-weight:600}
.row.is-unread .row-prev{color:var(--ink)}

/* ---------- meter ---------- */
.meter{height:var(--meter-h);background:var(--well);border-radius:var(--r-pill);overflow:hidden}
.meter i{display:block;height:100%;background:var(--meter-fill);border-radius:var(--r-pill)}

/* ---------- notice ----------
   A condition that is true about the account. One line, one link,
   no icon, no block around it unless it is bad news. */
.notice{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  padding:var(--s3) var(--s4);border-radius:var(--r-md);background:var(--surface);
  font-family:var(--f-ui);font-size:var(--t-ui);flex-wrap:wrap}
.notice--bad{background:var(--miss-wash);color:var(--miss)}
.notice a{font-family:var(--f-ui);font-size:var(--t-small);font-weight:600;
  text-decoration:none;flex:none}

/* One line is the default. A sentence and at most one link, in the
   page's own voice, sitting where the thing it concerns is. */
.line{display:flex;align-items:baseline;gap:var(--s2);flex-wrap:wrap;
  font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);line-height:1.6}
.line b{font-weight:500;color:var(--ink)}
.line--bad b{color:var(--miss)}
.line a,.line button{font-family:var(--f-ui);font-size:var(--t-small);text-decoration:none;
  color:var(--ink);font-weight:600;transition:color var(--dur) var(--ease)}
.line button{background:none;border:0;padding:0;cursor:pointer}
.line a:hover,.line button:hover{color:var(--muted)}

/* The same voice when the link sits INSIDE the sentence. `.line` is a
   flex row, so a link in the middle split the sentence into pieces and
   left the full stop on a line of its own. */
.note{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);line-height:1.6}
.note a{color:var(--ink);font-weight:600;text-decoration:none;
  transition:color var(--dur) var(--ease)}
.note a:hover{color:var(--muted)}

/* ---------- empty state ----------
   Never a blank table. It says what is not here, what happens if
   you act, and gives ready made ways to start. */
.empty{display:flex;flex-direction:column;align-items:flex-start;gap:var(--s3);
  background:var(--surface);border-radius:var(--r-lg);padding:var(--s7) var(--s6)}
.empty-title{font-family:var(--f-display);font-size:var(--t-title-md);font-weight:600}
.empty-text{font-size:var(--t-read);color:var(--muted);line-height:1.5;max-width:var(--measure-prose-sm)}
.empty-picks{display:flex;flex-direction:column;gap:var(--s3);margin-top:var(--s3)}
.empty .btn{margin-top:var(--s4)}

/* ---------- conversation ----------
   Like a messaging app: who wrote it is clear from ALIGNMENT and
   TONE, there is no need to read a name on every line. The customer
   side and the admin panel share the same template, so both sides
   see the conversation the same way.

   `.msg` is not the bubble ITSELF, it is the column carrying the
   bubble and the name line above it. The bubble is `.msg-in`. The
   reason for separating them: the name and time sit outside the
   bubble and do not take the bubble's tone. */
.thread{display:flex;flex-direction:column;gap:var(--s5)}
.msg{max-width:min(82%,var(--measure-msg-max));align-self:flex-start;
  display:flex;flex-direction:column;gap:var(--gap-msg)}
/* The padding is NARROW. A bubble should be only as big as its
   content: a huge box for a one-word message thins out the
   conversation and makes the list unreadable. A one-line bubble's
   height is 9 + 26 + 6 = 41px.

   The horizontal measure was once bumped up to --s5 (24px) and that
   was too much; the bubbles sticking to the edge was not a padding
   problem, it came from the message list itself having no side
   gutter of its own (see layout.css, .chat-log).

   Top and bottom are NOT EQUAL, and that is deliberate: with
   symmetric padding, the text sits slightly above the bubble's
   middle. The reason is the line box itself -- with
   `line-height:1.55`, 17px text sits inside a 26px box, and the
   bottom part of that box is space reserved for descenders. On a
   line with no descenders -- which is most lines -- the letter mass
   visible to the eye stays in the upper half of the box. Giving
   three extra pixels at the top settles it into place. */
.msg-in{background:var(--surface);border-radius:var(--r-lg);
  padding:var(--msg-pad-top) var(--s4) var(--msg-pad-bottom);display:flex;flex-direction:column;gap:var(--s3)}
/* What we write sits on the RIGHT and in a different tone: having to
   count who said what while reading the conversation is the
   shortest path to replying to the wrong person. */
.msg--us{align-self:flex-end;align-items:flex-end}
.msg--us .msg-in{background:var(--well)}

/* Consecutive messages read as ONE STATEMENT: the name line appears
   once, the gap between them is tight, and the neighbouring corner
   shrinks. This is what makes a conversation a conversation --
   starting every message separately reads like being introduced to
   the same person thirty times.

   The gap is tightened with a negative margin: `.thread`'s gap is a
   single value, and instead of writing a second rule for two
   different gaps, the difference is closed here. */
.msg--run{margin-top:calc(var(--s2) - var(--s5))}
.msg--run .msg-in{border-top-left-radius:var(--r-sm)}
.msg--us.msg--run .msg-in{border-top-left-radius:var(--r-lg);
  border-top-right-radius:var(--r-sm)}

/* Name and time sit on the SAME line, and only at the start of a
   run. Writing a time under every bubble in a long run produces a
   column nobody reads; the run already breaks every five minutes,
   so the time at the start is close enough. */
.msg-who{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);
  display:flex;gap:var(--s2);padding:0 var(--s2)}
.msg-body{white-space:pre-wrap;line-height:1.55;overflow-wrap:anywhere}

/* ---------- attachments ----------
   What gets sent in a support conversation is mostly a screenshot.
   Showing it as a file name wastes the effort of sending it.

   The sizes work together WITH the bubble. The image used to be
   fixed at 240px and the bubble always stayed wider than it: the
   bubble's padding sat around the image like a frame, and it did
   not line up with text bubbles next to it either. Now the image
   decides the bubble's inner width ITSELF -- text wraps to that
   same width too -- so the two become the same thing.

   `width`/`height` come from the template and are the real
   dimensions. Without them the browser cannot reserve the space in
   advance, and the conversation jumps down when the image
   arrives. */
.att{display:flex;flex-wrap:wrap;gap:var(--s2)}
.att-img{display:block;border-radius:var(--r-md);background:var(--well);
  max-width:min(var(--measure-att-img),100%);max-height:var(--measure-att-img);width:auto;height:auto;
  cursor:zoom-in}
/* When there is more than one image, each one shrinks and they line
   up side by side. They are not cropped: the part of a screenshot
   that gets cropped might be exactly the part someone wanted to
   show. */
.att--many .att-img{max-width:min(var(--measure-att-thumb),100%);max-height:var(--measure-att-thumb)}

/* In a message with no text, carrying only an image, the bubble
   ITSELF becomes the image. Drawing a separate box around the image
   loses the image inside the box -- and that is exactly where the
   impression of the sizes not fitting came from. */
.msg--art .msg-in{padding:0;background:none;overflow:hidden}
.msg--art .att-img{border-radius:var(--r-lg)}
.msg--art.msg--run .att-img{border-top-left-radius:var(--r-sm)}
.msg--us.msg--art.msg--run .att-img{border-top-left-radius:var(--r-lg);
  border-top-right-radius:var(--r-sm)}
/* A non-image attachment: name and size. Because it sits inside the
   bubble, its tone needs to differ from the bubble's, otherwise the
   attachment reads as the bubble itself. */
.att-file{display:flex;align-items:baseline;gap:var(--s2);text-decoration:none;
  background:var(--well);border-radius:var(--r-md);padding:var(--s2) var(--s3);
  font-family:var(--f-ui);font-size:var(--t-small);max-width:100%;
  transition:background-color var(--dur) var(--ease)}
.att-file b{font-weight:500;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.att-file span{color:var(--muted);flex:none}
.att-file:hover{background:var(--well-hover)}
.msg--us .att-file{background:var(--paper)}
.msg--us .att-file:hover{background:var(--surface)}

/* ---------- file request ----------
   On their own, the customer only ever sends a photo. This block is
   the one exception to that rule: inside the conversation, once,
   and a prompt whose terms we set.

   Inside the bubble but in a tone SEPARATE from it: the reader needs
   to see that there is something to do here before finishing the
   sentence. */
.ask{background:var(--well);border-radius:var(--r-md);
  padding:var(--s3);display:flex;flex-direction:column;gap:var(--s3)}
.msg--us .ask{background:var(--paper)}
.ask-form{display:flex;flex-direction:column;gap:var(--s3);align-items:flex-start}
.ask-file{font-family:var(--f-ui);font-size:var(--t-small);background:var(--surface)}
.ask-line{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);
  line-height:1.4}

/* ---------- day divider ----------
   Just the date, no line. There used to be a line on either side of
   it, and that made the conversation look like a document split
   into sections; but what is being separated here is not a section,
   just a day. A small, faint date already says enough. */
.day{display:flex;justify-content:center}
.day span{font-family:var(--f-ui);font-size:var(--t-label);font-weight:600;
  letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--muted)}

/* ---------- composer ----------
   Not a labelled form, a single field: text, attachment and send
   side by side. There used to be a `.panel` with a textarea labelled
   "Reply" and a bare file input here; writing felt like a
   TRANSACTION, not a conversation. */
.composer{background:var(--well);border-radius:var(--r-lg);padding:var(--s2);
  display:flex;flex-direction:column;gap:var(--s2);
  transition:box-shadow var(--dur) var(--ease)}
/* Focus lives on the CONTAINER itself: the text area inside it has
   no frame of its own, so this is the only thing that shows where
   focus is. */
.composer:focus-within{box-shadow:inset 0 0 0 var(--ring-w) var(--ink)}
.composer.is-drop{box-shadow:inset 0 0 0 var(--ring-w) var(--ink)}
/* The subject field sits in the same container as the text field,
   but in its own tone: the two are not the same job, and if they
   looked the same, the subject would read as just another message
   line. */
.composer-subject{background:var(--paper);font-weight:600}
.composer-subject:focus{background:var(--surface);box-shadow:none}
.composer-in{display:flex;align-items:flex-end;gap:var(--s2)}
.composer-text{flex:1;min-width:0;border:0;background:transparent;resize:none;
  font-family:var(--f-ui);font-size:var(--t-ui);line-height:1.55;
  padding:var(--pad-compact-y) var(--s3);max-height:var(--measure-composer-max);overflow-y:auto;
  /* The scrollbar that appears while writing a long message is
     faint too: same behaviour, same tone, as the conversation
     list. */
  scrollbar-width:thin;scrollbar-color:var(--rule-firm) transparent}
.composer-text::-webkit-scrollbar{width:var(--scrollbar-w)}
.composer-text::-webkit-scrollbar-track{background:transparent}
.composer-text::-webkit-scrollbar-thumb{background:var(--rule-firm);
  border-radius:var(--r-pill);border:var(--scrollbar-thumb-inset) solid transparent;
  background-clip:content-box}
.composer-text:focus{outline:none}
.composer-text::placeholder{color:var(--muted)}
.composer-acts{display:flex;align-items:center;gap:var(--s2);flex:none}
.composer-note{font-family:var(--f-ui);font-size:var(--t-small);
  color:var(--muted);padding:0 var(--s3) var(--s1)}
/* An extra control attached under the writing field -- the "also
   send this as mail" checkbox in the panel. Its alignment matches
   the text field. */
.composer-extra{padding:0 var(--s3) var(--s2);overflow-wrap:anywhere}

/* The line that appears when sending fails. NOT a toast: the error
   belongs to this form, it should not be a notification that pops
   up in a corner and leaves, it should be an explanation standing
   next to what was written. Because the page does not reload, the
   server's flash message does not arrive either -- the text comes
   from the JSON response. */
.composer-err{font-family:var(--f-ui);font-size:var(--t-small);
  color:var(--miss);line-height:1.5;padding:0 var(--s3) var(--s2)}

/* The file input is hidden when a script is present, and the attach
   button takes its place. Without a script it stays AS IS: ugly but
   working, and working beats pretty but broken.

   The gate is `.chat-js`, not `.js`. `.js` is set by the one-line
   script in base.html, meaning "a script is running"; but what
   drives the attach button is chat.js, and if that file fails to
   load, the button would do nothing. This class is set by chat.js
   ITSELF: what is shown is what actually works. */
.chat-js .composer-file{position:absolute;width:var(--sz-hidden);height:var(--sz-hidden);opacity:0;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.composer-file{font-family:var(--f-ui);font-size:var(--t-small);
  padding:0 var(--s3) var(--s2)}
.attach{display:none}
.chat-js .attach{display:inline-flex;align-items:center;justify-content:center;
  width:var(--attach-size);height:var(--attach-size);border-radius:var(--r-md);background:transparent;
  border:0;color:var(--muted);cursor:pointer;flex:none;
  transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease)}
.attach:hover{background:var(--well-hover);color:var(--ink)}
.attach svg{width:var(--icon-size);height:var(--icon-size);display:block}
/* Because the input is invisible, the focus ring is drawn on the
   LABEL instead; otherwise someone navigating by keyboard would not
   be able to see where focus went. */
.composer-file:focus-visible + .attach{outline:var(--ring-w) solid var(--ink);
  outline-offset:var(--ring-offset-tight)}

/* Attachments waiting to be sent. They can be removed one at a
   time: picking the wrong file does not mean redoing the whole
   selection. */
.tray{display:flex;flex-wrap:wrap;gap:var(--s2);padding:0 var(--s2)}
.tray:empty{display:none}
.tray-item{display:flex;align-items:center;gap:var(--s2);
  background:var(--surface);border-radius:var(--r-pill);
  padding:var(--pad-tray-tight) var(--pad-tray-tight) var(--pad-tray-tight) var(--s3);font-family:var(--f-ui);font-size:var(--t-small)}
.tray-item span{color:var(--muted)}
.tray-x{background:none;border:0;padding:0;width:var(--tray-x-size);height:var(--tray-x-size);cursor:pointer;
  border-radius:50%;color:var(--muted);font:400 var(--t-tray-x)/1 var(--f-ui);
  display:flex;align-items:center;justify-content:center;
  transition:background-color var(--dur) var(--ease),color var(--dur) var(--ease)}
.tray-x:hover{background:var(--well-hover);color:var(--ink)}

/* The enlarged image. The system's only shadow belongs to the
   modal, and this is a modal too; there is no card, because the
   only thing that needs to be seen is the image itself. */
.modal--zoom{max-width:min(var(--measure-zoom-w),var(--measure-zoom-max))}
.modal--zoom img{display:block;max-width:100%;max-height:var(--measure-zoom-h);
  width:auto;height:auto;border-radius:var(--r-lg);cursor:zoom-out}

/* ---------- raw text ----------
   The body of an incoming mail, or a command meant to be copied.
   Line breaks are PRESERVED: a step-by-step error report must not
   collapse into a single paragraph.

   This class WAS USED in inbox.html but had never been defined --
   meaning incoming mail sat there as a shapeless lump of text. */
.codeblock{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:var(--t-small);line-height:1.6;color:var(--ink);
  background:var(--well);border-radius:var(--r-md);
  padding:var(--s3) var(--s4);white-space:pre-wrap;overflow-x:auto}

/* ---------- table ----------
   Tone alternates instead of a rule, because the system has no
   rules around things. Wide tables scroll inside their own box. */
.table-scroll{overflow-x:auto;border-radius:var(--r-lg);background:var(--surface)}
.table{font-family:var(--f-ui);font-size:var(--t-ui);min-width:var(--measure-table-min)}
.table th{text-align:left;font-size:var(--t-small);font-weight:600;color:var(--muted);
  background:var(--well);padding:var(--s3) var(--s4);white-space:nowrap}
.table td{padding:var(--s4);vertical-align:top}
.table tbody tr:nth-child(even) td{background:var(--well)}
.table .num{font-variant-numeric:tabular-nums}

/* ---------- modal ----------
   The only component allowed a shadow, because it is the only one
   that genuinely floats. Built on <dialog> so it closes with Esc
   and traps focus without a line of our own JavaScript. */
/* `color` is written EXPLICITLY. The browser's own stylesheet has
   `color:CanvasText` for <dialog>, and because that is a declaration
   written directly on the element, it OVERRIDES the inheritance
   coming from body. Everything whose colour was not specified -- the
   title, labels, the password being typed -- took the browser's
   colour; the ones with colour specified (text, buttons) were
   visible. "Some of the text is not showing" was exactly this
   split. */
.modal{border:0;padding:0;background:transparent;color:var(--ink);
  max-width:min(var(--measure-modal-max),var(--measure-overlay-w))}
.modal::backdrop{background:var(--veil)}
/* `--surface`, not `--paper`: the modal is a card sitting ON TOP of
   the page. When it was the same colour as the page ground, it
   looked like part of the page instead of a surface floating over
   the veil. */
/* Block, not flex. There used to be `display:flex` and
   `align-items:flex-start` here: because the form was a flex item,
   it shrank to fit ITS CONTENT, so max-width never kicked in and the
   password field did not end up the same width as the card. The
   card looked narrow and unbalanced because of this. */
.modal-in{background:var(--surface);border-radius:var(--r-lg);padding:var(--s6);
  box-shadow:var(--shadow-lift)}
.modal-title{font-family:var(--f-display);font-size:var(--t-title-md);font-weight:600;
  line-height:1.2;letter-spacing:var(--tr-tight)}
.modal-text{font-family:var(--f-ui);font-size:var(--t-ui);color:var(--muted);line-height:1.55}
/* Actions sit on the RIGHT, below a line: the decision is a region
   separate from the text being read. The safe option comes first,
   the destructive one last -- the path to the final stop is
   longer. */
.modal-acts{display:flex;gap:var(--s3);flex-wrap:wrap;justify-content:flex-end;
  margin-top:var(--s5);padding-top:var(--s4);box-shadow:inset 0 var(--hairline) 0 var(--rule)}

/* ---------- site nav ---------- */
.nav{display:flex;align-items:center;justify-content:space-between;gap:var(--s5)}
.nav-mark{font-family:var(--f-display);font-size:var(--t-wordmark-site);font-weight:600;
  letter-spacing:var(--tr-tight);text-decoration:none;white-space:nowrap}
.nav-links{display:flex;align-items:center;gap:var(--s5)}
.nav-links a{font-family:var(--f-ui);font-size:var(--t-ui);text-decoration:none;
  color:var(--muted);transition:color var(--dur) var(--ease)}
/* `:not(.btn)`: this rule used to apply to the button too, and on
   hover it turned the text to --ink, making it invisible on the
   dark ground. */
.nav-links a:not(.btn):hover{color:var(--ink)}
.nav-links .btn{color:var(--on-mark)}
.nav-links .btn--line{color:var(--ink)}

/* ---------- the product view ----------
   One component doing two jobs: the picture on the landing page
   and the working surface in the app. A recessed ground with
   raised rows on it. */
.screen{background:var(--well);border-radius:var(--r-lg);padding:var(--s3);
  display:flex;flex-direction:column;gap:var(--s2)}
.screen-bar{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  padding:var(--s2) var(--s3)}
.screen-mark{font-family:var(--f-display);font-size:var(--t-wordmark-screen);font-weight:600}
/* A product's own logo in place of the set name (templates/brand/logo.svg,
   see _wordmark.html): sized by height, coloured by the element around
   it. */
.nav-mark svg,.mark svg,.screen-mark svg,.foot-mark svg{display:block;width:auto;
  height:var(--logo-h)}
.screen-mark svg{height:var(--logo-h-screen)}
.foot-mark svg{height:var(--logo-h-foot)}
.screen-right{display:flex;align-items:center;gap:var(--s3)}
.screen-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--s4);
  padding:var(--s2) var(--s3) var(--s3)}
.screen-date{font-family:var(--f-ui);font-size:var(--t-small);color:var(--muted);
  margin-bottom:var(--s1)}
.screen-title{font-family:var(--f-display);font-size:var(--t-screen-title);font-weight:600;line-height:1.15}
.screen-list{display:flex;flex-direction:column;gap:var(--s2)}
.screen-foot{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  padding:var(--s3);font-family:var(--f-ui);font-size:var(--t-small);flex-wrap:wrap}
.screen-foot b{font-weight:600}

/* ---------- footer ----------
   A full-width dark band (round 12): the public pages end somewhere
   you can see. Its colours are the --foot-* tokens, not ink and paper
   swapped, so dark mode gets a band darker than its paper. */
.foot-band{background:var(--foot-bg);color:var(--foot-ink);margin-top:var(--s9);
  /* An accent drawn on the band (a logo's dot) takes its dark-ground step. */
  --accent:var(--accent-dark)}
.foot{display:grid;grid-template-columns:minmax(0,1.6fr) repeat(3,minmax(0,1fr));
  gap:var(--s6);padding-block:var(--s8) var(--s9)}
.foot-mark{font-family:var(--f-display);font-size:var(--t-h2);font-weight:var(--w-light);
  letter-spacing:var(--tr-tight);line-height:1.1}
.foot-col{display:flex;flex-direction:column;gap:var(--s3)}
.foot-label{font-family:var(--f-ui);font-size:var(--t-label);font-weight:600;
  letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--foot-soft)}
.foot-col a{font-family:var(--f-ui);font-size:var(--t-small);color:var(--foot-ink);
  text-decoration:none;transition:color var(--dur) var(--ease)}
.foot-col a:hover{color:var(--foot-soft)}
/* The languages, a line under the columns. */
.foot-langs{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5);padding-block:0 var(--s7);
  font-family:var(--f-ui);font-size:var(--t-small)}
.foot-langs a{color:var(--foot-soft);text-decoration:none}
.foot-langs a:hover,.foot-langs a[aria-current]{color:var(--foot-ink)}
.foot-langs a[aria-current]{font-weight:600}
.foot-band a:focus-visible{outline-color:var(--foot-ink)}
@media (max-width:680px){
  .foot{grid-template-columns:repeat(2,minmax(0,1fr));padding-block:var(--s7) var(--s8)}
  .foot-mark{grid-column:1 / -1}
}

/* ---------- narrow screens ---------- */
/* On a narrow phone the bar keeps the mark, one link and the button. */
@media (max-width:480px){ .nav-extra{display:none} }
@media (max-width:680px){
  .nav{gap:var(--s3)}
  .nav-mark{font-size:var(--t-wordmark-site-sm)}
  .nav-mark svg{height:var(--logo-h-sm)}
  .nav-links{gap:var(--s4)}
  .nav-links a{white-space:nowrap}
  .nav-links .btn{padding:var(--pad-pill-y) var(--s3)}
  /* "Open <product>" does not fit a phone once the name is longer than a
     word or two; the verb alone still says where it goes. */
  .nav-long{display:none}

  .screen{padding:var(--s2)}
  .screen-bar,.screen-head,.screen-foot{
    padding-left:var(--s3);padding-right:var(--s3)}
  .screen-head{flex-wrap:wrap;gap:var(--s3)}
  .screen-title{font-size:var(--t-h3)}

  .row{flex-wrap:wrap;row-gap:var(--s3);gap:var(--s3);padding:var(--s3)}
  .row-body{flex:1 1 auto}

  .empty{padding:var(--s6) var(--s5)}
  .foot{gap:var(--s5) var(--s6)}
  .foot-col{flex:1 1 40%}
  .foot-mark{margin-left:0;flex:1 1 100%;text-align:left}
}

/* A row's check becomes a real control when the page is the app
   rather than a picture of it. Same box, same states. */
.check-form{flex:none;display:flex}
button.check{border:0;padding:0;cursor:pointer;font:inherit}
button.check:hover{background:var(--rule-firm)}
.row.is-done button.check:hover{background:var(--mark-hover)}

/* ---------- destructive confirm ----------
   The form inside the modal. The same form is also used on its own
   page, so its layout lives here, not in the dialog. */
.modal-form{display:flex;flex-direction:column;gap:var(--s3)}
