/* =========================================================================
   osTicket client portal theme — Global Pathways Academy
   =========================================================================

   PRESENTATION LAYER. This stylesheet carries the client-portal redesign;
   the only template edits made alongside it are wrapper/markup changes in
   footer.inc.php and login.inc.php, verified attribute-identical with
   tools/attr-inventory.sh. No PHP logic, form attribute, or JS hook changed.

   Scope: loaded only by include/client/header.inc.php (via ASSETS_PATH,
   defined in client.inc.php). The agent and admin panels load
   scp/css/scp.css instead and are unaffected.

   Design source — the Stitch redesign
   -----------------------------------
   The design is Dani's, made in Google Stitch: project 11017655128922025229,
   "Support Ticket Attachment Redesign", six client-facing screens. The token
   values below are read out of that project's design theme rather than
   sampled off a screenshot, so they are the design's own numbers:

     background   #fbf9f4   the cream page
     surface      #ffffff   cards (Stitch's surface-container-lowest)
     ...low       #f5f3ee   the nav rail, insets
     ...high      #eae8e3   hovers
     ...highest   #e4e2dd   the footer band
     surface-alt  #EBE9E2   every card and input border
     primary      #14181c   near-black — filled buttons and headlines
     on-surface   #1b1c19   body text        (14.9:1 on cream)
     ...variant   #44474a   secondary text    (9.2:1)
     outline      #75777b   the floor for small labels
     secondary    #3d674e   sage — semantic only (open, resolved)
     upload-blue  #5A78A8   focus rings, in-progress
     success      #618C71   the Post Reply fill
     error        #ba1a1a
     radius       4px inputs, 8px panels, 12px cards, pills for nav
     type         Hanken Grotesk — headlines 600/700, labels 600 tracked,
                  body 400
     shell        a 64px top bar over a 256px left rail, 1200px canvas

   What changed from the first pass: sage is no longer the primary action
   colour. Stitch puts the weight on a near-black CTA and reserves sage for
   status — the Open chip and the active nav pill — so colour on this portal
   now means something specific everywhere it appears. The top pill nav is
   replaced by the rail, and the 300-weight hairline headlines are gone.

   The gp.academy palette the first pass copied is not abandoned: sage,
   cream and the warm surface ladder all survive, retuned to Stitch's exact
   values, so the portal still reads as the same institution as the parent
   site even though the face and the shell changed.

   Hanken Grotesk is self-hosted from assets/default/font/ (SIL Open Font
   License; it is a variable font, so one file covers 400-700 and a second
   covers the latin-ext range). Self-hosting keeps the GDPR posture from
   docs/PROJECT-CONTEXT.md intact: no visitor IP is sent to a font CDN. The
   General Sans files are still in that directory — nothing else in the
   repository references them, but they are the parent site's face and
   deleting them is a separate decision from this one.

   Load order matters. These files load AFTER this one and win ties, so some
   rules below are raised to #content specificity (and keep !important where
   upstream used it):
     css/thread.css       .thread-body typography and link colour
     css/redactor.css     .redactor-box editor chrome
     css/filedrop.css     .filedrop upload widget (injected as field media)
     css/font-awesome.min.css

   Other invariants preserved from the first pass (see git history):
   colour is semantic-only outside the brand hue; reference numbers, dates
   and file sizes are tabular mono; section labels share one uppercase
   micro-label voice; alert bars auto-height (upstream clipped at 16px);
   the ticket-form 50/50 table-layout bug and the subject-cell descender
   clipping stay fixed; responsive from 320px up with no horizontal
   overflow (asserted by tools/preview/check.mjs).

   Re-branding: the palette lives in the custom properties below.
   ========================================================================= */

/* --- Brand face --------------------------------------------------------- */

/* --- Brand face --------------------------------------------------------- */

/* Hanken Grotesk ships as a variable font: one file covers 400-700, so the
   five separate weight files General Sans needed collapse to two (plus two
   italics). The latin-ext subset is a separate file with its own
   unicode-range, exactly as Google Fonts serves it — a browser fetches it
   only when the page actually contains a character in that range, which for
   this portal is a European name or a Latin-ext help topic. */
@font-face {
  font-family: 'Hanken Grotesk';
  src: url('../font/HankenGrotesk-Variable.woff2') format('woff2-variations'),
       url('../font/HankenGrotesk-Variable.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}
@font-face {
  font-family: 'Hanken Grotesk';
  src: url('../font/HankenGrotesk-Variable-ext.woff2') format('woff2-variations'),
       url('../font/HankenGrotesk-Variable-ext.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Hanken Grotesk';
  src: url('../font/HankenGrotesk-Variable-Italic.woff2') format('woff2-variations'),
       url('../font/HankenGrotesk-Variable-Italic.woff2') format('woff2');
  font-weight: 400 700;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}
@font-face {
  font-family: 'Hanken Grotesk';
  src: url('../font/HankenGrotesk-Variable-Italic-ext.woff2') format('woff2-variations'),
       url('../font/HankenGrotesk-Variable-Italic-ext.woff2') format('woff2');
  font-weight: 400 700;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Ink, primary and surfaces ------------------------------------------- */
  /* Stitch's `primary` is near-black: the redesign puts weight on the
     headline and the one filled CTA and lets colour mean something else
     entirely. Sage is demoted to `secondary` — it now appears as the Open
     status chip and the active nav pill, never as a generic button. */
  --gp-primary:        #14181c;   /* filled buttons, headlines */
  --gp-primary-strong: #000000;   /* hover on a filled button */
  --gp-ink:            #1b1c19;   /* body text        (14.9:1 on cream) */
  --gp-ink-2:          #44474a;   /* on-surface-variant  (9.2:1) */
  --gp-ink-3:          #75777b;   /* outline — the floor for small labels */

  /* Brand (gp.academy sage) — semantic, not decorative -------------------- */
  --gp-brand:        #3d674e;   /* secondary  (6.6:1 on cream) */
  --gp-brand-strong: #2c4c39;   /* hover/strong */
  /* Three rungs of sage, and they are not interchangeable. `tint` is the
     hover wash — it lands under body text on a row or a list item, so it has
     to stay near the page. `soft` is the active nav pill and `chip` the Open
     status chip: both are Stitch's saturated secondary-container/fixed, and
     both are only ever a background for a short bold label. */
  --gp-brand-tint:   #eef4f0;   /* hover wash */
  --gp-brand-soft:   #bcebcb;   /* secondary-container — the active nav pill */
  --gp-brand-soft-ink: #14321f; /* darkened from Stitch's #416b52 to clear
                                   4.5:1 on the pill it sits in */
  --gp-brand-chip:   #bfedce;   /* secondary-fixed — Open chips */
  --gp-brand-ink:    #002111;   /* on-secondary-fixed */
  --gp-brand-edge:   #cfdcd4;
  --gp-brand-ring:   rgba(61, 103, 78, 0.28);
  --gp-moss:         #99A87F;   /* accent hairline — decorative only */

  /* Blue. Stitch's `upload-blue`: focus rings, in-progress state, the one
     colour that means "something is happening". */
  --gp-blue:      #5A78A8;
  --gp-blue-deep: #3f5c8a;      /* 5.8:1 on white — the text-safe rung */
  --gp-blue-tint: #eef1f7;
  --gp-blue-edge: #c9d6ea;
  --gp-blue-ring: rgba(90, 120, 168, 0.28);

  /* Surfaces. Stitch's container ladder, low to high. Cards sit at
     `lowest` (white) on the `background` cream; the footer band and the
     heaviest chips sit at `highest`. */
  --gp-page:      #fbf9f4;   /* background / surface */
  --gp-surface:   #ffffff;   /* surface-container-lowest — cards */
  --gp-surface-1: #f5f3ee;   /* surface-container-low — insets, the rail */
  --gp-surface-2: #f0eee9;   /* surface-container */
  --gp-surface-3: #eae8e3;   /* surface-container-high */
  --gp-surface-4: #e4e2dd;   /* surface-container-highest — footer band */
  --gp-beige:     #EBE9E2;   /* surface-alt — every card and input border */

  --gp-line:      #EBE9E2;   /* surface-alt, as a hairline */
  --gp-line-soft: rgba(27, 28, 25, 0.06);
  --gp-outline:   #c5c6ca;   /* outline-variant — control borders */

  /* Upload states. Two colours, and they are the only two the uploader is
     allowed: blue means IN FLIGHT, green means LANDED. From the Stitch
     redesign, where the attachment row is the one place the interface
     reports progress — so it is the one place a progress colour appears. */
  --gp-upload:      #5A78A8;
  --gp-upload-tint: rgba(90, 120, 168, 0.10);
  --gp-upload-edge: #c9d6ea;
  --gp-done:        #618C71;

  /* Semantic — meaning only, never decoration ---------------------------- */
  --gp-ok:        #3d674e;   /* resolved / open */
  --gp-ok-bg:     #bfedce;
  --gp-ok-edge:   #a3d1b3;
  --gp-warn:      #8a5300;
  --gp-warn-bg:   #faf3e2;
  --gp-warn-edge: #e7d3a8;
  --gp-err:       #ba1a1a;   /* Stitch `error` */
  --gp-err-bg:    #ffdad6;   /* error-container */
  --gp-err-edge:  #f0b8b3;
  --gp-err-ink:   #93000a;   /* on-error-container */
  --gp-info:      #3f5c8a;
  --gp-info-bg:   #eef1f7;
  --gp-info-edge: #c9d6ea;
  --gp-send:      #618C71;   /* success-green — the Post Reply fill */
  --gp-send-strong: #4e7460;

  /* Type ------------------------------------------------------------------ */
  --gp-sans: "Hanken Grotesk", ui-sans-serif, -apple-system, BlinkMacSystemFont,
             "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --gp-mono: ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Mono",
             Menlo, Consolas, monospace;

  /* The Stitch type scale, verbatim. Weights are heavier than the previous
     pass across the board — headlines are 600/700 here where they were a
     300 hairline before, which is the single biggest change in the file. */
  --gp-t-hxl:   48px;  --gp-lh-hxl:   56px;   /* page h1, desktop */
  --gp-t-hlg:   32px;  --gp-lh-hlg:   40px;   /* section h1 */
  --gp-t-hlgm:  28px;  --gp-lh-hlgm:  36px;   /* h1, phone */
  --gp-t-hmd:   24px;  --gp-lh-hmd:   32px;   /* card heading */
  --gp-t-hsm:   18px;  --gp-lh-hsm:   24px;   /* rail heading */
  --gp-t-blg:   18px;  --gp-lh-blg:   28px;   /* lede */
  --gp-t-bmd:   16px;  --gp-lh-bmd:   24px;   /* body */
  --gp-t-bsm:   14px;  --gp-lh-bsm:   20px;   /* meta */
  --gp-t-lmd:   14px;  --gp-lh-lmd:   16px;   /* button and nav labels */
  --gp-t-lsm:   12px;  --gp-lh-lsm:   14px;   /* chips, eyebrows */

  /* Metrics ---------------------------------------------------------------- */
  /* Column scale. The three rungs are a golden-ratio progression — 27φ≈43.7,
     44φ≈71.2 — so a surface that steps up one reads as a deliberate change of
     measure rather than a mismatch. Which rung a surface takes is decided by
     the widest thing that MUST fit inside it, rounded up to the next rung:

       auth  one short text input          sign-in, check status, sign-up
       form  a rich-text toolbar, or a
             picker with a button beside   new ticket, profile, ticket reply
       page  anything tabular or
             multi-column                  ticket list, thread, KB, landing
       console  a working queue an agent
             scans all day                 agent panel ticket queue

     Nothing else may set a container width. A surface that needs a value not
     on this list is a surface whose contents need rethinking.

     The rungs survive the redesign unchanged. Stitch draws its canvas at a
     1200px max-width with the 256px rail taken out of it, which lands the
     content column within a hair of --gp-w-page; keeping the rungs keeps the
     width audit in tools/preview/check.mjs meaningful. */
  --gp-w-auth: 27rem;   /* 432px */
  --gp-w-form: 44rem;   /* 704px */
  --gp-w-page: 71rem;   /* 1136px, which is 1088 inside the page gutters */
  --gp-w-console: 100rem;  /* 1600px — the agent console; sits outside the
                              golden progression deliberately: it is sized by
                              the widest display the queue must fill, not by
                              its neighbours. Used by scp/css/scp-theme.css,
                              which copies these tokens (this file never loads
                              in the agent panel). */

  /* Shell. The rail and the bar are fixed; everything else is measured
     against them, so both are tokens rather than repeated magic numbers. */
  --gp-rail:   256px;   /* w-64 */
  --gp-bar-h:  64px;    /* h-16 */
  --gp-margin:      64px;   /* margin-desktop */
  --gp-margin-sm:   16px;   /* margin-mobile */

  --gp-pad:    24px;    /* gutter */
  --gp-gutter: 56px;    /* thread avatar gutter */
  --gp-r:      12px;    /* cards — rounded-xl */
  --gp-r-sm:   8px;     /* panels and buttons — rounded-lg */
  --gp-r-xs:   4px;     /* inputs and chips — rounded */
  --gp-r-pill: 999px;

  /* Card treatment. Two levels, and only two: a CARD is a standalone surface
     sitting on the page background; an INSET is a secondary surface nested in
     a page's flow. Boxes at the same width still look mismatched when one is
     padded 30 and its neighbour 22, which is most of what made the portal feel
     uneven — five different card paddings had accumulated. Stitch pads its
     cards on the 8px unit (p-8 / p-6), so both values move onto it. */
  --gp-card-pad:  32px;
  --gp-card-gap:  24px;   /* space above a card */
  --gp-inset-pad: 24px;

  /* Prose. Boxes fill their rung; the lines inside them do not. */
  --gp-measure:      68ch;   /* body copy in page-width content */
  --gp-measure-lede: 52ch;   /* the centred intro under a page heading */

  /* Motion — colours fast, transforms slightly slower, both ease-out ------- */
  --gp-t-color: 120ms ease-out;
  --gp-t-move:  160ms cubic-bezier(0.23, 1, 0.32, 1);

  /* Optical correction for Font Awesome 3.2.1 glyphs set beside text.
     FA's em box has ascent 1536 / descent 0, so a glyph's box centre does not
     coincide with its ink centre, and align-items:center lines up boxes, not
     ink — leaving the glyph visibly low against the text's cap band. Measured
     against General Sans by decoding rendered pixels: the text's cap band runs
     18.00-29.13px inside a 40px pill (centre 23.60) while the glyph ink centre
     landed at 22.69 — about 1px high. Nudging down by 1px at 15px text lands
     the plus, magnifier, house, book, and document glyphs on the cap-band
     centre. Expressed in em so it tracks font-size. Hanken Grotesk's cap
     height is within a hair of General Sans's, so the measured value carries
     over. */
  --gp-icon-nudge: 0.065em;

  /* Names kept so nothing downstream breaks while the file is being
     retuned. Both now resolve into the new palette. */
  --gp-navy:        var(--gp-blue-deep);
  --gp-navy-strong: #334970;
  --gp-navy-tint:   var(--gp-blue-tint);
  --gp-navy-edge:   var(--gp-blue-edge);
  --gp-gold-soft:   rgba(153, 168, 127, 0.22);  /* moss, decorative only */
  --gp-surface-2-legacy: #f0eee9;
}

/* =========================================================================
   1. Reset  (upstream reset.less, kept intact except where noted)
   ========================================================================= */

html {
  font-size: 100%;
  overflow-y: scroll;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
}
body {
  margin: 0;
  padding: 0;
  font-size: 16px;
  line-height: 1.55;
}
body, input, select, textarea, button {
  font-family: var(--gp-sans);
  color: var(--gp-ink);
}
b, strong { font-weight: 600; }
blockquote {
  margin: 1em 0;
  padding: 2px 0 2px 16px;
  border-left: 3px solid var(--gp-line);
  color: var(--gp-ink-2);
}
hr {
  display: block;
  height: 1px;
  border: 0;
  border-top: 1px solid var(--gp-line);
  margin: 1.5em 0;
  padding: 0;
}
small { font-size: 85%; }
ul, ol { margin: 1em 0; padding: 0 0 0 24px; }
img { border: 0; vertical-align: middle; max-width: 100%; }
form { margin: 0; }
fieldset { border: 0; margin: 0; padding: 0; }
label { cursor: pointer; }
input, select, textarea {
  font-size: 100%;
  margin: 0;
  vertical-align: baseline;
}
input { line-height: normal; }
input[type="button"], input[type="reset"], input[type="submit"], button {
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
input[type="checkbox"], input[type="radio"] {
  box-sizing: border-box;
  width: 17px;
  height: 17px;
  accent-color: var(--gp-primary);
  vertical-align: -3px;
}
textarea { overflow: auto; vertical-align: top; resize: vertical; }
table { border-collapse: collapse; border-spacing: 0; }
th, td { vertical-align: top; }
th { text-align: left; font-weight: normal; }
h1, h2, h3, h4, h5, h6, form, fieldset { margin: 0; padding: 0; }
code, kbd, samp, pre { font-family: var(--gp-mono); font-size: 0.9em; }

/* Every element that reports a size should measure it the same way. */
*, *::before, *::after { box-sizing: border-box; }

/* =========================================================================
   2. Typography
   ========================================================================= */

/* Stitch sets headlines SEMIBOLD — 600 at every rung, 700 only for the one
   48px page title — with tight tracking that opens up as the size drops.
   This reverses the first pass, where headlines were a 300 hairline. The
   weight is what carries the hierarchy here: the type scale itself is
   shallow (48/32/24/18), so if the headings were light they would not read
   as headings at all against 16px body copy.

   The page title is set with clamp() rather than a media query so it moves
   continuously between Stitch's headline-lg-mobile (28px) and headline-xl
   (48px) rungs instead of snapping at one breakpoint. */
h1, h2, h3, h4 {
  color: var(--gp-primary);
  font-weight: 600;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
h1 {
  font-size: clamp(var(--gp-t-hlgm), 2.2vw + 1.15rem, var(--gp-t-hlg));
  line-height: 1.25;
  letter-spacing: -0.015em;
  margin-bottom: 0.4em;
}
h2, .subject {
  font-size: var(--gp-t-hmd);
  line-height: calc(var(--gp-lh-hmd) / var(--gp-t-hmd));
}
h3 {
  font-size: var(--gp-t-hsm);
  line-height: calc(var(--gp-lh-hsm) / var(--gp-t-hsm));
}
h4 { font-size: var(--gp-t-bmd); }

/* Headings that open a new section need air above them. Scoped to direct
   children and article bodies so #reply h2 and .headline keep their own
   spacing. */
#content > h2,
#content > h3,
#content .faq-content .thread-body h2,
#content .faq-content .thread-body h3 { margin-top: 1.5em; }
#content .faq-content .thread-body h2 { font-size: var(--gp-t-hmd); }
#content .faq-content .thread-body h3 { font-size: var(--gp-t-hsm); }
#content .thread-body h1 { font-weight: 600; letter-spacing: -0.015em; }

/* Prose keeps a readable measure. The BOXES fill the width of the page (see the
   cards below); the lines inside them do not — a 130-character line is hard to
   track back to the next one. */
#content > p,
.thread-body p,
.faq-content p,
#landing_page .thread-body li { max-width: var(--gp-measure); }

#content > p { color: var(--gp-ink-2); margin: 0 0 1em; }

/* Reference numbers, dates, sizes: tabular mono. Ticket numbers read as
   record locators; digits align down a column. */
#ticketTable tbody td:first-child a,
#ticketInfo h1 small,
#clientLogin #ticketno,
.filesize,
.thread-entry .header time {
  font-family: var(--gp-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* One voice for every micro-label. Stitch's label-sm, tracked and uppercased
   — it labels the four facts above a thread, the columns of the ticket list,
   the attachment strip, and the reply box, and they must all sound alike. */
#ticketTable thead th,
#ticketTable caption,
.headline,
.sidebar section .header,
.freetext-files .title,
#reply h2 {
  font-size: var(--gp-t-lsm);
  line-height: calc(var(--gp-lh-lsm) / var(--gp-t-lsm));
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gp-ink-3);
}

/* --- Links ------------------------------------------------------------ */
/* Links resolve to blue, not sage. In the redesign sage carries status —
   an Open chip, the nav pill you are standing on — so a sage link would
   read as a state rather than a destination. Stitch's upload-blue is the
   interactive colour throughout: it is what a row title turns on hover and
   what a focused field rings. --gp-blue-deep, not --gp-blue, because the
   link sits on white at 14-16px and needs 4.5:1. */
a, .link {
  color: var(--gp-blue-deep);
  text-decoration: none;
  text-decoration-color: var(--gp-blue-edge);
  text-underline-offset: 0.18em;
  text-decoration-thickness: from-font;
  transition: color var(--gp-t-color);
}
a:hover, .link:hover {
  color: var(--gp-primary);
  text-decoration: underline;
  text-decoration-color: currentColor;
  cursor: pointer;
}
a:focus-visible,
.link:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[contenteditable]:focus-visible {
  outline: 2px solid var(--gp-blue);
  outline-offset: 2px;
  border-radius: var(--gp-r-xs);
}

/* =========================================================================
   3. Helpers  (names are markup contracts — keep all of them)
   ========================================================================= */

.centered     { text-align: center; }
.clear        { clear: both; height: 1px; visibility: hidden; }
.hidden       { display: none; }
.faded        { color: var(--gp-ink-3); }
.flush-right  { text-align: right; }
.flush-left   { text-align: left; }
.bleed        { margin: 0 !important; padding: 0 !important; }
.valign-helper { height: 100%; display: inline-block; vertical-align: middle; }
.truncate {
  display: inline-block;
  width: auto;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}
.row { }
.span4, .span8 { display: inline-block; vertical-align: top; margin: 0 1%; }
.span4 { width: 29.5%; }
.span8 { width: 66%; }

/* =========================================================================
   4. Page frame
   ========================================================================= */

body { background: var(--gp-page); }

/* Pin the footer to the viewport bottom on short pages. The body's flow
   children are #container then #footer (#overlay/#loading are fixed and
   scripts render nothing), so a flex column with the container growing
   keeps the footer at the bottom edge of every screen instead of floating
   mid-page after short content. */
body {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
#container { flex: 1 0 auto; }
#footer { flex-shrink: 0; }

/* The shell is a fixed 64px bar over a fixed 256px rail, with the content
   canvas inset past both. #container is no longer the centred column it was
   — it is the whole area right of the rail and below the bar — so the
   centring moved down onto #content, which is the box that actually holds
   the page.

   The rail is only laid out from --gp-bp-rail up. Below that it becomes the
   drawer the #nav-toggle checkbox opens, so the inset collapses with it and
   the phone layout is a single full-width column, exactly as Stitch draws
   it (the rail is `hidden md:flex` there). */
#container {
  width: 100%;
  max-width: none;
  margin: 0;
  padding: var(--gp-bar-h) 0 0;
  background: transparent;
  box-shadow: none;
}
@media (min-width: 981px) {
  #container { padding-left: var(--gp-rail); }
}

/* --- Embedded mode ------------------------------------------------------ */
/* header.inc.php puts .embed on <body> when the page is being framed, so the
   very same open.php that serves the portal serves the embed and no part of
   the ticket flow is duplicated. Everything below is chrome: the ticket form
   itself, its card, its fields and its uploader are untouched.

   The flex column that pins the footer to the bottom of the viewport has to
   be undone with it. Inside an iframe, min-height:100vh means the document
   can never measure shorter than the frame it is already in, so the height
   reporter in footer.inc.php could only ever ratchet the frame taller. */
body.embed {
  min-height: 0;
  display: block;
  /* The host page owns the backdrop. Painting cream over it would make the
     frame read as a pasted-in block rather than part of their layout. */
  background: transparent;
}
/* .site-bar is display:contents now, so hiding it hides nothing — its two
   children are the shell and both have to be named. */
body.embed .agent-bar,
body.embed #header,
body.embed .site-rail,
body.embed #footer { display: none; }
/* Outer width and gutters belong to the host's column, not to ours: the frame
   renders the widget and nothing else, and the host spaces it. The form keeps
   its own 44rem card and stays centred in whatever width it is given. */
body.embed #container {
  max-width: none;
  padding: 0;
}
/* #container reserves the bar's height on every page; framed there is no bar
   to reserve for, and the widget has to start at the top of the frame. */
body.embed #container { padding-top: 0; }
body.embed #content {
  max-width: none;
  margin: 0;
  padding: 0;
  min-height: 0;
}
/* Both offsets are set inside a min-width query, which the rule above cannot
   reach from outside one. */
@media (min-width: 981px) {
  body.embed #container { padding-left: 0; }
  body.embed #content { padding: 0; }
}
/* The embed is Stitch's standalone card: centred in whatever width the host
   gives it, heading and all. Every other page of the portal left-aligns its
   heading against the rail — there is no rail here, so there is nothing for
   it to align to. */
body.embed #ticketForm:has(> table) { margin: 0 auto; }
body.embed #content:has(#ticketForm > table) > h1,
body.embed #content:has(#ticketForm > table) > p {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

/* --- Agent context bar -------------------------------------------------- */
/* Shown only while a staff session is live and valid — header.inc.php gates
   the whole element on it — so an end user never renders this markup at all.

   It sits above .site-bar rather than inside it on purpose. The bar's nav is
   centred by giving the logo and the account cluster the same flex basis, and
   the 1150px collapse breakpoint was measured against the widest row the
   templates can emit; a fifth item in that row would break both, for every
   visitor, to serve a state only agents reach. Out here it costs the bar
   nothing.

   Navy is the portal's staff-facing accent and appears nowhere else above the
   fold, so the strip reads as a different plane from the client chrome —
   which is exactly what it is: a second identity, with one action. */
.agent-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
  margin: 18px 0 0;
  /* Matches .site-bar's insets exactly so the label starts on the logo's left
     edge and the action ends on the account cluster's right edge. */
  padding: 8px 10px 8px 20px;
  background: var(--gp-navy-tint);
  border: 1px solid var(--gp-navy-edge);
  border-radius: var(--gp-r-pill);
  font-size: 0.875rem;
}
/* The bar keeps its own 18px top margin when it leads the page; stacked under
   the strip the pair reads as one header block instead of two. */
.agent-bar + .site-bar { margin-top: 8px; }
/* Text only, like every other uppercase micro-label in the theme
   (.hero-eyebrow, .agent-signin-label, .sidebar section strong). This one
   briefly carried a Font Awesome glyph and was the odd one out; the glyph's
   ink centre measured 1.38px below the cap band and could not be aligned
   without a hand-tuned magic number.

   The cause is worth recording, because the same trap is set for any future
   icon+label pair: --gp-icon-nudge was derived by decoding rendered pixels
   for 15px MIXED-CASE nav text. Uppercase has no descenders, so its optical
   centre sits higher in the line box, and a 12px glyph's ink (10.4px) is
   taller than an 11px cap band (8.4px) — so the nudge, which pushes down,
   made it worse rather than better. Baseline alignment was measured too and
   lands about 1px high instead: Font Awesome 3.2.1's em box (ascent 1536,
   descent 0) means box centre never coincides with ink centre, so there is no
   principled alignment here, only a per-glyph constant. The strip already
   reads as staff-facing from its navy tint and filled pill; it does not need
   a 12px glyph bought at that price. */
.agent-bar-label {
  color: var(--gp-navy);          /* 8.4:1 on the tint */
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.agent-bar-name { color: var(--gp-ink); font-weight: 500; }
/* Filled, not ghost: this is the one action on the strip, and it has to beat
   the footer's ghost .staff-link for prominence. */
.agent-bar-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  min-height: 36px;
  padding: 7px 16px;
  background: var(--gp-navy);
  border: 1px solid var(--gp-navy);
  border-radius: var(--gp-r-pill);
  color: #fff !important;         /* 9.6:1 */
  font-weight: 500;
  white-space: nowrap;
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color),
              transform var(--gp-t-move);
}
.agent-bar-link::after { content: "\2192"; font-size: 0.9em; }  /* → */
.agent-bar-link:hover {
  background: var(--gp-navy-strong);
  border-color: var(--gp-navy-strong);
  color: #fff !important;
  text-decoration: none;
}
.agent-bar-link:active { transform: scale(0.98); }

/* --- Site bar: the fixed top bar ---------------------------------------- */
/* Stitch's shell is a 64px bar across the full width with the wordmark at
   the left and the account controls at the right, and a 256px rail below it
   down the left edge. The template emits #header and .site-rail as siblings
   inside .site-bar, so .site-bar is dissolved with display:contents and each
   of the two is positioned on its own. Nothing is reordered in the DOM and
   every conditional in header.inc.php still emits exactly what it did. */
.site-bar { display: contents; }
/* The template emits an <hr> in place of the nav when there is no nav, and a
   float-clearing div the shell makes redundant. Dissolving .site-bar makes
   both flow children of #container, where the clear's 1px height pushed every
   page down by a pixel — and in the frame, pushed the widget off the top. */
.site-bar > hr,
.site-bar > .clear { display: none; }

#header {
  box-sizing: border-box;
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 8px 20px;
  height: var(--gp-bar-h);
  padding: 0 var(--gp-margin-sm);
  background: var(--gp-page);
  border-bottom: 1px solid var(--gp-outline);
}
@media (min-width: 981px) {
  #header { padding: 0 var(--gp-margin); }
}
#header #logo {
  order: 1;
  height: auto;
  flex: 0 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
}
#header #logo:hover { text-decoration: none; }
#header #logo img {
  max-height: 40px;
  max-width: 240px;
  width: auto;
  height: auto;
  vertical-align: middle;
}
/* Account cluster / sign-in, anchored right — Stitch's top-right controls. */
#header .pull-right {
  order: 3;
  float: none;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 4px 14px;
}
#header p {
  width: auto;
  margin: 0;
  padding: 0;
  font-size: var(--gp-t-bsm);
  color: var(--gp-ink-2);
}
#header p:empty { display: none; }
/* The template always emits a second <p> for the language flags, holding only
   whitespace when the desk runs one language — so :empty never matches it.
   Keying on "has no element children" collapses it, and still shows it when
   flags are actually rendered. */
#header .pull-right > p:not(:has(*)) { display: none; }
#header p + p { margin-top: 0; }
#header p a { white-space: nowrap; }
/* Signed in, the template emits "Name Profile Tickets (n) Sign Out" as one
   run of text and links. Stitch's bar carries no chip around its controls —
   the bar is flat and hairline-bounded — so the capsule the first pass drew
   here is gone and the run is spaced instead. */
#header .pull-right > p:has(a):not(:has(.signin-button)) {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  color: var(--gp-ink-2);
}
.acct-name { color: var(--gp-primary); font-weight: 600; }
.acct-link {
  color: var(--gp-ink-2);
  font-weight: 500;
  border-radius: var(--gp-r-pill);
  transition: color var(--gp-t-color);
}
.acct-link:hover { color: var(--gp-primary); }
/* Tickets is already a rail item; in the bar it is noise. It stays in the
   collapsed drawer, where the rail is not on screen to carry it. */
@media (min-width: 981px) {
  #header .acct-tickets { display: none; }
}

/* Signed out there is one action in the bar, and it is filled near-black —
   the same treatment as every other primary action in the redesign. */
.signin-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 9px 20px;
  background: var(--gp-primary);
  border: 1px solid var(--gp-primary);
  border-radius: var(--gp-r-sm);
  color: #fff !important;
  font-size: var(--gp-t-lmd);
  font-weight: 600;
  white-space: nowrap;
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color),
              transform var(--gp-t-move);
}
.signin-button::after { content: "\2192"; font-size: 0.9em; }   /* → */
.signin-button:hover {
  background: var(--gp-primary-strong);
  border-color: var(--gp-primary-strong);
  color: #fff !important;
  text-decoration: none;
}
.signin-button:active { transform: scale(0.95); }

/* --- Menu toggle -------------------------------------------------------- */
/* A checkbox drives the mobile drawer so it works with no JavaScript at all —
   the client portal loads no pjax, so nothing re-renders the header behind
   our back, and a nav that depends on scripts is a nav that can fail. The
   input stays 1px and focusable (not display:none) so it remains reachable
   by keyboard and the label picks up its focus ring. */
#nav-toggle {
  position: fixed;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  opacity: 0;
  appearance: none;
  -webkit-appearance: none;
  border: 0;
}
.nav-toggle-btn {
  display: none;         /* shown by the media query below */
  order: 0;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex: none;
  border: 1px solid var(--gp-outline);
  border-radius: var(--gp-r-sm);
  color: var(--gp-primary);
  cursor: pointer;
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color);
}
.nav-toggle-btn::before {
  font-family: FontAwesome;
  font-style: normal;
  font-weight: normal;
  content: "\f0c9";      /* reorder (hamburger) */
  font-size: 15px;
  line-height: 1;
}
.nav-toggle-btn:hover {
  background: var(--gp-surface-3);
  border-color: var(--gp-ink-3);
}
#nav-toggle:checked ~ #header .nav-toggle-btn {
  background: var(--gp-primary);
  border-color: var(--gp-primary);
  color: #fff;
}
#nav-toggle:checked ~ #header .nav-toggle-btn::before { content: "\f00d"; }  /* × */
#nav-toggle:focus-visible ~ #header .nav-toggle-btn {
  outline: 2px solid var(--gp-blue);
  outline-offset: 2px;
}

/* --- The rail ------------------------------------------------------------ */
/* Fixed under the bar, down the left edge, on the low container surface so it
   reads as a plane behind the content rather than a card on it. */
.site-rail {
  box-sizing: border-box;
  position: fixed;
  left: 0;
  top: var(--gp-bar-h);
  bottom: 0;
  z-index: 40;
  width: var(--gp-rail);
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: var(--gp-pad) 16px;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--gp-surface-1);
  border-right: 1px solid var(--gp-outline);
}
.rail-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 16px 16px;
}
.rail-title {
  font-size: var(--gp-t-hsm);
  line-height: calc(var(--gp-lh-hsm) / var(--gp-t-hsm));
  font-weight: 600;
  color: var(--gp-primary);
}
.rail-org {
  font-size: var(--gp-t-lsm);
  line-height: calc(var(--gp-lh-lsm) / var(--gp-t-lsm));
  color: var(--gp-ink-2);
}

/* --- Primary navigation ------------------------------------------------- */
/* Rail items are full-width pills; the one you are standing on is filled with
   Stitch's secondary-container. Icons come from the Font Awesome build the
   page already loads. */
#nav {
  order: 2;
  flex: 0 0 auto;
  margin: 0;
  padding: 0;
  height: auto;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  white-space: normal;
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: auto;
  max-width: 100%;
}
#nav li { margin: 0; padding: 0; list-style: none; display: block; }
#nav li a {
  display: flex;
  align-items: center;
  gap: 12px;
  width: auto;
  height: auto;
  min-height: 44px;
  line-height: 1.3;
  text-align: left;
  padding: 12px 16px;
  margin: 0;
  color: var(--gp-ink-2);
  font-size: var(--gp-t-lmd);
  font-weight: 500;
  border-radius: var(--gp-r-pill);
  background: none;
  transition: background-color var(--gp-t-color), color var(--gp-t-color),
              transform var(--gp-t-move);
}
#nav li a::before {
  font-family: FontAwesome;
  font-weight: normal;
  font-style: normal;
  font-size: 1em;
  line-height: 1;
  width: 1.15em;
  text-align: center;
  color: var(--gp-ink-3);
  flex: none;
  transform: translateY(var(--gp-icon-nudge));
  transition: color var(--gp-t-color);
}
#nav li a.home::before    { content: "\f015"; }  /* home        */
#nav li a.kb::before      { content: "\f02d"; }  /* book        */
#nav li a.new::before     { content: "\f067"; }  /* plus        */
#nav li a.tickets::before { content: "\f0f6"; }  /* file-text   */
#nav li a.status::before  { content: "\f002"; }  /* search      */

#nav li a:hover {
  background: var(--gp-surface-3);
  color: var(--gp-primary);
  text-decoration: none;
}
#nav li a:hover::before { color: var(--gp-primary); }
/* Stitch nudges a rail item sideways on press rather than scaling it — the
   item is the full width of the rail, and scaling something that wide reads
   as the whole panel moving. */
#nav li a:active { transform: translateX(4px); }

/* Open a New Ticket is the rail's one filled action, lifted to the top of
   the list. Stitch draws it as a button above the items rather than as an
   item among them: it is the thing the portal exists for, and it should not
   have to be found in a list. Ordered rather than moved, so the template's
   loop still emits the nav in the order the backend gives it. */
#nav li:has(a.new) { order: -1; margin-bottom: 12px; }
#nav li a.new,
#nav li a.new.active {
  justify-content: center;
  min-height: 44px;
  padding: 12px 16px;
  background: var(--gp-primary);
  color: #fff;
  font-weight: 600;
  border-radius: var(--gp-r-sm);
}
#nav li a.new::before,
#nav li a.new.active::before { color: rgba(255, 255, 255, 0.9); width: auto; }
#nav li a.new:hover,
#nav li a.new.active:hover { background: var(--gp-primary-strong); color: #fff; }

/* --- Which pill is lit --------------------------------------------------- */
/* client.inc.php builds the nav with Home already active —
   `new UserNav($thisclient, 'home')` — and several pages never override it:
   login.php's override is unreachable behind an `if (!$nav)` that is never
   true, and account.php, profile.php, kb/*.php and pages/*.php make no
   setActiveNav() call at all. The result is a lit Home item on the sign-in,
   register, profile and knowledgebase pages, which misreports where you are.

   Corrected here rather than in PHP to keep this pass presentation-only. Each
   page is identified by markup only it renders. The real fix is four lines of
   setActiveNav() in those entry points; see this commit's message. */
#nav li a.active:not(.new),
#container:has(.searchbar) #nav li a.kb {
  color: var(--gp-brand-soft-ink);
  background: var(--gp-brand-soft);
  font-weight: 600;
}
#nav li a.active:not(.new)::before,
#container:has(.searchbar) #nav li a.kb::before { color: var(--gp-brand); }
#nav li a.active:not(.new):hover,
#container:has(.searchbar) #nav li a.kb:hover {
  background: var(--gp-brand-edge);
  color: var(--gp-brand-soft-ink);
}

/* Unlight the item the default left lit where it is wrong: on the KB pages
   (the rule above lights the right one instead), and on the sign-in, register
   and profile pages, which no nav item leads to at all. */
#container:has(.searchbar) #nav li a.home.active,
#container:has(.login-row) #nav li a.active:not(.new),
#container:has(form[action="account.php"]) #nav li a.active:not(.new),
#container:has(form[action="profile.php"]) #nav li a.active:not(.new) {
  color: var(--gp-ink-2);
  background: none;
  font-weight: 500;
}
#container:has(.searchbar) #nav li a.home.active::before,
#container:has(.login-row) #nav li a.active:not(.new)::before,
#container:has(form[action="account.php"]) #nav li a.active:not(.new)::before,
#container:has(form[action="profile.php"]) #nav li a.active:not(.new)::before {
  color: var(--gp-ink-3);
}
#container:has(.searchbar) #nav li a.home.active:hover,
#container:has(.login-row) #nav li a.active:not(.new):hover,
#container:has(form[action="account.php"]) #nav li a.active:not(.new):hover,
#container:has(form[action="profile.php"]) #nav li a.active:not(.new):hover {
  background: var(--gp-surface-3);
  color: var(--gp-primary);
}
#container:has(.searchbar) #nav li a.home.active:hover::before,
#container:has(.login-row) #nav li a.active:not(.new):hover::before,
#container:has(form[action="account.php"]) #nav li a.active:not(.new):hover::before,
#container:has(form[action="profile.php"]) #nav li a.active:not(.new):hover::before {
  color: var(--gp-primary);
}
.rtl #nav li a { padding: 12px 16px; margin: 0; background-position: initial; }

/* The rail on a phone: a drawer the toggle opens, not a column. It slides
   from the left edge under the bar and covers the content rather than
   displacing it, so opening the menu never reflows the page behind it. */
@media (max-width: 980px) {
  /* The bar is a fixed 64px row, so everything in it has to fit on one line.
     The logo is capped and the account cluster shrinks to its links: the name
     is the one thing in the cluster that says nothing you cannot see from
     being signed in, so it is what goes. */
  #header { gap: 8px 12px; }
  #header #logo img { max-height: 34px; max-width: 150px; }
  #header .pull-right { gap: 4px 10px; }
  #header .pull-right > p:has(a):not(:has(.signin-button)) { gap: 10px; }
  #header p { font-size: var(--gp-t-lsm); }
  .acct-name { display: none; }
  .signin-button { min-height: 36px; padding: 7px 14px; }
  .site-rail {
    width: min(300px, 86vw);
    transform: translateX(-100%);
    visibility: hidden;
    box-shadow: 24px 0 48px -32px rgba(20, 24, 28, 0.45);
    /* visibility is stepped, not interpolated: held until the slide-out
       finishes on the way closed, flipped at once on the way open. A plain
       `visibility 160ms` would leave the computed value `hidden` for the
       first frame after the toggle — invisible to the eye, but it means a
       script (or a test) reading the style right after the tap sees a drawer
       that has not opened. */
    transition: transform var(--gp-t-move),
                visibility 0s linear var(--gp-t-move);
  }
  #nav-toggle:checked ~ .site-rail {
    transform: none;
    visibility: visible;
    transition: transform var(--gp-t-move), visibility 0s;
  }
  .nav-toggle-btn { display: inline-flex; }

  /* The account cluster joins the foot of the drawer, which is where Stitch
     puts Settings and Sign Out. It cannot be moved there in the DOM without
     splitting the template's one conditional in two, so it is positioned onto
     the drawer instead — both boxes are fixed and both take their width from
     the same expression, so they cannot drift apart. Below this width there
     is not room in a 64px bar for a logo, a toggle AND two account links, and
     the cluster is what overflowed. */
  #header .pull-right {
    position: fixed;
    left: 0;
    bottom: 0;
    z-index: 41;
    box-sizing: border-box;
    width: min(300px, 86vw);
    margin: 0;
    padding: 16px;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 10px;
    background: var(--gp-surface-1);
    border-top: 1px solid var(--gp-outline);
    border-right: 1px solid var(--gp-outline);
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--gp-t-move),
                visibility 0s linear var(--gp-t-move);
  }
  #nav-toggle:checked ~ #header .pull-right {
    transform: none;
    visibility: visible;
    transition: transform var(--gp-t-move), visibility 0s;
  }
  #header .pull-right > p { width: 100%; }
  #header .pull-right > p:has(a):not(:has(.signin-button)) {
    justify-content: flex-start;
    flex-wrap: wrap;
  }
  .acct-name { display: inline; }
  /* Match the full-width rows above it in the drawer. */
  .signin-button { width: 100%; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .site-rail { transition: none; }
}

/* --- Content canvas ------------------------------------------------------ */
/* #container is now the whole area right of the rail, so the measure lives
   here: the canvas is Stitch's 1200px max-width minus the rail it sits
   beside, which lands on --gp-w-page. Padding is the design's margin token —
   16px on a phone, 64px from the rail breakpoint up. */
#content {
  width: 100%;
  max-width: var(--gp-w-page);
  margin: 0 auto;
  padding: var(--gp-pad) var(--gp-margin-sm) 56px;
  height: auto !important;
  min-height: 42vh;
}
@media (min-width: 981px) {
  #content { padding: 40px var(--gp-margin) 72px; }
}

/* --- Footer ------------------------------------------------------------ */
/* Stitch's footer is the top of the container ladder — surface-container-
   highest — banded across the canvas and inset past the rail with it. It
   also carries the site's "Staff Sign In" link, which is the pattern the
   portal copies for the agent panel (footer.inc.php adds .staff-link on
   every page). */
#footer {
  margin-top: 0;
  padding: 0;
  text-align: left;
  font-size: var(--gp-t-lsm);
  line-height: calc(var(--gp-lh-lsm) / var(--gp-t-lsm));
  color: var(--gp-ink-2);
  background: var(--gp-surface-4);
  border-top: 1px solid var(--gp-outline);
}
@media (min-width: 981px) {
  #footer { padding-left: var(--gp-rail); }
}
#footer a { color: var(--gp-ink-2); }
#footer a:hover { color: var(--gp-primary); }
#footer p { margin: 0; }
.footer-inner {
  max-width: var(--gp-w-page);
  margin: 0 auto;
  padding: 32px var(--gp-margin-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px 28px;
}
@media (min-width: 981px) {
  .footer-inner { padding: 32px var(--gp-margin); }
}
.footer-copyright { color: var(--gp-ink-2); }
.footer-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 22px;
}
/* Staff entrance: present on every page, and the only thing in the footer
   that is an action rather than a note. Stitch's footer links are plain
   text that darken on hover, so the ghost pill the first pass drew here is
   reduced to a hairline-bounded chip — enough to read as a door, not enough
   to compete with the page above it. */
.staff-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 6px 14px;
  border: 1px solid var(--gp-outline);
  border-radius: var(--gp-r-sm);
  color: var(--gp-ink-2) !important;
  font-weight: 600;
  transition: background-color var(--gp-t-color), color var(--gp-t-color),
              border-color var(--gp-t-color);
}
.staff-link::after { content: "\2192"; font-size: 0.9em; }  /* → */
.staff-link:hover {
  background: var(--gp-primary);
  border-color: var(--gp-primary);
  color: #fff !important;
  text-decoration: none;
}
#footer #poweredBy {
  display: block;
  width: 126px;
  height: 23px;
  outline: none;
  text-indent: -9999px;
  margin: 0;
  background: url('../images/poweredby.png') top left no-repeat;
  background-size: auto 20px;
  opacity: 0.45;
  transition: opacity var(--gp-t-color);
}
#footer #poweredBy:hover { opacity: 0.85; }

/* =========================================================================
   5. Buttons and form controls
   ========================================================================= */

/* --- .button family ----------------------------------------------------- */
/* .blue.button is the primary action in every template that uses it,
   .green.button the secondary. Stitch draws the pair as a filled near-black
   button beside an outlined one that borrows the same near-black for its
   ring — no hue at all, at either level. That is deliberate on the design's
   part: with the buttons neutral, the only coloured thing on a page is a
   status, so colour stays readable as meaning.

   Radius drops from a pill to Stitch's rounded-lg. A pill on a 44px control
   reads as a tag; the design uses pills only in the rail, where the item
   spans the full width and the shape is what distinguishes it. */
.button,
.button:visited {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 22px;
  max-width: 100%;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  text-align: center;
  text-decoration: none;
  text-shadow: none;
  border: 1px solid var(--gp-primary);
  border-radius: var(--gp-r-sm);
  background: var(--gp-primary);
  color: #fff;
  box-shadow: none;
  position: relative;
  cursor: pointer;
  font-family: inherit;
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color),
              color var(--gp-t-color), transform var(--gp-t-move);
}
.button:hover {
  background: var(--gp-primary-strong);
  border-color: var(--gp-primary-strong);
  color: #fff;
  text-decoration: none;
}
.button:active { top: 0; box-shadow: none; transform: scale(0.95); }

.button, .button:visited,
.blue.button, .blue.button:visited {
  background: var(--gp-primary);
  border-color: var(--gp-primary);
  color: #fff;
}
.blue.button:hover {
  background: var(--gp-primary-strong);
  border-color: var(--gp-primary-strong);
}
.green.button, .green.button:visited {
  background: transparent;
  border-color: var(--gp-primary);
  color: var(--gp-primary);
}
.green.button:hover {
  background: var(--gp-surface-1);
  border-color: var(--gp-primary);
  color: var(--gp-primary);
}

/* --- Native buttons ----------------------------------------------------- */
input[type="submit"],
button[type="submit"],
.btn {
  min-height: 44px;
  padding: 10px 22px;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  color: #fff;
  background: var(--gp-primary);
  border: 1px solid var(--gp-primary);
  border-radius: var(--gp-r-sm);
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color),
              transform var(--gp-t-move);
}
input[type="submit"]:hover,
button[type="submit"]:hover,
.btn:hover {
  background: var(--gp-primary-strong);
  border-color: var(--gp-primary-strong);
}
input[type="reset"],
input[type="button"],
button {
  min-height: 44px;
  padding: 10px 18px;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--gp-ink-2);
  background: transparent;
  border: 1px solid var(--gp-outline);
  border-radius: var(--gp-r-sm);
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color),
              color var(--gp-t-color), transform var(--gp-t-move);
}
input[type="reset"]:hover,
input[type="button"]:hover,
button:hover {
  background: var(--gp-surface-1);
  border-color: var(--gp-primary);
  color: var(--gp-primary);
}
input[type="submit"]:active,
input[type="reset"]:active,
input[type="button"]:active,
button:active,
.btn:active { transform: scale(0.95); }
input[type="submit"][disabled],
input[type="button"][disabled],
input[type="reset"][disabled],
button[disabled] {
  opacity: 0.55;
  cursor: default;
}

/* js/osticket.js flags an edited form with an inline color:red on the
   submit button, and clears it on reset. Inline colour wins, so give the
   button a light ground the red can be read against and carry the signal
   in the border too. */
input[type="submit"][style*="color"] {
  background: var(--gp-surface);
  border: 2px solid var(--gp-warn);
  font-weight: 600;
}

/* Action rows */
/* Action rows. These MUST stay direct-child selectors: as descendant
   selectors they also matched the last <p> inside Redactor's contenteditable
   (which is :last-child of .redactor-in), turning the paragraph the user types
   into a centred flex container with a 20px top margin — so text centred
   itself and the caret sat below the placeholder instead of over it. */
p.buttons,
#reply > p:last-child,
#ticketForm > p:last-child {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  align-items: center;
  margin: 20px 0 0;
}

/* --- Text inputs -------------------------------------------------------- */
input[type="text"],
input[type="password"],
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="number"],
input[type="search"],
input[type="date"],
input[type="time"],
select,
textarea {
  min-height: 44px;
  padding: 10px 14px;
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--gp-ink);
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r-xs);
  max-width: 100%;
  transition: border-color var(--gp-t-color), box-shadow var(--gp-t-color);
}
textarea { min-height: 120px; padding: 12px 14px; line-height: 1.55; }
select {
  padding-right: 34px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--gp-ink-3) 50%),
    linear-gradient(135deg, var(--gp-ink-3) 50%, transparent 50%);
  background-position: right 17px center, right 12px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  -webkit-appearance: none;
  appearance: none;
}
.rtl select {
  padding-right: 14px;
  padding-left: 34px;
  background-position: left 12px center, left 17px center;
}
input[type="text"]:hover,
input[type="password"]:hover,
select:hover,
textarea:hover { border-color: var(--gp-outline); }

input:focus,
select:focus,
textarea:focus {
  border-color: var(--gp-blue);
  box-shadow: 0 0 0 3px var(--gp-blue-ring);
  outline: none;
}
input::placeholder, textarea::placeholder { color: var(--gp-ink-3); }
input[type="file"] { min-height: 0; padding: 0; border: 0; font-size: 0.9375rem; }

/* The contenteditable holds USER content, not form furniture — anything the
   form rules above could reach into it is reset here. Kept next to the editor
   styling so the two are read together. */
#content .redactor-in em {
  color: inherit;
  font-size: inherit;
}
/* Editor block spacing. Redactor sets no paragraph margins of its own, so a
   <p> kept the UA default 1em top margin — which at the editor's 15px font
   pushed the first line, and the caret, 15px below the placeholder. Redactor
   generates the placeholder on .redactor-in itself, so it sits at the padding
   box top and the two never lined up: you could only type under the
   placeholder, never over it. Zero the leading margin and space blocks from
   the bottom instead, the way a text editor should. */
#content .redactor-in p,
#content .redactor-in ul,
#content .redactor-in ol,
#content .redactor-in blockquote,
#content .redactor-in pre {
  margin: 0 0 0.7em;
}
#content .redactor-in > :first-child { margin-top: 0; }
#content .redactor-in > :last-child { margin-bottom: 0; }

/* Redactor draws its own chrome; match it to the inputs above. Raised to
   #content specificity because css/redactor.css loads after this file. */
#content .redactor-box.redactor-styles-on {
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r-sm);
  overflow: hidden;
  background: var(--gp-surface);
}
#content .redactor-box.redactor-styles-on:focus-within {
  border-color: var(--gp-blue);
  box-shadow: 0 0 0 3px var(--gp-blue-ring);
}
#content .redactor-box.redactor-styles-on .redactor-toolbar {
  background: var(--gp-page);
  border-bottom: 1px solid var(--gp-line);
  padding: 4px 6px;
  border-radius: 0;
}
#content .redactor-box .redactor-in {
  padding: 12px 14px;
  min-height: 140px;
  font-size: 0.9375rem;
  line-height: 1.6;
}

/* --- Attachments: one control with the editor ---------------------------- */
/* ThreadEntryWidget::render() prints the uploader straight after the textarea,
   so redactor's .redactor-box and .filedrop end up adjacent siblings on BOTH
   the new-ticket form and the ticket reply. That lets the two be drawn as a
   single bounded field: editor on top, attached files, then an attach bar fused
   to the bottom edge. Upstream anticipated this — filedrop.css already squares
   the dropzone's top corners inside a .-redactor-container.
   All rules are raised to #content specificity because css/filedrop.css is
   injected as field media and therefore loads after this file. */

/* The editor gives up its bottom edge to whatever follows. */
#content .redactor-box.redactor-styles-on:has(+ .filedrop) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom: 0;
}
#content .redactor-box + .filedrop {
  padding-bottom: 0;
  border: 1px solid var(--gp-beige);
  border-top: 1px solid var(--gp-line);
  border-radius: 0 0 var(--gp-r-sm) var(--gp-r-sm);
  overflow: hidden;
  background: var(--gp-surface);
}
/* Standalone uploader (a file field of its own) keeps a full box. */
#content .filedrop { padding-bottom: 10px; }

/* The attach bar. The template's text is "Drop files here or choose them"; the
   link is the actionable half, so it reads as the control. */
#content .filedrop .dropzone {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  border: 0;
  border-radius: 0;
  background: var(--gp-page);
  color: var(--gp-ink-3);
  padding: 11px 14px;
  font-size: 0.875rem;
  transition: background-color var(--gp-t-color), color var(--gp-t-color);
}
#content .redactor-box + .filedrop .dropzone { border-radius: 0 0 var(--gp-r-sm) var(--gp-r-sm); }
#content .filedrop .dropzone i.icon-upload {
  flex: none;
  /* The glyph shares the prompt's muted ink rather than taking the brand
     colour: with the link beside it already accented, a coloured icon made
     three colours in one short row. */
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  position: relative;
  top: var(--gp-icon-nudge);
}
/* The prompt is one flex item, not two. FileUploadWidget wraps the sentence
   in .dropzone-prompt so the row's gap separates the glyph from the words and
   nothing else; inside it, "Drop files here or choose them" is ordinary
   inline text and the space before the link is the same space as every other
   space in the line. letter-spacing is pinned to normal for the same reason:
   the row inherits from whatever field it was dropped into, and the prompt
   and its link have to be tracked alike to read as one sentence. */
#content .filedrop .dropzone .dropzone-prompt {
  min-width: 0;
  letter-spacing: normal;
  word-spacing: normal;
}
#content .filedrop .dropzone a.manual {
  color: var(--gp-blue-deep);
  font-weight: 550;
  text-decoration: underline;
  text-decoration-color: var(--gp-brand-edge);
  text-underline-offset: 0.18em;
}
#content .filedrop .dropzone a.manual:hover {
  color: var(--gp-primary);
  text-decoration-color: currentColor;
}
/* Drag state. filedrop.field.js sets an inline
   background-color: rgba(0,0,0,0.3) on dragover and clears the whole style
   attribute on leave/drop — there is no class to hook, so the inline value is
   the selector, and it needs !important to be overridden. */
#content .filedrop .dropzone[style*="background-color"] {
  background-color: var(--gp-brand-tint) !important;
  color: var(--gp-primary);
  box-shadow: inset 0 0 0 2px var(--gp-blue);
}
#content .filedrop .dropzone[style*="background-color"] i.icon-upload,
#content .filedrop .dropzone[style*="background-color"] a.manual { color: var(--gp-primary); }

/* --- The attached-file list ----------------------------------------------
   Every attachment is its own bordered row that REPORTS ITS STATE: in flight
   it takes a blue wash with a progress bar along its top edge and a blue
   "Uploading…" line; landed, it goes white with a green check at the leading
   edge. Upstream drew one 25px line of grey text for both, with a striped
   Bootstrap bar underneath that said "something is happening" without saying
   what.

   THE STATE HOOK. filedrop.field.js drives the whole lifecycle by showing
   and hiding elements — no class is ever set — so the state has to be read
   off what is visible, and no JavaScript changes. That matters more here
   than anywhere else in this stylesheet: filedrop.field.js is the code path
   that carries a user's file to the server.

   The obvious selector is a :has() on the row, and it does not work.
   Chrome matches it correctly but will not repaint it when the inline style
   on the descendant changes — verified against these fixtures, where the row
   rendered its state on load and then froze. Frozen is the worst possible
   outcome for the one element on the page whose job is to change while you
   watch it, so the state is carried two other ways instead:

     the wash, the edge and the bar   .progress itself, stretched over the
                                      row — the script shows and hides it,
                                      so no ancestor selector is involved
     the green check                  .trash ~ .filetype — a sibling
                                      combinator, which DOES invalidate on
                                      an attribute change

   tools/preview/check.mjs mutates the inline display both ways and requires
   the row to follow, so a jQuery upgrade that changes the spelling, or a
   browser that stops invalidating, fails the suite instead of quietly going
   flat.

   Raised to the specificity below because css/filedrop.css is injected as
   field media and therefore loads after this file. */

#content .filedrop .files { padding: 0; border: 0; }
#content .filedrop .files:not(:empty) {
  border: 0;
  border-radius: 0;
  /* Equal on all four sides. Padded 8/8/0 the list sat 8px below the editor
     and flush against the attach bar, so the same 1px rule read as two
     different gaps. */
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#content .filedrop .files:not(:empty) + .dropzone { border-top: 1px solid var(--gp-line); }

/* Upstream pins each row to height:25px, which crushes any thumbnail into a
   sliver. Rows size to their content instead, and become the design's card. */
#content .filedrop .files .file {
  position: relative;      /* the overlay and the bar are pinned to this box */
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 12px;
  height: auto;
  min-height: 0;
  padding: 10px 12px;
  margin: 0;
  overflow: hidden;        /* keeps the overlay inside the rounded corners */
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r-sm);
  transition: border-color var(--gp-t-color);
}
#content .filedrop .files .file:hover { border-color: var(--gp-ink-3); }

/* --- Leading state icon --------------------------------------------------
   addNode() emits an empty <div class="filetype"> and never puts a class on
   it. It is the one stable slot at the head of the row, so it carries the
   state glyph. Blue is the resting state: a row exists because a file is
   arriving. A thumbnail says more than a glyph, so an image that has a
   preview keeps the preview and drops the icon. */
#content .filedrop .files .filetype {
  order: 0;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  font-size: 1rem;
  line-height: 1;
  color: var(--gp-upload);
}
#content .filedrop .files .filetype::before {
  font-family: FontAwesome;
  font-style: normal;
  font-weight: normal;
  content: "\f016";        /* file */
}
#content .filedrop .files .file:has(.preview[src]:not([src=""])) .filetype { display: none; }

/* Landed, in one sibling hop. .trash is the only element that precedes
   .filetype AND tracks the settled state: addNode() prepends it,
   beforeEach() hides it while the bytes move, uploadFinished() shows it, and
   an attachment that was already on the record renders with it visible.
   Written sideways rather than downwards because a sibling combinator
   invalidates on an attribute change and :has() does not.

   Where the widget is not deletable there is no .trash and no row takes the
   check. That is the honest failure: a row that cannot say it has landed is
   better than one that says so before it has. */
#content .filedrop .files .file .trash:not([style*="display: none"]):not([style*="display:none"]) ~ .filetype {
  color: var(--gp-done);
}
#content .filedrop .files .file .trash:not([style*="display: none"]):not([style*="display:none"]) ~ .filetype::before {
  content: "\f058";        /* check-circle */
}

/* A picture should look like the picture. Was max 100x25. */
#content .filedrop .preview {
  order: 1;
  width: 42px;
  height: 42px;
  max-width: 42px;
  max-height: 42px;
  padding: 0;
  float: none;
  flex: none;
  object-fit: cover;
  border-radius: var(--gp-r-sm);
  background: var(--gp-surface-2);
}
/* Non-image uploads leave the <img> with no src; collapse it rather than
   showing a broken-image box. */
#content .filedrop .preview:not([src]),
#content .filedrop .preview[src=""] { display: none; }

#content .filedrop .files .file .filename {
  order: 2;
  flex: 1 1 12rem;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 10px;
  padding: 0;
  font-size: 0.9375rem;
  color: var(--gp-ink);
  overflow: hidden;
}
#content .filedrop .files .file .filename > a,
#content .filedrop .files .file .filename > span:first-child {
  flex: 1 1 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#content .filedrop .files .file .filesize {
  margin: 0;
  flex: none;
  font-family: var(--gp-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--gp-ink-3);
}
/* The transfer rate is the second line while a file is moving, and it is the
   only text on the row that is blue. filedrop.field.js hides it on finish, so
   it says nothing once there is nothing to say. */
#content .filedrop .files .file .upload-rate {
  margin: 0;
  float: none;
  flex: none;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--gp-upload);
}
#content .filedrop .files .file .upload-rate:not(:empty)::before {
  content: "Uploading… ";
}

/* Remove/cancel controls sit at the far end; .pull-right's float is inert in
   a flex row, so they are pushed there instead. They must outrank the
   overlay, which covers the whole row. */
#content .filedrop .files .file .trash,
#content .filedrop .files .file .cancel {
  order: 3;
  position: relative;
  z-index: 1;
  float: none;
  margin-left: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: var(--gp-ink-3);
  cursor: pointer;
  transition: background-color var(--gp-t-color), color var(--gp-t-color);
}
#content .filedrop .files .file .trash:hover,
#content .filedrop .files .file .cancel:hover {
  background: var(--gp-err-bg);
  color: var(--gp-err);
}
/* Everything the overlay sits over has to sit over it back, or the wash
   washes the text too. */
#content .filedrop .files .file .filetype,
#content .filedrop .files .file .preview,
#content .filedrop .files .file .filename { position: relative; z-index: 1; }

/* --- In flight -----------------------------------------------------------
   .progress is stretched over the whole row: it paints the wash and the blue
   edge, and the bar runs along its top. The bar reads as the row filling up,
   which is what it measures. filedrop.field.js writes the bar's width as an
   inline percentage; against a full-row overlay that percentage is a
   percentage of the row, which is what it was always meant to mean. */
#content .filedrop .files .file .progress {
  position: absolute;
  inset: 0;
  height: auto;
  margin: 0;
  background: var(--gp-upload-tint);
  box-shadow: inset 0 0 0 1px var(--gp-upload-edge);
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;   /* the cancel button is underneath */
}
#content .filedrop .files .file .progress-bar {
  position: absolute;
  top: 0;
  left: 0;
  height: 3px;
  background-color: var(--gp-upload);
  background-image: none;   /* upstream's candy stripes */
  box-shadow: none;
  animation: none;
  transition: width 140ms linear;
}

/* Queued, not uploading. filedrop.field.js parks a file at width:100% with
   .progress-bar-striped.active until a transfer slot frees, and strips both
   classes the moment bytes move. With the candy stripes gone, a queued file
   would otherwise read as a FINISHED one — a full blue bar — which is the
   most misleading thing the row could say. It gets an indeterminate sweep
   instead: motion means waiting, a filling bar means progress. */
#content .filedrop .files .file .progress-bar.progress-bar-striped {
  background-image: linear-gradient(90deg,
    var(--gp-upload-tint) 0%,
    var(--gp-upload) 45%,
    var(--gp-upload) 55%,
    var(--gp-upload-tint) 100%);
  background-size: 240% 100%;
  background-color: transparent;
  animation: gp-upload-wait 1.1s linear infinite;
}
@keyframes gp-upload-wait {
  from { background-position: 120% 0; }
  to   { background-position: -120% 0; }
}
@media (prefers-reduced-motion: reduce) {
  #content .filedrop .files .file .progress-bar.progress-bar-striped { animation: none; }
}

/* --- The whole editor is the drop target --------------------------------
   filedrop.field.js widens the surface from the attach bar to the entire
   editor and adds .filedrop-over while a file is over it (see
   extendDropSurface). The bar alone was ~40px at the foot of a control ten
   times that, so the obvious place to drop a file was the one place that did
   not take one.

   The cue has to be unmistakable and must not move anything: an outline and
   a wash, both drawn inside the box, so nothing reflows under the pointer
   mid-drag. It borrows the uploader's own blue — a file is about to be in
   flight, which is exactly what that colour means everywhere else here. */
#content .redactor-box.filedrop-over {
  border-color: var(--gp-upload) !important;
  box-shadow: inset 0 0 0 1px var(--gp-upload),
              inset 0 0 0 9999px var(--gp-upload-tint);
}
#content .redactor-box.filedrop-over .redactor-toolbar {
  background: transparent;
}

#content .filedrop.filedrop-over .dropzone {
  border-color: var(--gp-upload);
  border-style: solid;
  background: var(--gp-upload-tint);
  color: var(--gp-upload);
}




/* Field furniture inside forms */
label.required, span.required { font-weight: 600; }
.form-header h3 { margin-bottom: 2px; }
.form-header > div { color: var(--gp-ink-2); font-size: 0.9375rem; }

.uploads { display: inline-block; padding-right: 20px; }
.uploads label { padding: 3px 10px 3px 3px; width: auto !important; }

.freetext-files {
  padding: 14px 16px;
  margin-top: 12px;
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r-sm);
  background: var(--gp-surface);
}
.freetext-files .file {
  margin-right: 10px;
  display: inline-block;
  width: 48%;
  padding-top: 0.2em;
}
.freetext-files .title { margin-bottom: 0.5em; }

/* =========================================================================
   6. Alerts, notices, and errors
   ========================================================================= */

/* Auto height (upstream clipped at 16px), Font Awesome icons, semantic
   left edge — palette harmonised with the brand: success speaks sage,
   info speaks the staff navy. */
#msg_notice, #msg_warning, #msg_error, #msg_info,
.notice_bar, .warning_bar, .error_bar, .warning-banner {
  position: relative;
  display: block;
  height: auto;
  min-height: 0;
  line-height: 1.5;
  margin: 0 0 16px;
  padding: 12px 16px 12px 42px;
  font-size: 0.9375rem;
  border: 1px solid;
  border-left-width: 3px;
  border-radius: var(--gp-r-sm);
  background-image: none;
}
#msg_notice::before, #msg_warning::before, #msg_error::before,
#msg_info::before, .notice_bar::before, .warning_bar::before,
.error_bar::before, .warning-banner::before {
  position: absolute;
  left: 15px;
  top: 13px;
  font-family: FontAwesome;
  font-style: normal;
  font-weight: normal;
  font-size: 15px;
  line-height: 1.2;
}
#msg_notice, .notice_bar {
  color: var(--gp-ok);
  background-color: var(--gp-ok-bg);
  border-color: var(--gp-ok-edge);
  border-left-color: var(--gp-ok);
}
#msg_notice::before, .notice_bar::before { content: "\f058"; }  /* ok-sign */

#msg_warning, .warning_bar, .warning-banner {
  color: var(--gp-warn);
  background-color: var(--gp-warn-bg);
  border-color: var(--gp-warn-edge);
  border-left-color: var(--gp-warn);
}
#msg_warning::before, .warning_bar::before,
.warning-banner::before { content: "\f071"; }                   /* warning */

#msg_error, .error_bar {
  color: var(--gp-err);
  background-color: var(--gp-err-bg);
  border-color: var(--gp-err-edge);
  border-left-color: var(--gp-err);
}
#msg_error::before, .error_bar::before { content: "\f06a"; }     /* excl.   */

#msg_info {
  color: var(--gp-info);
  background-color: var(--gp-info-bg);
  border-color: var(--gp-info-edge);
  border-left-color: var(--gp-info);
}
/* view.inc.php puts its own icon-compass in #msg_info; don't double up. */
#msg_info::before { content: none; }
#msg_info { padding-left: 16px; }
#msg_info .icon-2x { color: var(--gp-info); margin-right: 12px; }
#msg_notice a, #msg_warning a, #msg_error a, #msg_info a { color: inherit; }

/* The banners the header emits sit above the content column. */
.notice_bar, .warning_bar, .error_bar { margin: 16px 0 0; }

.warning {
  background: var(--gp-warn-bg);
  border: 1px solid var(--gp-warn-edge);
  border-radius: var(--gp-r-sm);
  padding: 10px 14px;
  font-style: normal;
  color: var(--gp-ink-2);
}
.warning strong {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.8125rem;
  color: var(--gp-warn);
  font-style: normal;
}

/* Inline field errors. font.error is emitted by the ticket form. */
.error, font.error { color: var(--gp-err); font-size: 0.875rem; }
.error input { border-color: var(--gp-err); }
div.error { margin-top: 4px; }

/* =========================================================================
   7. Landing page
   ========================================================================= */

/* Stitch's dashboard: a left-aligned 48px headline over a lede, then one
   prominent search field spanning the column, then the support categories as
   a three-up bento of cards, then a card of recent items. The portal's
   landing page has all four of those pieces already — they are just in a
   different order and a different shape — so nothing here is invented:

     headline + lede   the configurable landing page's own body
     search            the knowledgebase search form
     bento             Category::getFeatured(), which index.php already
                       renders as .featured-category blocks
     the card          sidebar.tmpl.php's featured questions and resources

   The DOM order (sidebar first, then main-content, then the featured-articles
   block) is untouched — the sidebar's CTA conditionals stay in
   sidebar.tmpl.php — and flex `order` does the arranging. */
#landing_page {
  overflow: visible;
  display: flex;
  flex-direction: column;
}
.main-content { width: auto; }
#landing_page .main-content {
  order: 1;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  text-align: left;
  padding: 0;
}
.hero-eyebrow {
  order: 1;
  font-size: var(--gp-t-lsm);
  line-height: calc(var(--gp-lh-lsm) / var(--gp-t-lsm));
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gp-ink-3);
  margin-bottom: 12px;
}
#landing_page .thread-body {
  order: 2;
  padding: 0;
  background: transparent;
  max-width: none;
}
/* The measure applies to the copy, not to the headline. --gp-measure is
   counted in ch of the BODY size, so holding a 48px h1 to it wrapped the
   greeting after three words. */
#landing_page .thread-body > :not(h1):not(h2) { max-width: var(--gp-measure); }
/* The one place the type scale reaches headline-xl. Stitch sets the greeting
   at 48/56 with -0.02em and lets everything below it be small — the page has
   exactly one loud thing on it. */
#landing_page .thread-body h1 {
  font-size: clamp(var(--gp-t-hlgm), 3.4vw + 0.9rem, var(--gp-t-hxl));
  line-height: 1.16;
  letter-spacing: -0.02em;
  margin-bottom: 10px;
}
#landing_page .thread-body p {
  margin-left: 0;
  margin-right: 0;
  max-width: var(--gp-measure-lede);
  font-size: var(--gp-t-blg);
  line-height: calc(var(--gp-lh-blg) / var(--gp-t-blg));
  color: var(--gp-ink-2);
}
#landing_page .search-form {
  order: 3;
  width: 100%;
  padding: 32px 0 0;
  display: block;
}
#landing_page .search-form form { width: 100%; }

/* The hero search is the design's single most prominent control: full width,
   tall, softly shadowed, and — unlike every other field on the portal —
   drawn at the card radius rather than the input radius, because at this
   size it reads as a surface. */
#landing_page .search-form .search {
  flex: 1 1 260px;
  width: auto;
  min-height: 60px;
  padding: 16px 20px;
  font-size: var(--gp-t-blg);
  border-radius: var(--gp-r);
  box-shadow: 0 2px 12px rgba(20, 24, 28, 0.04);
}
#landing_page .search-form .button { min-height: 60px; padding: 16px 28px; }

/* The sidebar block becomes the CTA row + the featured-questions card. */
#landing_page .sidebar {
  order: 2;
  float: none !important;
  width: 100%;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}
#landing_page .front-page-button {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 12px;
  margin: 28px 0 44px;
}
#landing_page .front-page-button p { margin: 0; }
/* sidebar.tmpl.php puts an inline display:block on the CTA anchors. */
#landing_page .front-page-button .button {
  display: inline-flex !important;
  width: auto;
  min-width: 200px;
}
/* Stitch's recent-items card: white, hairline, 12px radius — the same card
   every other page of the portal uses, so the landing page is not a
   one-off. */
#landing_page .sidebar .content {
  width: 100%;
  padding: var(--gp-card-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r);
  display: flex;
  flex-wrap: wrap;
  gap: 24px 56px;
  text-align: left;
}
#landing_page .sidebar .content section { flex: 1 1 240px; min-width: 0; }
/* With the knowledgebase off and no "other resources" pages, sidebar.tmpl.php
   emits <div class="content"></div> with nothing in it. The generic
   `.sidebar .content:empty { display: none }` guard further down could not win
   against the `display: flex` above it — id beats pseudo-class — so the empty
   card still painted as a box where the featured questions would be. */
#landing_page .sidebar .content:empty { display: none; }
/* Same scenario, the other half: index.php always emits the wrapper for the
   featured-articles block, and with the knowledgebase off it holds nothing but
   whitespace — leaving this rule's top padding as dead space. Keyed on "has no
   heading and no category" so it also collapses when the KB is on but nothing
   is featured. */
#landing_page > .clear + div:not(:has(h1, .featured-category)) {
  display: none;
}
#landing_page .sidebar section + section {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

#landing_page > .clear { order: 4; }
/* index.php separates the featured-articles block with a .clear plus two
   <br>s; the card above already provides the section break. */
#landing_page > .clear + div { order: 5; border-top: 0; padding-top: 8px; margin-top: 0; }
#landing_page > .clear + div > br { display: none; }
#landing_page > .clear + div > h1 {
  font-size: var(--gp-t-hmd);
  line-height: calc(var(--gp-lh-hmd) / var(--gp-t-hmd));
  margin-bottom: 16px;
}
#landing_page h1,
#landing_page h2,
#landing_page h3 { margin-bottom: 12px; }

.search-form { padding: 0 0 24px; }
.search-form form { display: flex; gap: 10px; flex-wrap: wrap; }
.searchbar .search,
.search-form .search {
  display: inline-block;
  width: 400px;
  max-width: 100%;
  flex: 1 1 240px;
  border-radius: var(--gp-r-xs);
  border: 1px solid var(--gp-beige);
  padding: 10px 16px;
  box-shadow: none;
  background: var(--gp-surface);
}
.searchbar .search {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 10px;
  flex: none;
}
.search-form .button { flex: none; }

/* Upstream #new_ticket / #check_status blocks — no current template emits
   them, but a customised landing page may; keep them harmless. */
#landing_page #new_ticket,
#landing_page #check_status {
  width: 295px;
  max-width: 100%;
  padding-left: 75px;
  margin-top: 40px;
}
#landing_page #new_ticket {
  background: url('../images/new_ticket_icon.png') top left no-repeat;
}
#landing_page #check_status {
  background: url('../images/check_status_icon.png') top left no-repeat;
}

/* --- Featured KB categories: Stitch's bento -------------------------------
   index.php emits each featured category as an inline-block half-column of
   loose text. The design draws them as equal cards in a three-up grid, each
   led by a tinted icon tile, that lift their border to the interactive blue
   on hover. Grid rather than the old 49% inline-block: cards in a row have
   to be the same height, and inline-block cannot make them so. */
#landing_page > .clear + div {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--gp-card-gap);
  align-items: stretch;
}
/* The block's own heading spans the grid rather than becoming a cell. */
#landing_page > .clear + div > h1 { grid-column: 1 / -1; }
.featured-category {
  margin: 0;
  width: auto;
  display: flex;
  flex-direction: column;
  vertical-align: top;
  padding: var(--gp-inset-pad) var(--gp-inset-pad) 8px;
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r);
  transition: border-color var(--gp-t-color), box-shadow var(--gp-t-color);
}
.featured-category:hover {
  border-color: var(--gp-blue);
  box-shadow: 0 4px 24px rgba(90, 120, 168, 0.1);
}
/* The folder glyph becomes the design's icon tile: a 48px rounded square in
   the sage fixed tint, with the glyph centred in it. It is the only place on
   the landing page colour appears, and the categories are what it points at. */
.featured-category i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  margin: 0 0 14px;
  color: var(--gp-brand);
  background: var(--gp-brand-chip);
  border-radius: var(--gp-r-sm);
  text-shadow: none;
  font-size: 1.15rem !important;
  line-height: 1;
  transition: transform var(--gp-t-move);
}
.featured-category:hover i { transform: scale(1.1); }
.category-name {
  display: block;
  font-weight: 600;
  font-size: var(--gp-t-hsm);
  line-height: calc(var(--gp-lh-hsm) / var(--gp-t-hsm));
  letter-spacing: -0.005em;
  color: var(--gp-primary);
}
.article-headline {
  margin-left: 0;
  padding: 12px 0;
  border-top: 1px solid var(--gp-line);
}
.article-headline:first-of-type { margin-top: 8px; }
.article-title {
  font-weight: 500;
  font-size: var(--gp-t-bsm);
  line-height: calc(var(--gp-lh-bsm) / var(--gp-t-bsm));
}
/* Upstream clipped the teaser with a hard height:3em, which cut text
   mid-word with no ellipsis. Clamp it properly instead. */
.article-teaser {
  font-size: var(--gp-t-bsm);
  line-height: 1.5;
  height: auto;
  max-height: none;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--gp-ink-2);
  margin-top: 2px;
}
.faq-content .article-title {
  font-size: clamp(var(--gp-t-hlgm), 2.2vw + 1.15rem, var(--gp-t-hlg));
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
  margin-top: 4px;
}

/* =========================================================================
   8. Sidebar
   ========================================================================= */

.sidebar {
  margin-bottom: 24px;
  margin-left: 36px;
  width: 284px;
}
/* On the knowledgebase pages the sidebar sits inside .span4, so it should fill
   that column instead of holding a fixed width and leaving a gap beside it. */
.span4 .sidebar {
  width: auto;
  margin-left: 0;
}
.front-page-button { display: grid; gap: 10px; margin-bottom: 22px; }
.front-page-button p { margin: 0; }
.front-page-button .button { width: 100%; max-width: none; }

.sidebar .content {
  padding: var(--gp-inset-pad);
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r);
  background: var(--gp-surface);
}
.sidebar .content:empty { display: none; }
.sidebar section .header { margin-bottom: 8px; }
.sidebar section strong {
  display: block;
  margin-bottom: 8px;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gp-ink-3);
}
.sidebar section + section {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--gp-line-soft);
}
.sidebar section > div { padding: 4px 0; line-height: 1.45; }
.sidebar .content a { color: var(--gp-blue-deep); }
.sidebar select { width: 100%; max-width: 100%; }

/* =========================================================================
   9. Knowledgebase
   ========================================================================= */

#breadcrumbs {
  color: var(--gp-ink-3);
  font-size: 0.875rem;
  margin-bottom: 16px;
}
#breadcrumbs a { color: var(--gp-ink-2); }

#kb { margin: 4px 0 0; padding: 0; overflow: hidden; }
#kb > li {
  position: relative;
  padding: 22px 0;
  height: auto !important;
  overflow: hidden;
  margin: 0;
  background: none;
  border-bottom: 1px solid var(--gp-line);
  display: block;
  list-style: none;
}
#kb > li h4 { margin-bottom: 2px; }
#kb > li h4 a { font-size: 1.1rem; font-weight: 500; }
#kb > li h4 span { color: var(--gp-ink-3); }
/* Was a 32px folder PNG; now the Font Awesome folder already on the page. */
#kb > li > i {
  display: block;
  width: 34px;
  height: 34px;
  float: left;
  margin-right: 6px;
  background: var(--gp-brand-tint);
  border-radius: 50%;
  color: var(--gp-brand);
  font-size: 15px;
  line-height: 34px;
  text-align: center;
}
#kb > li > i::before { font-family: FontAwesome; content: "\f07b"; }
.rtl #kb > li > i { float: right; margin-right: 0; margin-left: 6px; }
#kb > li > div { margin-left: 48px; }
#kb .faded { color: var(--gp-ink-2); font-size: 0.9375rem; }

.popular-faq { padding: 4px 0 4px 0; line-height: 1.45; }
.popular-faq i { color: var(--gp-ink-3); margin-right: 6px; }

#faq { clear: both; margin: 0; padding: 0; }
#faq ol {
  font-size: 1rem;
  margin: 12px 0 0;
  padding-left: 0;
  border-top: 1px solid var(--gp-line);
}
#faq ol li { list-style: none; margin: 0; padding: 0; color: var(--gp-ink-3); }
#faq ol li a {
  display: block;
  padding: 13px 8px 13px 26px;
  height: auto !important;
  overflow: hidden;
  margin: 0;
  border-bottom: 1px solid var(--gp-line);
  line-height: 1.45;
  background: none;
  position: relative;
  transition: background-color var(--gp-t-color);
}
#faq ol li a::before {
  font-family: FontAwesome;
  content: "\f016";               /* file-alt */
  position: absolute;
  left: 4px;
  top: 13px;
  color: var(--gp-ink-3);
  font-size: 0.9em;
}
#faq ol li a:hover { background-color: var(--gp-brand-tint); text-decoration: none; }
#faq .article-meta { padding: 8px; background: var(--gp-surface-2); }

.faq-content { margin-top: 4px; }
.faq-content .thread-body { background: transparent; }

/* Legacy KB search ids. No current template emits them; harmless to keep. */
#kb-search { padding: 10px 0; overflow: hidden; }
#kb-search div { clear: both; overflow: hidden; padding-top: 5px; }
#kb-search #query, #kb-search #cid { width: 200px; margin-right: 8px; }
#kb-search #topic-id { width: 410px; max-width: 100%; }

/* =========================================================================
   10. Ticket form  (open.inc.php, edit.inc.php, register, profile)
   ========================================================================= */

/* Centred form column, matching the sign-in page. Scoped to "#ticketForm that
   contains a table" so it hits open.inc.php and edit.inc.php but not the bare
   #ticketForm the alerts specimen uses. The fields stay left-aligned inside the
   card — only the column and the page heading are centred. */
/* The head shares the card's column, so the page is one measure from the
   heading down rather than a full-bleed title above a narrow card. The lede
   additionally keeps a reading measure, which on this rung is the narrower of
   the two. */
#content:has(#ticketForm > table) > h1,
#content:has(#ticketForm > table) > p {
  display: block;
  text-align: left;
  margin-left: 0;
  margin-right: 0;
  max-width: var(--gp-w-form);
}
#content:has(#ticketForm > table) > h1 { margin-bottom: 10px; }
#content:has(#ticketForm > table) > p {
  max-width: min(var(--gp-w-form), var(--gp-measure-lede));
}
#ticketForm:has(> table) {
  width: 100%;
  /* Form rung: the editor toolbar and its attachment bar are the widest things
     that must fit, and they cramp at the auth rung below. */
  max-width: var(--gp-w-form);
  margin: var(--gp-card-gap) 0 0;
  padding: var(--gp-card-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r);
}
/* The template opens with <td colspan=2><hr>, which is now redundant against
   the card's own top edge. */
#ticketForm:has(> table) > table > tbody:first-of-type > tr:first-child hr { display: none; }
#ticketForm:has(> table) > hr:last-of-type { display: none; }

#ticketForm > table,
#clientLogin table,
table.padded {
  width: 100%;
  max-width: 100%;
}
/* Upstream set table-layout:fixed here, but the first row of every client
   form is a <td colspan="2"><hr>, so fixed layout had no column widths to
   read and split the table 50/50 — leaving a half-width label column.
   Auto layout plus a shrink-to-fit label column instead. */
#ticketForm > table { table-layout: auto; }
#ticketForm > table > tbody > tr > td { padding: 4px 0; }
#ticketForm > table > tbody > tr > td:first-child:not([colspan]) {
  width: 1%;
  white-space: nowrap;
  padding-right: 28px;
  color: var(--gp-ink-2);
}
#ticketForm > table > tbody > tr > td[colspan] { width: auto; white-space: normal; }

#ticketForm div.clear,
#clientLogin div.clear { clear: both; padding: 3px 0; overflow: hidden; }

#ticketForm div label,
#clientLogin div label { display: block; }
#ticketForm div label.required,
#clientLogin div label.required { text-align: left; }
#ticketForm div input,
#clientLogin div input,
#ticketForm div textarea,
#clientLogin div textarea { width: auto; display: block; }
#ticketForm div input[type="file"],
#clientLogin div input[type="file"] { border: 0; }
#ticketForm div select,
#clientLogin div select { display: block; float: none; }

#ticketForm td textarea,
#clientLogin td textarea,
#ticketForm div textarea,
#clientLogin div textarea { width: 100%; max-width: 640px; }

#ticketForm td em,
#clientLogin td em,
#ticketForm div em,
#clientLogin div em { color: var(--gp-ink-3); font-size: 0.875rem; }

#ticketForm label > span:first-child {
  display: inline-block;
  margin-bottom: 6px;
  font-weight: 500;
  color: var(--gp-ink);
}
#ticketForm label > span:first-child.required { font-weight: 600; }

#ticketForm div div.captchaRow,
#clientLogin div div.captchaRow { line-height: normal; }
#ticketForm div div.captchaRow input,
#clientLogin div div.captchaRow input { position: static; top: 0; }
tr.captchaRow td { padding-top: 14px; }
#ticketForm td .captcha,
#clientLogin td .captcha,
#ticketForm div .captcha,
#clientLogin div .captcha {
  width: 88px;
  height: 31px;
  background: var(--gp-ink);
  border-radius: 4px;
  overflow: hidden;
  display: inline-block;
  float: none;
  vertical-align: middle;
  margin-right: 12px;
}
#ticketForm td label.inline,
#clientLogin td label.inline,
#ticketForm div label.inline,
#clientLogin div label.inline { width: auto; padding: 0 10px; display: inline; }

#ticketForm div.error input,
#clientLogin div.error input { border-color: var(--gp-err); }
#ticketForm div.error label,
#clientLogin div.error label { color: var(--gp-err); }

/* Section dividers inside the stacked form tables. */
#ticketForm .form-header,
table.padded h3 { margin-top: 4px; }
table.padded tr > td,
table.padded tr > th { height: auto; padding: 6px 12px 6px 0; }
table.padded h3 { font-size: 1.1rem; }

/* --- Account pages: register.inc.php + profile.inc.php ------------------- */
/* These two mix two row shapes in one table: the user form's rows put the label
   ABOVE the field (one colspan="2" cell holding <label><span>Name</span><br>
   input</label>), while the hand-written Time Zone / Preferred Language /
   password rows are two columns with the label BESIDE the field — and their
   inputs are size="18" against the form's size="40". The result read as two
   different forms stapled together.

   Every row is stacked to the label-above shape, and every control gets one
   width. Done by dropping the table display roles: with any table box left in
   the chain the two-column rows keep a shared column axis, so the label cell
   would still size itself against every other first cell in the table. */
#content:has(table.padded) > h1,
#content:has(table.padded) > p {
  display: block;
  text-align: left;
  margin-left: 0;
  margin-right: 0;
  max-width: var(--gp-w-form);
}
#content:has(table.padded) > h1 { margin-bottom: 10px; }
#content:has(table.padded) > p {
  max-width: min(var(--gp-w-form), var(--gp-measure-lede));
}

/* Profile lands on the form rung, not the page rung it used to occupy: its
   widest row is the time zone picker with Auto Detect beside it (see the
   .action-button rules further down), which needs about 34rem and therefore
   rounds up to 44. At the page rung this card was 1088px wrapping 416px
   fields — 670px of empty white, and the one surface that had escaped the
   scale entirely. */
/* Matched on the form that CONTAINS the padded table, not on any form that
   happens to share a page with one — the alerts specimen has both and was
   being given a form-rung width it has no business with. */
#content > form:has(table.padded) {
  width: 100%;
  max-width: var(--gp-w-form);
  margin: var(--gp-card-gap) 0 0;
  padding: var(--gp-card-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r);
}
/* Sign-up sits a rung lower, as the sign-in card's twin: it hides Auto Detect
   (below), so a short text input is the widest thing it has to hold. Keyed on
   the form action because profile.inc.php shares this markup. The head follows
   the card down, or the page would change measure halfway. */
/* Sign-up is centred with the sign-in card it is reached from, not aligned to
   the rail like the pages you use once you are inside. The three doors into
   the portal — sign in, check status, sign up — are one flow and have to look
   like one; profile.inc.php shares this markup and is NOT one of them, which
   is why the centring is keyed to the form action rather than to the table. */
#content > form[action="account.php"]:has(table.padded) {
  max-width: var(--gp-w-auth);
  margin-left: auto;
  margin-right: auto;
}
#content:has(form[action="account.php"]) > h1,
#content:has(form[action="account.php"]) > p {
  max-width: var(--gp-w-auth);
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

table.padded,
table.padded > tbody,
table.padded > tbody > tr {
  display: block;
  width: 100%;
  max-width: 100%;
}
table.padded > tbody > tr > td {
  display: block;
  width: auto !important;
  height: auto;
  padding: 0;
}
/* Rows carrying a field get the same rhythm as the user form's rows. */
table.padded > tbody > tr { padding-top: 14px; }
table.padded > tbody > tr:first-child { padding-top: 0; }

/* The label cell of a two-column row becomes the heading above its field,
   matched to the <span> the user form emits. :not([colspan]) keeps the section
   -break rows out of it — including the ones whose upstream `colspan=2"` typo
   still registers as an attribute. */
table.padded > tbody > tr > td:first-child:not([colspan]) {
  margin-bottom: 6px;
  font-weight: 500;
  font-size: 0.9375rem;
  color: var(--gp-ink);
}
/* One weight for every label in these forms, so a required field is marked by
   its asterisk rather than by being heavier than its neighbours. */
table.padded label > span:first-child,
table.padded label > span:first-child.required {
  display: inline-block;
  margin-bottom: 6px;
  font-weight: 500;
  font-size: 0.9375rem;
  color: var(--gp-ink);
}

/* One field width throughout — the passwords were half the length of the rest. */
table.padded input[type="text"],
table.padded input[type="password"],
table.padded input[type="email"],
table.padded input[type="url"],
table.padded select,
table.padded .select2-container {
  width: 100% !important;
  max-width: 26rem;
}
/* Phone + Ext share a line. Now that fields fill the card, the Ext box would
   otherwise be pushed onto its own row, so the label becomes a flex line with
   the caption on top and the two inputs beneath it. */
table.padded label:has(input[type="tel"]) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 8px;
}
table.padded label:has(input[type="tel"]) > span:first-child { flex-basis: 100%; }
table.padded label:has(input[type="tel"]) > br { display: none; }
table.padded input[type="tel"] {
  flex: 1 1 8rem;
  width: auto;
  max-width: 18rem;
  min-width: 0;
}
table.padded input[type="tel"] ~ input[type="text"] {
  flex: 0 0 5.5rem;
  width: 5.5rem !important;
  max-width: 5.5rem;
}

/* Time Zone: the picker matches the other fields, so Auto Detect fits beside it. */
table.padded > tbody > tr > td:has(> .select2-container) > .action-button {
  display: inline-flex;
  margin: 0 0 0 8px;
  vertical-align: middle;
}
/* ...but not on sign-up, where the location detection is gone entirely. The
   button comes from include/staff/templates/timezone.tmpl.php, which the
   profile page and the whole staff panel also include, so it cannot be removed
   at the source — it is dropped here for this one form. register.inc.php's
   jstz auto-detect script was deleted outright in the same change, so nothing
   on sign-up reads the visitor's location any more; the field is a plain
   manual picker. */
#content:has(table.padded) > form[action="account.php"] .action-button {
  display: none;
}
/* select2 draws a 28px-tall box with its own border, against 44px inputs either
   side of it. Matched to the fields so the picker is the same height and shape
   as everything else in the form. */
table.padded .select2-container--default .select2-selection--single {
  height: 44px;
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r-sm);
  background: var(--gp-surface);
}
table.padded .select2-container--default .select2-selection--single .select2-selection__rendered {
  line-height: 42px;
  padding-left: 13px;
  color: var(--gp-ink);
}
table.padded .select2-container--default .select2-selection--single .select2-selection__arrow {
  height: 42px;
}

/* Section breaks. The user form emits <hr> + .form-header; the hand-written
   rows emit <div><hr><h3>. Given one spacing so they read as one sequence. */
table.padded h3 {
  font-size: 1.0625rem;
  font-weight: 500;
  margin: 0;
}
table.padded hr { margin: 22px 0 14px; }
/* The first section break lands at the very top of the card, where the card's
   own edge already separates things. Its rule was redundant and its margins are
   what made the top padding read deeper than the sides. */
table.padded > tbody > tr:first-child hr { display: none; }
table.padded .form-header { margin-bottom: 0 !important; }
table.padded .form-header > div:empty { display: none; }

/* Inline field errors sit after the control; empty ones must not reserve space. */
table.padded .error { font-size: 0.875rem; }
table.padded div.error:empty { display: none; }

/* The action row under the table. */
#content:has(table.padded) > form > hr { display: none; }
#content:has(table.padded) > form > p:last-child {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  align-items: center;
  margin: 24px 0 0;
}

/* The ticket-form variant of #ticketTable (upstream kept both). */
#ticketTable th.required,
#ticketTable td.required { font-weight: 600; text-align: left; }

/* =========================================================================
   11. Ticket list  (tickets.inc.php)
   ========================================================================= */

/* Stitch's filter toolbar: one hairline-bounded strip of controls above the
   list, at the panel radius rather than the card radius — it is furniture,
   not content. */
.search.well {
  padding: var(--gp-inset-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r);
  margin: 0 0 var(--gp-card-gap);
}
#ticketSearchForm { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; float: none; padding: 0; }
#ticketSearchForm input[type="text"] { flex: 1 1 220px; min-width: 0; padding-left: 14px; }
#ticketSearchForm .pull-right {
  float: none;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--gp-ink-2);
}
.search.well + h1 { margin-top: 0 !important; }

/* Page heading rows that carry a floated action cluster. */
#content > h1,
#ticketInfo h1 {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
}
/* tickets.inc.php wraps its whole heading in a refresh link, so without this
   the page title takes the link colour and reads as an action. */
#content > h1 > a,
#ticketInfo h1 > a { color: inherit; }
#content > h1 > a:hover,
#ticketInfo h1 > a:hover { color: inherit; text-decoration: none; }
#content > h1 > .pull-right,
#ticketInfo h1 > .pull-right { float: none; margin-left: auto; }

/* Open / Closed become Stitch's filter pills: a ringed pill each, the one
   you are looking at filled with the near-black. The template separates them
   with a literal "|" inside .states, which no selector can reach — it is
   hidden by collapsing the container's font-size and restoring it on the
   pills themselves, the only way to drop a bare text node in CSS. */
.states { display: inline-flex; align-items: center; }
/* The literal "|" the template prints between the two links sits inside the
   <small>, so the collapse has to happen there — a bare text node cannot be
   selected, and zeroing the font-size of its parent is the only way to drop
   one. Each pill restores the size on itself. */
.states small { display: inline-flex; align-items: center; gap: 8px; font-size: 0; }
.states i { display: none; }
.state {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 7px 16px;
  font-size: var(--gp-t-lmd);
  font-weight: 500;
  color: var(--gp-ink-2);
  border: 1px solid var(--gp-outline);
  border-radius: var(--gp-r-pill);
  transition: background-color var(--gp-t-color), color var(--gp-t-color),
              border-color var(--gp-t-color);
}
.state:hover {
  background: var(--gp-surface-1);
  color: var(--gp-primary);
  text-decoration: none;
}
.active.state {
  font-weight: 600;
  color: #fff;
  background: var(--gp-primary);
  border-color: var(--gp-primary);
  text-decoration: none;
}
.active.state:hover { color: #fff; background: var(--gp-primary-strong); }
i.refresh { color: var(--gp-ink-3); font-size: 0.75em; vertical-align: middle; }
#content > h1 a:hover i.refresh { color: var(--gp-primary); }

/* --- The table ----------------------------------------------------------- */
/* Stitch draws the list as one card: a hairline box at the card radius with
   the column heads on the low container surface, rows divided by hairlines,
   and nothing bleeding past the rounded corners. The table is the card —
   border-collapse:separate is what lets a <table> hold a radius at all, and
   the row rules move onto the cells because a collapsed border would be
   clipped by the corner. */
#ticketTable {
  width: 100%;
  margin-top: 4px;
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r);
  border-collapse: separate;
  border-spacing: 0;
  background: var(--gp-surface);
}
/* The corners are rounded on the corner CELLS rather than by clipping the
   table: overflow:hidden on a <table> also clips its <caption>, which the
   template renders above the head and which then lost its first letter. */
#ticketTable thead th:first-child { border-top-left-radius: var(--gp-r); }
#ticketTable thead th:last-child  { border-top-right-radius: var(--gp-r); }
#ticketTable caption {
  padding: 0 0 12px;
  text-align: left;
  color: var(--gp-ink-3);
  background: none;
  border: 0;
  caption-side: top;
}
#ticketTable thead th {
  height: auto;
  line-height: 1.4;
  background: var(--gp-surface-1);
  border: 0;
  border-bottom: 1px solid var(--gp-beige);
  padding: 14px 16px;
  white-space: normal;
  vertical-align: middle;
}
#ticketTable thead th:first-child { padding-left: var(--gp-inset-pad); }
#ticketTable thead th:last-child { padding-right: var(--gp-inset-pad); }
#ticketTable thead th a { color: var(--gp-ink-3); }
#ticketTable thead th a:hover { color: var(--gp-primary); text-decoration: none; }
#ticketTable thead th i { opacity: 0.5; font-size: 0.9em; }
#ticketTable thead th a:hover i { opacity: 1; }

#ticketTable td {
  padding: 16px;
  border: 0;
  border-top: 1px solid var(--gp-beige);
  vertical-align: middle;
}
#ticketTable td:first-child { padding-left: var(--gp-inset-pad); }
#ticketTable td:last-child { padding-right: var(--gp-inset-pad); }
#ticketTable tbody tr:first-child td { border-top: 0; }
#ticketTable tbody tr:hover td { background: var(--gp-page); }
#ticketTable tr.alt td { background: transparent; }

/* Column voices. The five columns are fixed in the template. */
#ticketTable tbody td:first-child a { font-weight: 600; letter-spacing: -0.01em; color: var(--gp-primary); }
#ticketTable tbody td:first-child a:hover { color: var(--gp-blue-deep); }
#ticketTable tbody td:nth-child(2) {
  font-family: var(--gp-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
  color: var(--gp-ink-3);
  white-space: nowrap;
}
/* Status is a chip, not a word. Stitch tints Open with the sage fixed
   container and leaves everything else on the neutral ladder — which is the
   whole colour system of the redesign in one cell: a hue means a state, and
   the absence of one means the ordinary case. The template prints the status
   name as bare text, so the chip is drawn on the cell's own box. */
#ticketTable tbody td:nth-child(3) {
  font-size: var(--gp-t-lsm);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gp-ink-2);
  white-space: nowrap;
}
#ticketTable tbody td:nth-child(3)::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 8px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: 0.1em;
}
#ticketTable tbody td:nth-child(4) { color: var(--gp-ink); }
#ticketTable tbody td:nth-child(4) .link { color: var(--gp-blue-deep); }
/* tickets.inc.php pins the subject cell to an inline max-height:1.2em, which
   clips descenders at any line-height above 1.2. .truncate already keeps it
   to one line, so the cap is redundant — and it needs !important to beat the
   inline style. */
#ticketTable tbody td .truncate {
  max-height: none !important;
  line-height: 1.45;
}
#ticketTable tbody td:nth-child(5) { color: var(--gp-ink-2); font-size: var(--gp-t-bsm); }
/* The "no records" row spans all five, so keep it plain. */
#ticketTable tbody td[colspan] {
  font: inherit;
  letter-spacing: normal;
  text-transform: none;
  color: var(--gp-ink-3);
  padding: 28px 0;
}

/* tickets.inc.php prints the page links in a bare <div> straight after the
   table. Stitch keeps pagination inside the list card, under a rule — so the
   div is joined to the table's bottom edge and given the card's own sides.
   Scoped to the sibling of #ticketTable so no other page-links row is
   caught. */
#ticketTable {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
#ticketTable:last-child {
  border-bottom-left-radius: var(--gp-r);
  border-bottom-right-radius: var(--gp-r);
}
#ticketTable + div {
  margin: 0;
  padding: 14px var(--gp-inset-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-top: 0;
  border-radius: 0 0 var(--gp-r) var(--gp-r);
  font-size: var(--gp-t-bsm);
  color: var(--gp-ink-2);
}

@media (max-width: 900px) {
  /* The five columns plus 24px end insets do not fit a tablet at the desktop
     rhythm; the cells give back what the card keeps. */
  #ticketTable thead th,
  #ticketTable td { padding: 12px 7px; }
  #ticketTable thead th:first-child,
  #ticketTable td:first-child { padding-left: 12px; }
  #ticketTable thead th:last-child,
  #ticketTable td:last-child { padding-right: 12px; }
  #ticketTable + div { padding: 12px; }
  /* tickets.inc.php pins the subject to an inline max-width:320px, which sets
     the table's min-content and is what pushed it past the viewport. */
  #ticketTable tbody td .truncate { max-width: 100% !important; }
}

/* --- Source-type and file icons ------------------------------------------ */
.Icon {
  width: auto;
  padding-left: 0;
  background-image: none !important;
  color: var(--gp-brand);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.Icon::before {
  font-family: FontAwesome;
  font-style: normal;
  font-weight: normal;
  font-size: 0.9em;
  line-height: 1;
  color: var(--gp-ink-3);
  flex: none;
  transform: translateY(var(--gp-icon-nudge));
}
.Icon.Ticket::before,
.Icon.webTicket::before   { content: "\f0ac"; }   /* globe     */
.Icon.emailTicket::before { content: "\f003"; }   /* envelope  */
.Icon.phoneTicket::before { content: "\f095"; }   /* phone     */
.Icon.otherTicket::before,
.Icon.apiTicket::before    { content: "\f013"; }  /* cog       */
.Icon.attachment::before,
.Icon.file::before         { content: "\f0c6"; }  /* paperclip */
.Icon.refresh::before      { content: "\f021"; }  /* refresh   */
.Icon.thread::before       { content: "\f0e5"; }  /* comment   */
.Icon.thread { font-weight: 600; font-size: 1em; }
.Icon:hover { text-decoration: underline; }
.Icon:hover::before { color: var(--gp-primary); }
.rtl .Icon { padding-left: 0; padding-right: 0; }

/* =========================================================================
   12. Ticket view  (view.inc.php)
   ========================================================================= */

/* Stitch opens the ticket with one header card: the reference and the status
   as chips, the subject beneath them, then the facts about the ticket in a
   grid under a rule. view.inc.php emits all of that as an <table id=ticketInfo>
   holding two <td>s of .infoTable, so the card is drawn on the table itself
   and the cells are unwound into a responsive grid — display roles have to go
   for that to be possible, and dropping them costs nothing here because the
   two columns never needed a shared axis. */
#ticketInfo {
  width: 100%;
  display: block;
  padding: var(--gp-card-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r);
}
#ticketInfo > tbody { display: block; }
#ticketInfo > tbody > tr {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 8px 48px;
}
#ticketInfo h1 {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding-bottom: 20px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--gp-beige);
}
#ticketInfo h1 b {
  order: 2;
  flex-basis: 100%;
  font-weight: 600;
  letter-spacing: -0.015em;
}
/* The ticket number becomes Stitch's reference chip and jumps above the
   subject: it is the thing the requester quotes back at you, and in the
   design it leads. */
#ticketInfo h1 small {
  order: 1;
  padding: 5px 10px;
  background: var(--gp-surface-1);
  border-radius: var(--gp-r-xs);
  font-weight: 600;
  font-size: var(--gp-t-lsm);
  line-height: calc(var(--gp-lh-lsm) / var(--gp-t-lsm));
  letter-spacing: 0.04em;
  color: var(--gp-ink-2);
}
/* The Print / Edit cluster stays on the chip's row rather than dropping to a
   line of its own — the subject is what takes the full basis below it. */
#ticketInfo h1 > .pull-right { order: 1; margin-left: auto; }
#ticketInfo > tbody > tr > td {
  display: block;
  padding: 0;
  min-width: 0;
}
#ticketInfo > tbody > tr > td:last-child { padding-right: 0; }

.infoTable,
table.custom-data {
  background: none;
  width: 100%;
  margin-top: 0;
}
table.custom-data { margin-top: 20px; }
/* Stitch sets each fact as a tracked uppercase micro-label OVER its value,
   in a grid of equal cells — not as a label beside it. The templates emit
   th/td pairs, so the display roles are dropped and each row becomes a cell
   of a grid that reflows by available width. That is also what stops the
   long labels ("Create Date:") from wrapping onto two lines in a narrow
   first column, which is what the old fixed 34% did. */
.infoTable > tbody,
table.custom-data > tbody {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: 20px 32px;
}
.infoTable > tbody > tr,
table.custom-data > tbody > tr { display: block; min-width: 0; }
.infoTable th,
table.custom-data th {
  display: block;
  text-align: left;
  padding: 0 0 4px;
  width: auto;
  background: none;
  color: var(--gp-ink-3);
  font-weight: 600;
  font-size: var(--gp-t-lsm);
  line-height: calc(var(--gp-lh-lsm) / var(--gp-t-lsm));
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: normal;
}
table.custom-data th { width: auto; }
.infoTable td,
table.custom-data td {
  display: block;
  padding: 0;
  color: var(--gp-ink);
  font-size: var(--gp-t-bsm);
  line-height: calc(var(--gp-lh-bsm) / var(--gp-t-bsm));
}
/* The section headings the templates put in a <thead> (or in the first row)
   have to span the grid rather than becoming one of its cells — and the row
   and cell inside the thead have to lose their display roles with it, or the
   heading's rule stops at the width of its own text. */
.infoTable > thead,
table.custom-data > thead,
.infoTable > thead > tr,
table.custom-data > thead > tr,
.infoTable > thead > tr > td,
table.custom-data > thead > tr > td { display: block; }
.infoTable > tbody > tr:has(> .headline),
table.custom-data > tbody > tr:has(> .headline) { grid-column: 1 / -1; }

/* The first fact in the left column is the ticket status, and Stitch draws a
   status as a chip. Nothing else in either table is a state, so the chip is
   scoped to that one cell rather than given a class the template cannot
   emit. */
.infoTable > tbody > tr:first-of-type > td {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  background: var(--gp-brand-chip);
  border-radius: var(--gp-r-pill);
  color: var(--gp-brand-ink);
  font-weight: 600;
  font-size: var(--gp-t-lsm);
  line-height: calc(var(--gp-lh-lsm) / var(--gp-t-lsm));
}
#ticketInfo > tbody > tr > td:last-child .infoTable > tbody > tr:first-of-type > td {
  display: block;
  padding: 0;
  background: none;
  border-radius: 0;
  color: var(--gp-ink);
  font-weight: 400;
  font-size: var(--gp-t-bsm);
}

table .headline,
table.custom-data .headline {
  border-bottom: 1px solid var(--gp-line);
  background: none;
  padding: 0 0 8px;
}
.infoTable thead .headline,
table.custom-data thead .headline,
table.custom-data tr:first-child .headline { border-top: 0; }

/* --- Thread --------------------------------------------------------------- */
/* Stitch draws the conversation as two kinds of bubble: yours on the white
   card surface, the agent's one rung down on the low container, each with a
   heading row, a body, and an attachment strip under a rule. The two are
   told apart by surface rather than by hue — the only colour in a thread is
   the STAFF badge, and that is near-black. */
#ticketThread {
  z-index: 0;
  position: relative;
  border-bottom: 0;
}
#ticketThread::before { content: none; }

.thread-entry { margin-bottom: 18px; }
.thread-entry::before,
.thread-entry::after { content: none; }      /* old timeline masks */

.thread-entry.avatar { margin-left: var(--gp-gutter); }
.thread-entry.response.avatar { margin-right: var(--gp-gutter); margin-left: 0; }
.thread-entry > .avatar {
  margin-left: calc(-1 * var(--gp-gutter));
  display: inline-block;
  width: 40px;
  height: auto;
  border-radius: 50%;
  overflow: hidden;
}
.thread-entry.response > .avatar {
  margin-left: initial;
  margin-right: calc(-1 * var(--gp-gutter));
}
img.avatar { border-radius: inherit; }
.avatar > img.avatar { width: 100%; height: auto; }

.thread-entry .header {
  position: relative;
  padding: var(--gp-inset-pad) var(--gp-inset-pad) 0;
  border: 1px solid var(--gp-beige);
  /* No bottom edge: the header and the body are one card split across two
     elements, and a rule between them would draw a seam through it. */
  border-bottom: 0;
  border-radius: var(--gp-r) var(--gp-r) 0 0;
  background: var(--gp-surface);
  font-size: var(--gp-t-bsm);
  color: var(--gp-ink-2);
  line-height: calc(var(--gp-lh-bsm) / var(--gp-t-bsm));
}
.thread-entry .header b {
  color: var(--gp-primary);
  font-weight: 600;
  font-size: var(--gp-t-lmd);
}
.thread-entry .header time { font-size: var(--gp-t-bsm); color: var(--gp-ink-2); }
/* Retire the CSS-triangle speech tails. */
.thread-entry.avatar .header::before,
.thread-entry.avatar .header::after,
.thread-entry.avatar.message .header::before,
.thread-entry.avatar.message .header::after,
.thread-entry.avatar.response .header::after { content: none; border: 0; }

.thread-entry.message .header { background: var(--gp-surface); }
.thread-entry.response .header {
  background: var(--gp-brand-tint);
  border-color: var(--gp-brand-edge);
}
/* Stitch marks the agent's side with a filled STAFF badge before the name.
   The template emits no element for one, so it is drawn as a pseudo-element
   on the name — the only thing in the header that is reliably the agent. */
.thread-entry.response .header b { color: var(--gp-primary); }
.thread-entry.response .header b::before {
  content: "Staff";
  display: inline-block;
  margin-right: 8px;
  padding: 3px 7px;
  background: var(--gp-primary);
  border-radius: var(--gp-r-xs);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: 0.1em;
}
.thread-entry .header .title {
  max-width: 500px;
  vertical-align: bottom;
  display: inline-block;
  margin-left: 12px;
  font-size: 0.8125rem;
}
.rtl .thread-entry .header .title { margin-left: 0; margin-right: 12px; }

/* css/thread.css loads after this file and pins font-size with !important. */
#content .thread-entry .thread-body,
#content .faq-content .thread-body,
#content #landing_page .thread-body {
  font-size: 1rem !important;
  line-height: 1.62;
  color: var(--gp-ink);
  font-family: var(--gp-sans);
}
#content .thread-body a:not(.button) {
  color: var(--gp-blue-deep) !important;
  text-decoration: underline;
  text-decoration-color: var(--gp-blue-edge);
  text-underline-offset: 0.18em;
}
#content .thread-body a:not(.button):hover {
  color: var(--gp-primary) !important;
  text-decoration-color: currentColor;
}
/* Inset: nested inside a thread entry, under its header. */
.thread-entry .thread-body {
  padding: var(--gp-inset-pad);
  border: 1px solid var(--gp-beige);
  border-top: none;
  border-radius: 0 0 var(--gp-r) var(--gp-r);
  background: var(--gp-surface);
}
/* The agent's bubble sits one rung down the container ladder, header and
   body together, so the two voices read apart at a glance. */
.thread-entry.response .header,
.thread-entry.response .thread-body { background: var(--gp-surface-1); }

/* Attachment strip pinned to the bottom of the card interior. */
/* Attachment strip under a rule at the foot of the bubble, the way Stitch
   draws it — inside the card, not a band bolted to the bottom of it. */
.thread-body .attachments {
  background: transparent;
  margin: 20px 0 0;
  position: static;
  top: 0;
  padding: 16px 0 0;
  border-top: 1px solid var(--gp-beige);
  border-radius: 0;
  font-size: var(--gp-t-bsm);
}
.thread-body .attachments .filesize { margin-left: 0.6em; font-size: 0.75rem; }
.thread-body .attachments i { color: var(--gp-ink-3); margin-right: 4px; }
.thread-body .attachments a,
.thread-body .attachments a:hover { text-decoration: none; }
.thread-body .attachments a:hover { text-decoration: underline; }
.thread-body .attachment-info {
  margin-right: 10px;
  display: inline-block;
  width: 48%;
  padding: 2px 0;
}
.thread-body .attachment-info .filename { max-width: calc(100% - 80px); }

/* --- Thread events --------------------------------------------------------- */
/* Hidden from the client view. include/client/templates/thread-entries.tmpl.php
   whitelists six event types for clients — created, closed, reopened, edited,
   collab, merged — so assignment, priority and department changes never reached
   the client anyway. What did reach them was internal bookkeeping: "Created by
   <agent>" on every ticket, plus "Edited by <agent>" whenever staff touched the
   ticket, and collaborator/merge notices. None of that is the requester's
   business, and it sat directly under their own message.

   Done in CSS, so no PHP or logic changed: the events are still rendered and
   still counted by the template's own "no entries have been posted" guard, and
   nothing in js/ references .thread-event or .type-icon. The client print view
   (templates/ticket-print.tmpl.php) renders only M and R entries, so it never
   showed events to begin with.

   To bring them back, delete the single display:none rule at the end of this
   section; the styling in between is left intact. */
.thread-event {
  padding: 0 0 18px;
  margin-left: var(--gp-gutter);
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.875rem;
}
.rtl .thread-event { margin-left: 0; margin-right: var(--gp-gutter); }
.type-icon {
  border-radius: 50%;
  background: var(--gp-surface-2);
  border: 1px solid var(--gp-line);
  width: 26px;
  height: 26px;
  padding: 0;
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  flex: none;
  vertical-align: middle;
}
.type-icon i { color: var(--gp-ink-3); }
.type-icon.dark { border-color: var(--gp-ink-2); background: var(--gp-ink-2); }
.type-icon.dark i { color: #fff; }
.type-icon::after { content: none; }         /* old timeline mask */
.thread-event img.avatar {
  vertical-align: middle;
  border-radius: 4px;
  width: auto;
  max-height: 22px;
  margin: -2px 3px 0;
}
.thread-event .description {
  margin-left: 0;
  padding: 0;
  display: block;
  width: auto;
  line-height: 1.5;
  color: var(--gp-ink-2);
}

/* The rule that hides them — see the note at the top of this section. Placed
   last so it wins on order rather than on specificity, and so it holds for any
   thread the client portal renders, whatever html-id it is given. */
.thread-event { display: none; }

/* --- Reply form -------------------------------------------------------------- */
/* The reply box is a card like every other, on the white surface — Stitch
   gives it a faint lift so it reads as the thing you act in rather than one
   more thing to read. */
#reply {
  margin-top: var(--gp-card-gap);
  padding: var(--gp-card-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-beige);
  border-radius: var(--gp-r);
  box-shadow: 0 4px 24px rgba(20, 24, 28, 0.02);
}
#reply h2 {
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--gp-beige);
}
#reply > table { width: auto; max-width: 100%; }
#reply table td { vertical-align: top; }
#reply textarea { width: 100% !important; }
#reply input[type="text"],
#reply #response_options textarea {
  border: 1px solid var(--gp-beige);
  background: var(--gp-surface);
}
#reply > div > p:first-child { margin-top: 0; }
#reply em { color: var(--gp-ink-2); }
#reply .attachments .uploads div { display: inline-block; padding-right: 20px; }
#reply .file {
  display: inline-block;
  padding-left: 20px;
  margin-right: 20px;
  background: url('../images/icons/file.gif') 0 50% no-repeat;
}
#reply .warning-banner { margin-top: 14px; }

/* =========================================================================
   13. Sign in / access link  (login.inc.php, accesslink.inc.php, pwreset)
   ========================================================================= */

/* Focused single column. Both templates that use #clientLogin — login.inc.php
   (.login-row/.login-aside) and accesslink.inc.php (an inline display:table-row
   with .login-box/.instructions) — become one narrow centred card with the form
   on top and the secondary routes stacked beneath a hairline. The old
   side-by-side split gave the weaker column equal visual weight and doubled the
   page's width for no gain. */
#content:has(#clientLogin) > .hero-eyebrow,
#content:has(#clientLogin) > h1,
#content:has(#clientLogin) > p {
  /* display:block undoes the flex heading rule used by the ticket pages;
     a flex container ignores text-align for its items. */
  display: block;
  text-align: center;
  margin-left: auto;
  margin-right: auto;
  /* Head shares the card's column. On this rung the column is already
     narrower than a reading measure, so it needs no separate cap. */
  max-width: var(--gp-w-auth);
}
#content:has(#clientLogin) > .hero-eyebrow { margin-bottom: 12px; }
#content:has(#clientLogin) > h1 { margin-bottom: 10px; }

#clientLogin {
  display: block;
  width: 100%;
  /* Auth rung: every field here is a single short input. */
  max-width: var(--gp-w-auth);
  margin: var(--gp-card-gap) auto 0;
  padding: 0;
  background: var(--gp-surface);
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r);
  box-shadow: none;
  overflow: hidden;
}
.rtl #clientLogin { background-position: 5% 50%; }

/* The row wrappers stack. accesslink sets display:table-row inline. */
.login-row,
#clientLogin > div[style] {
  display: block !important;
  gap: 0;
}

/* #clientLogin carries no padding of its own — these two stacked sections are
   the card's inside, so they take the card padding. Both must use the same
   value or their contents would not line up down the card's edge. */
.login-box {
  display: block;
  width: auto;
  flex: none;
  box-shadow: none;
  padding: var(--gp-card-pad);
  background: none;
  border: 0;
  border-radius: 0;
  vertical-align: top;
}
.rtl .login-box { box-shadow: none; }
.login-box > div { margin-bottom: 14px; }
.login-box > div:last-of-type { margin-bottom: 0; }
.login-box label { display: block; font-weight: 500; font-size: 0.875rem; }
.login-box label input { margin-top: 6px; }
/* Submit fills the card; the password link sits centred beneath it. */
.login-box p {
  margin: 20px 0 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}
#clientLogin input[type="submit"],
#clientLogin .btn { width: 100%; padding: 11px 22px; }
.pwreset-link { display: block; text-align: center; font-size: 0.875rem; }

/* Secondary routes: external auth, then the register prompt. */
/* This section is a band across the card, so its tint spans the full width
   while its CONTENTS stay in the same centred column as the fields above. */
/* .login-aside stacks block children (provider buttons, a rule, the register
   prompt), so it is a centred flex column. .instructions is a run of prose with
   inline links — as a flex column each <a> would become its own line, which is
   exactly what it did — so it stays a block. */
.login-aside {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.login-aside,
#clientLogin .instructions {
  display: block;
  width: auto;
  flex: none;
  padding: var(--gp-card-pad);
  border-top: 1px solid var(--gp-line-soft);
  background: var(--gp-page);
  color: var(--gp-ink-2);
  font-size: 0.9375rem;
  text-align: center;
  vertical-align: top;
}
.rtl #clientLogin .instructions { padding: var(--gp-card-pad); }
.login-aside {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.login-aside > * { width: 100%; }
.login-aside hr {
  width: 100% !important;
  margin: 16px 0;
  border-top-color: var(--gp-line);
}
.register-prompt { margin: 0; }

/* Full-width provider button so it reads as a peer of Sign In, not an afterthought. */
.login-aside .external-auth { display: block; }
.login-aside .external-auth + .external-auth { margin-top: 8px; }
.login-aside a.external-sign-in { display: block; }
.login-aside .external-auth-box { display: flex; width: 100%; }
.login-aside .external-auth-name { flex: 1; justify-content: center; min-width: 0; }

#clientLogin p { clear: both; }
#clientLogin strong {
  display: block;
  margin-bottom: 12px;
  padding: 9px 12px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--gp-err);
  background: var(--gp-err-bg);
  border: 1px solid var(--gp-err-edge);
  border-radius: var(--gp-r-sm);
  text-align: left;
}
#clientLogin strong:empty { display: none; }
#clientLogin #username,
#clientLogin #passwd,
#clientLogin #email,
#clientLogin #ticketno { width: 100%; margin-right: 0; }
#clientLogin input[type="text"],
#clientLogin input[type="password"] { margin-bottom: 0; }
#clientLogin hr { margin: 16px 0; border-top-color: var(--gp-line); }

/* The trailing "first time contacting us" line stays a quiet centred row. */
#content:has(#clientLogin) > p:last-of-type {
  text-align: center;
  max-width: var(--gp-w-auth);
  margin: 20px auto 0;
  font-size: 0.9375rem;
}
/* Staff route: its own card, same width and shape as the client card above it,
   so the two audiences read as two parallel doors rather than a form with an
   afterthought stuck to the bottom. */
.agent-signin {
  width: 100%;
  max-width: var(--gp-w-auth);
  /* Tighter than --gp-card-gap on purpose: this card is a continuation of the
     one above it, and the shorter gap groups the pair. */
  margin: 16px auto 0;
  padding: var(--gp-card-pad);
  background: var(--gp-surface);
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}
.agent-signin-label {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gp-ink-3);
}
/* A navy ghost pill, matching the footer's Staff Sign In: quiet enough not to
   compete with the client card, but still unmistakably a control — agents land
   on this page more than any other. */
.agent-signin-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 38px;
  padding: 7px 16px;
  background: transparent;
  border: 1px solid var(--gp-navy);
  border-radius: var(--gp-r-sm);
  color: var(--gp-navy) !important;
  font-weight: 500;
  transition: background-color var(--gp-t-color), color var(--gp-t-color);
}
.agent-signin-link::after { content: "\2192"; font-size: 0.9em; }   /* → */
.agent-signin-link:hover {
  background: var(--gp-navy);
  color: #fff !important;
  text-decoration: none;
}
.agent-signin-link:active { transform: scale(0.98); }

.external-auth + .external-auth { margin-top: 8px; }
a.external-sign-in { text-decoration: none; }
a.external-sign-in:hover { text-decoration: none; }
.external-auth-box {
  display: inline-flex;
  align-items: stretch;
  vertical-align: middle;
  border-radius: var(--gp-r-sm);
  border: 1px solid var(--gp-outline);
  background: var(--gp-surface);
  overflow: hidden;
  transition: border-color var(--gp-t-color);
}
a.external-sign-in:hover .external-auth-box { border-color: var(--gp-ink); }
.external-auth-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--gp-ink-2);
  width: 42px;
  padding: 0;
  border-right: 1px solid var(--gp-line);
  background: var(--gp-page);
}
.external-auth-name {
  display: inline-flex;
  align-items: center;
  color: var(--gp-ink);
  width: auto;
  min-width: 110px;
  padding: 9px 16px;
  line-height: 1.3;
  font-size: 0.9375rem;
  font-weight: 500;
}
img.sign-in-image {
  border: none;
  max-height: 40px;
  max-width: 200px;
  width: auto;
  height: auto;
}

/* =========================================================================
   14. Odds and ends
   ========================================================================= */

/* Print / Edit chips on the ticket view. */
.action-button {
  border-radius: var(--gp-r-sm);
  -webkit-background-clip: padding-box;
  background-clip: padding-box;
  color: var(--gp-ink-2) !important;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--gp-line);
  cursor: pointer;
  font-size: 0.8125rem;
  font-weight: 500;
  overflow: hidden;
  background: var(--gp-surface);
  background-image: none;
  padding: 7px 14px;
  text-decoration: none;
  line-height: 1.3;
  margin-left: 6px;
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color);
}
.action-button:hover {
  background: var(--gp-brand-tint);
  border-color: var(--gp-brand-edge);
  color: var(--gp-primary) !important;
  text-decoration: none;
}
.action-button i { color: var(--gp-ink-3); }
.action-button:hover i { color: var(--gp-primary); }
.action-button span,
.action-button a { color: var(--gp-ink-2) !important; display: inline-block; float: none; }
.action-button a { text-decoration: none; }
.image-hover a.action-button:hover,
.image-hover a.action-button { color: initial !important; text-decoration: none; }

.label {
  font-size: 0.6875rem;
  padding: 2px 8px;
  border-radius: var(--gp-r-pill);
  font-weight: 600;
  letter-spacing: 0.03em;
  line-height: 1.5;
  color: #fff;
  vertical-align: baseline;
  white-space: nowrap;
  text-shadow: none;
  background: var(--gp-ink-3);
  display: inline-block;
}
/* The one place gold appears: quiet metadata chips ("Edited"). */
.label-bare {
  background: var(--gp-gold-soft);
  border: 1px solid transparent;
  color: #6d5526;
  text-shadow: none;
}

/* Pagination: the client templates emit bare <a><b>n</b></a>, so there is
   no hook for a proper pager without a markup change. Kept for the
   #pagination markup other surfaces use. */
#pagination { border: 0; margin: 0 0 32px; padding: 0; }
#pagination li { border: 0; margin: 0; padding: 0; font-size: 0.75rem; list-style: none; display: inline; }
#pagination li a { margin-right: 2px; display: block; float: left; padding: 5px 10px; border-radius: var(--gp-r-pill); text-decoration: none; }
#pagination li a:hover { color: var(--gp-primary); background: var(--gp-surface-3); }
#pagination .previousOff, #pagination .nextOff { color: var(--gp-ink-3); display: block; float: left; font-weight: 600; padding: 5px 8px; }
#pagination .next a, #pagination .previous a { font-weight: 600; }
#pagination .active { color: var(--gp-ink); font-weight: 600; margin-right: 2px; display: block; float: left; padding: 5px 10px; text-decoration: none; }

/* Inline Font Awesome <i> elements set beside text get the same optical
   correction as the ::before glyphs above. Grouped here so the nudge lives in
   one place; .type-icon is excluded because it centres a glyph inside its own
   round chip, where the box centre is the right reference. */
.states i,
.popular-faq i,
.featured-category i,
.action-button i,
.thread-body .attachments i,
#msg_info .icon-2x,
i.refresh {
  position: relative;
  top: var(--gp-icon-nudge);
}

/* =========================================================================
   15. Responsive
   -------------------------------------------------------------------------
   The templates carry width="800"/width="320" table attributes, but
   presentational attributes lose to author CSS — which is what makes a
   fluid layout reachable without markup changes.
   ========================================================================= */

@media (max-width: 1000px) {
  .main-content { margin-right: 0; }
  .sidebar { float: none !important; width: auto; margin-left: 0; }
  .front-page-button { grid-template-columns: 1fr 1fr; }
}

/* --- The rail becomes a drawer ------------------------------------------ */
/* The switch itself lives with the rail in section 4, next to the geometry it
   undoes. What is left here is what the collapse does to the BAR: with the
   rail gone the toggle appears at the left of it, and the logo and the
   account cluster have to give up their desktop sizing so three things fit in
   a 64px row on a 320px screen.

   981px is where the rail stops earning its 256px: below it the canvas beside
   the rail is narrower than the form rung, so a page would start scrolling
   sideways to show a column of navigation nobody is reading. */
@media (max-width: 980px) {
  /* Centring is a desktop concern; in the collapsed bar a max-content floor
     would force the account cluster wider than the viewport. */
  #header #logo,
  #header .pull-right { flex: 0 1 auto; min-width: 0; }
  #header .pull-right { margin-left: auto; }
}

@media (max-width: 780px) {
  :root { --gp-pad: 18px; --gp-gutter: 44px; }

  .span4, .span8 { display: block; width: auto; margin: 0; }
  .span4 { margin-top: 28px; }
  .featured-category { width: auto; }

  #ticketInfo > tbody > tr > td {
    display: block;
    width: auto !important;
    padding: 0 0 20px;
  }
  #ticketInfo h1 { display: block; }
  #ticketInfo h1 > .pull-right { display: block; margin: 12px 0 0; }
  .action-button { margin: 0 6px 0 0; }

  .thread-body .attachment-info { width: 100%; margin-right: 0; }
  .freetext-files .file { width: 100%; }

  .footer-inner { flex-direction: column; align-items: flex-start; }
}

@media (max-width: 640px) {
  #content { padding: var(--gp-pad) var(--gp-margin-sm) 36px; }

  /* Header */
  #header .pull-right,
  #header p { text-align: left; }

  /* Stacked label/value form tables. The rows become plain blocks: with any
     table box left in the chain, an <input size="40">'s intrinsic width
     (~330px) sets the table's min-content and overflows a small phone —
     percentage caps can't shrink a table's min-content, blocks can. */
  #ticketForm > table,
  #ticketForm > table > tbody,
  #ticketForm > table > tbody > tr,
  table.padded,
  table.padded tbody,
  table.padded tr { display: block; width: 100%; table-layout: auto; }
  #ticketForm input[type="text"],
  #ticketForm input[type="password"],
  #ticketForm select { max-width: 100%; }
  #ticketForm > table > tbody > tr > td,
  table.padded tr > td,
  table.padded tr > th {
    display: block;
    width: auto;
    padding: 2px 0;
  }
  #ticketForm > table > tbody > tr > td:first-child:not([colspan]) {
    padding-top: 10px;
    color: var(--gp-ink-3);
    font-size: 0.875rem;
  }
  #ticketForm div textarea,
  #ticketForm td textarea { max-width: none; }

  /* Sign-in panels: login.inc.php uses .login-row (flex, wraps on its own);
     accesslink.inc.php still sets display:table-row/-cell inline. */
  #clientLogin > div > div[style] { display: block !important; }
  #clientLogin > div[style] { display: block !important; }
  .login-box,
  #clientLogin .instructions { display: block; width: auto; }
  .login-box { flex-basis: auto; }
  #clientLogin .instructions { padding: 18px 2px !important; }

  /* Ticket list: five columns will not fit, so let cells wrap and shrink
     rather than hiding a column or forcing a horizontal scroll. */
  /* Auto layout, and drop the template's width="120"/width="320" hints. The
     first three columns hold short atomic values, so pin them to nowrap and
     let auto layout hand the remaining width to subject and department —
     which are the two that can usefully wrap. */
  #ticketTable { font-size: 0.8125rem; table-layout: auto; }
  #ticketTable thead th,
  #ticketTable td { padding: 10px 6px 10px 0; width: auto !important; }
  #ticketTable thead th:first-child,
  #ticketTable td:first-child { padding-left: 10px; }
  #ticketTable thead th:last-child,
  #ticketTable td:last-child { padding-right: 10px; }
  #ticketTable + div { padding: 12px 10px; }
  #ticketTable thead th {
    font-size: 0.625rem;
    letter-spacing: 0.06em;
    white-space: normal !important;
  }
  #ticketTable tbody td:nth-child(1),
  #ticketTable tbody td:nth-child(2),
  #ticketTable tbody td:nth-child(3) { white-space: nowrap; }
  #ticketTable tbody td:nth-child(2) { font-size: 0.6875rem; }
  #ticketTable tbody td:nth-child(4),
  #ticketTable tbody td:nth-child(5) { white-space: normal; overflow-wrap: break-word; }
  /* The subject/department cells hold .truncate divs whose nowrap (plus the
     template's inline max-width:320px) sets the column's min-content to the
     full one-line width — let them wrap here instead. */
  #ticketTable tbody td:nth-child(4) .link,
  #ticketTable tbody td:nth-child(4) .truncate,
  #ticketTable tbody td:nth-child(5) .truncate {
    white-space: normal;
    max-width: none !important;
    display: block;
  }

  /* Search / filter rows go full width */
  #ticketSearchForm .pull-right { margin-left: 0; width: 100%; }
  #ticketSearchForm .pull-right select { flex: 1 1 auto; min-width: 0; }

  /* select2 is initialised with an inline width:350px in class.forms.php */
  .select2-container { width: 100% !important; max-width: 100% !important; }

  #reply { padding: 16px; }
  /* Cards give back some padding at phone widths so the fields keep their room. */
  #ticketForm:has(> table) { padding: 18px 16px 20px; }
  .login-box { padding: 20px 18px; }
  .login-aside,
  #clientLogin .instructions { padding: 18px 18px 20px; }
  .agent-signin { padding: 18px 18px; }
  .thread-entry .header .title { display: none; }   /* duplicates the subject */

  /* CAPTCHA image, field, and hint will not sit on one line at this width. */
  tr.captchaRow td em,
  #ticketForm div .captcha + * { display: block; }
  tr.captchaRow td em { margin-top: 8px; }
  #ticketForm td .captcha { margin-bottom: 8px; }
}

@media (max-width: 420px) {
  .front-page-button { grid-template-columns: 1fr; }
  /* Five columns at 320px: the date wraps, the source-type glyph goes, and
     the whole table steps down a size — the alternative is a horizontal
     scroll, which is worse than a smaller-but-complete row. */
  #ticketTable { font-size: 0.75rem; }
  #ticketTable tbody td:nth-child(2) {
    white-space: normal;
    overflow-wrap: anywhere;
  }
  #ticketTable .Icon { gap: 0; }
  #ticketTable .Icon::before { display: none; }
  #ticketTable thead th,
  #ticketTable td { padding-right: 5px; }
  #ticketTable thead th:first-child,
  #ticketTable td:first-child { padding-left: 6px; }
  #ticketTable thead th:last-child,
  #ticketTable td:last-child { padding-right: 6px; }
  #ticketTable thead th { letter-spacing: 0.03em; }
  #ticketTable tbody td:nth-child(3) { letter-spacing: 0.04em; font-size: 0.6875rem; }
  /* In the dropped-down menu the glyphs are a useful column, so they stay —
     it is the bar itself that has to shed width at 320px. */
  #header { padding: 0 12px; gap: 6px 8px; }
  #header #logo img { max-height: 30px; max-width: 120px; }
  .thread-entry.avatar,
  .thread-entry.response.avatar { margin-left: 0; margin-right: 0; }
  .thread-entry > .avatar,
  .thread-entry.response > .avatar { display: none; }
  .thread-event { margin-left: 0; }
  .thread-event, .rtl .thread-event { margin-right: 0; }
  .agent-signin { border-radius: var(--gp-r-sm); }
}

/* Honour a reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* -------------------------------------------------------------------------
   Contact details on the landing page (templates/contact-card.tmpl.php)

   Sits last in the hero column, under the two buttons: somebody who did not
   find what they needed above is exactly who wants a phone number. Quiet by
   design - it is a fallback, not a call to action, so it takes the page's
   surface and none of its emphasis.
   ------------------------------------------------------------------------- */
.gp-contact {
  order: 3;                      /* after the hero and its two buttons */
  width: 100%;
  max-width: var(--gp-measure, 44rem);
  margin: 34px auto 0;
  padding: 20px 24px;
  background: var(--gp-surface);
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r);
  text-align: left;
}
.gp-contact-head {
  margin: 0 0 14px;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gp-ink-3);
  text-align: center;
}
.gp-contact-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px 34px;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Icon in its own column so a two-line address keeps its label and value
   aligned with the single-line rows beside it. */
.gp-contact-item {
  display: grid;
  grid-template-columns: 18px 1fr;
  align-items: start;
  gap: 10px;
  margin: 0;
  font-size: 0.9375rem;
}
.gp-contact-item > i {
  color: var(--gp-brand);
  line-height: 1.5;
  text-align: center;
}
.gp-contact-label {
  display: block;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gp-ink-3);
  margin-bottom: 1px;
}
.gp-contact-item a { color: var(--gp-blue-deep); font-weight: 500; }
.gp-contact-item a:hover { color: var(--gp-primary); text-decoration: underline;
  text-underline-offset: 3px; }
.gp-contact-item b { font-weight: 500; color: var(--gp-ink); }
.gp-contact-address { line-height: 1.5; }

@media (max-width: 640px) {
  /* Stacked and left-aligned: three centred rows of different widths read as
     ragged, and a phone number is something people scan down a column. */
  .gp-contact { padding: 18px; }
  .gp-contact-list { flex-direction: column; align-items: flex-start; gap: 14px; }
}

/* -------------------------------------------------------------------------
   Cookie notice (templates/cookie-consent.tmpl.php)

   A card, not a full-bleed bar. The portal's surfaces are all bordered cards
   on cream, and a bar edge-to-edge across the bottom reads as browser chrome
   rather than as part of this site. Sized to --gp-w-form, the rung that holds
   the ticket form, so it lines up with the widest thing under it.

   The one shadow in this stylesheet that is not a focus ring. Everything else
   separates by hairline, which works because everything else sits IN the page
   flow; this floats over scrolling content, where a hairline disappears the
   moment warm text scrolls behind it. Kept low and cool so it reads as height
   rather than as a glow.

   Buttons are deliberately equal in weight apart from the fill: Accept takes
   the sage fill every primary action in the portal takes, Reject the same
   ghost pill a native <button> already gets here. Same size, same position in
   the row, both real buttons. Making Reject smaller, greyer or a text link is
   the pattern regulators call out, and it is not one this file is going to
   introduce.
   ------------------------------------------------------------------------- */
.gp-cc {
  position: fixed;
  z-index: 900;          /* under #overlay (1000) and #loading (3000) in
                            css/osticket.css — a modal must cover this. */
  left: 16px;
  right: 16px;
  bottom: 16px;
  bottom: max(16px, env(safe-area-inset-bottom, 16px));
  margin: 0 auto;        /* with left+right+max-width, this centres it */
  max-width: var(--gp-w-form);
}
.gp-cc[hidden] { display: none; }

.gp-cc-card {
  padding: 22px 24px;
  background: var(--gp-surface);
  border: 1px solid var(--gp-line);
  border-radius: var(--gp-r);
  box-shadow: 0 6px 22px rgba(20, 24, 28, 0.12);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity var(--gp-t-color), transform var(--gp-t-move);
}
.gp-cc--on .gp-cc-card { opacity: 1; transform: none; }

.gp-cc-title {
  margin: 0 0 8px;
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--gp-ink);
}
.gp-cc-text {
  margin: 0 0 8px;
  max-width: var(--gp-measure);
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--gp-ink-2);
}
.gp-cc-note {
  /* The honest half: says out loud that the essential cookie survives a
     rejection. Quieter than the line above it, never hidden behind a
     "more information" toggle. */
  margin: 0 0 16px;
  max-width: var(--gp-measure);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--gp-ink-3);
}

.gp-cc-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 20px;
}
.gp-cc-link {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--gp-blue-deep);
}
.gp-cc-link:hover {
  color: var(--gp-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.gp-cc-buttons { display: flex; gap: 10px; }
.gp-cc-btn {
  min-height: 44px;
  padding: 10px 22px;
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  border-radius: var(--gp-r-sm);
  cursor: pointer;
  transition: background-color var(--gp-t-color), border-color var(--gp-t-color),
              color var(--gp-t-color), transform var(--gp-t-move);
}
.gp-cc-btn:active { transform: scale(0.95); }
.gp-cc-accept {
  background: var(--gp-primary);
  border: 1px solid var(--gp-primary);
  color: #fff;
}
.gp-cc-accept:hover {
  background: var(--gp-primary-strong);
  border-color: var(--gp-primary-strong);
  color: #fff;
}
.gp-cc-reject {
  background: transparent;
  border: 1px solid var(--gp-outline);
  color: var(--gp-ink);
}
.gp-cc-reject:hover {
  background: var(--gp-surface-1);
  border-color: var(--gp-primary);
  color: var(--gp-ink);
}

.gp-cc-done {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.4;
  color: var(--gp-ink-2);
}
.gp-cc-done[hidden] { display: none; }

@media (max-width: 640px) {
  /* Two 44px targets side by side fit from 320px up; the policy link moves
     above them so neither button loses width to it. */
  .gp-cc { left: 12px; right: 12px; }
  .gp-cc-card { padding: 18px; }
  .gp-cc-actions { flex-direction: column; align-items: stretch; }
  .gp-cc-buttons { width: 100%; }
  .gp-cc-btn { flex: 1 1 0; padding: 10px 12px; }
}

/* =========================================================================
   Inline attachment viewer  (js/attachment-viewer.js)
   -------------------------------------------------------------------------
   Reading an attachment used to mean downloading it. This is the surface it
   opens in instead: a full-bleed overlay holding the image or the PDF and
   nothing else — a filename, a close button, and the file.

   There is deliberately no download control, no toolbar and no visible URL.
   That is a deterrent and not a control; see the note at the top of
   js/attachment-viewer.js for what it does and does not achieve.
   ========================================================================= */
.gp-viewer {
  position: fixed;
  inset: 0;
  z-index: 4000;          /* over #overlay (1000) and #loading (3000) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.gp-viewer[hidden] { display: none; }
.gp-viewer-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 24, 28, 0.72);
  opacity: 0;
  transition: opacity var(--gp-t-color);
}
.gp-viewer--on .gp-viewer-backdrop { opacity: 1; }

.gp-viewer-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(1100px, 100%);
  height: min(88vh, 100%);
  background: var(--gp-surface);
  border-radius: var(--gp-r);
  overflow: hidden;
  box-shadow: 0 24px 64px rgba(20, 24, 28, 0.35);
  opacity: 0;
  transform: translateY(8px) scale(0.99);
  transition: opacity var(--gp-t-color), transform var(--gp-t-move);
}
.gp-viewer--on .gp-viewer-panel { opacity: 1; transform: none; }

.gp-viewer-bar {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: none;
  padding: 12px 12px 12px 20px;
  border-bottom: 1px solid var(--gp-beige);
  background: var(--gp-surface);
}
.gp-viewer-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--gp-ink);
}
.gp-viewer-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  min-height: 0;
  padding: 0;
  background: transparent;
  border: 1px solid var(--gp-beige);
  border-radius: 50%;
  color: var(--gp-ink-2);
  cursor: pointer;
}
.gp-viewer-close:hover {
  background: var(--gp-err-bg);
  border-color: var(--gp-err-bg);
  color: var(--gp-err);
}

/* The stage is the file and nothing else. */
.gp-viewer-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--gp-surface-2);
}
.gp-viewer-stage--image { padding: 16px; }
.gp-viewer-stage img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  /* A dragged-out image is a saved image on most desktops. */
  -webkit-user-drag: none;
  user-select: none;
}
.gp-viewer-stage iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--gp-surface-2);
}
.gp-viewer-fail {
  margin: 0;
  padding: 32px;
  text-align: center;
  color: var(--gp-ink-2);
}

@media (max-width: 640px) {
  .gp-viewer { padding: 0; }
  .gp-viewer-panel { width: 100%; height: 100%; border-radius: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .gp-viewer-backdrop,
  .gp-viewer-panel { transition: none; }
}
