/* GobMail application styles.
 *
 * Bootstrap 5 (vendored, loaded first) provides the reset (Reboot) and every component; this file
 * adds only what is GobMail's own. There is no second framework and no inherited stylesheet: the
 * upstream fork's Bootstrap 2 base, its RainLoop id rules and its dead theme-variable bridge are
 * gone, so a component styles the Bootstrap 5 way and nothing shadows a gm- class.
 *
 * The Content-Security-Policy's style-src carries no 'unsafe-inline', and a nonce does not apply to a
 * style ATTRIBUTE - only to a <style> element. So the application uses classes, never style="...":
 * an inline style attribute would simply be dropped by the browser and the layout would silently
 * degrade. What genuinely varies per installation (the brand colour) is emitted by the server into a
 * nonce'd <style> block as a custom property, and read from here.
 */

/* The [hidden] attribute must win over Bootstrap's display utilities. The app toggles elements with
 * hide()/show() (the `hidden` attribute), but many of them also carry a display class - the loading and
 * error shells are `.d-flex ... .vh-100`, panes are `.d-flex`, alerts toggle with `hidden`. Bootstrap's
 * `.d-flex{display:flex!important}` outranks the UA `[hidden]{display:none}`, so a "hidden" full-height
 * shell keeps its space and shoves the whole app down off the top of the viewport. This file loads AFTER
 * Bootstrap, so an equally-specific !important rule here wins by source order and makes `hidden` mean hidden.
 */
[hidden] { display: none !important; }

/* Native controls (select, date, file) paint to the OS colour scheme unless the page declares one, so a
   dark theme must set color-scheme or those controls render light-on-dark. Declared on the theme attribute
   the shell stamps on the root, so it holds for every native control in the SPA. */
[data-bs-theme="dark"] { color-scheme: dark; }
[data-bs-theme="light"] { color-scheme: light; }

/* ---------------------------------------------------------------------------------------------------
   DESIGN TOKENS.

   Every colour, radius, shadow and overlay in this stylesheet is named for WHAT IT MEANS, never for what
   it looks like. A rule asks for "the elevated surface" or "the weak border", not for a hex value, so a
   surface can be re-tuned in one place and the whole application follows - and so light and dark cannot
   drift apart, because both themes answer the SAME questions with their own values.

   The vocabulary:

   - SURFACES form a stack, not a pair. `norm` is the page. `weak` and `strong` are quiet fills ON the
     page (a pane, a chip). `lowered` is recessed. `elevated` is the one that matters most in dark mode:
     it is LIGHTER than the page, so a modal, a dropdown or a popover reads as floating ABOVE the mail
     rather than merging into it. A dialog drawn on the page's own colour is why a dark UI looks flat.
   - TEXT is a hierarchy of four, not a body colour plus "muted". `weak` is secondary copy and stays
     comfortably legible (5.98:1 light, 7.31:1 dark on the elevated surface); `hint` is metadata
     (4.98:1 / 5.21:1); `disabled` is an inactive control, and still clears 3:1.
   - BORDERS come in two weights. `norm` separates regions; `weak` separates ROWS - a list ruled with the
     region weight reads as a spreadsheet.
   - INTERACTION states are TRANSLUCENT overlays, not opaque greys, so one hover rule is correct over the
     page, over a dropdown and over a tinted row alike.
   - FOCUS is an explicit outline plus a translucent accent ring; the browser default is never enough on a
     branded surface.
   - ELEVATION is an opacity per level over one shadow colour, so raised / elevated / overlay stay in
     proportion to each other in both themes.
   - Every SIGNAL (primary, danger, warning, success) is a RAMP, not one colour: `minor-2` and `minor-1`
     are the tint and tint-border an alert or badge sits on, the base is the usable strength (it carries
     TEXT on the normal and elevated surfaces and takes white or near-black ink as a solid fill), and
     `major-1..3` are the progressively stronger steps a hover, an active press and an emphasis need.
     Every one of those pairings is contrast-checked in both themes.

   The palette itself is the institution's, not a generic one: Nexo navy #1D3341 and accent teal #2B84A2,
   the dark-theme accent #2f8fb5 / #3ba3cc, and Inter. Kept in step with assets/admin/css/admin.css, which
   carries the same token architecture for the backoffice.

   Bootstrap ships PREBUILT, so it is bridged rather than recompiled: its `--bs-*` theme and component
   variables are re-pointed at these tokens once, below, and every Bootstrap component then inherits the
   system for free. The bridge is written ONCE - a custom property set on `[data-bs-theme]` beats
   Bootstrap's own `[data-bs-theme=dark]` block (equal specificity, later source order) and resolves per
   theme anyway, because what it points AT is theme-dependent. Restating it per theme would be the
   duplication this whole layer exists to remove.

   REGIONS. A theme is not always a property of the whole page. The mailbox top bar keeps the institutional
   navy in BOTH themes, so it is an inverted region in one and an ordinary one in the other - and a token
   that is right on the page surface (dark text, a region-weight border) is WRONG on it. That is not a
   property of the two controls that happened to be there when it was noticed; it is a property of the bar,
   and anything added to the bar later inherits the same wall.

   So both families are declared on the THEME ATTRIBUTE rather than on the root element: the light tokens
   answer to `[data-bs-theme="light"]`, the dark ones to `[data-bs-theme="dark"]`, and the Bootstrap bridge
   to `[data-bs-theme]`. Bootstrap 5.3 already honours a nested `data-bs-theme`, so ONE attribute on a
   container declares a region and both families - `--gm-*` and `--bs-*` - resolve inside it against that
   region's theme. `:root` is listed alongside the light block only because the shell writes the resolved
   theme onto the root itself; the pair is what makes the declaration scopable, not two ways of saying it.

   Custom properties are substituted where they are DECLARED, not where they are read, which is why the
   bridge has to answer to the attribute too: a `--bs-*` computed once on the root would carry the root's
   theme into every region below it.

   `.gm-theme-page` is the way back OUT. A floating layer opened from an inverted region - a dropdown menu
   from a control on the bar - belongs to the page it covers, not to the bar it was opened from, and it is
   a DOM descendant of the region only by accident of where its toggle sits. It re-declares both families
   from the page's own theme, which the root still carries.
   --------------------------------------------------------------------------------------------------- */
/* The institution's own colour is NOT a theme value. The top bar wears it in both themes, and the light
   ramp's darkest step is the same navy, so it is named once here - above the themes, where neither can
   move it and no region can resolve it to something else. */
:root { --gm-brand-navy: #1D3341; }

:root,
[data-bs-theme="light"],
html[data-bs-theme="light"] .gm-theme-page {
	/* Inter is the platform's UI font (shared with the backoffice); system fonts stand in until it loads. */
	--bs-body-font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

	/* Surfaces. */
	--gm-background-lowered: #eceff2;
	--gm-background-norm: #ffffff;
	--gm-background-weak: #f4f6f8;
	--gm-background-strong: #e8ecef;
	--gm-background-elevated: #ffffff;

	/* Text. */
	--gm-text-norm: #1a2028;
	--gm-text-norm-rgb: 26, 32, 40;
	--gm-text-weak: #5a6572;
	--gm-text-weak-rgb: 90, 101, 114;
	--gm-text-hint: #67717d;
	--gm-text-hint-rgb: 103, 113, 125;
	--gm-text-disabled: #868e99;
	--gm-text-emphasis: #0b0f14;
	--gm-text-emphasis-rgb: 11, 15, 20;

	/* Borders. */
	--gm-border-norm: #dbe0e5;
	--gm-border-weak: #edeff2;
	--gm-border-translucent: rgba(11, 15, 20, 0.1);

	/* Interaction overlays. */
	--gm-interaction-hover: rgba(29, 51, 65, 0.06);
	--gm-interaction-active: rgba(29, 51, 65, 0.12);
	--gm-interaction-selected: rgba(43, 132, 162, 0.12);

	/* Focus. */
	--gm-focus-outline: #2B84A2;
	--gm-focus-ring: rgba(43, 132, 162, 0.3);

	/* Overlay and elevation. */
	--gm-backdrop-color: #101820;
	--gm-backdrop-opacity: 0.45;
	--gm-shadow-color: 16 24 32;
	--gm-shadow-opacity-raised: 0.08;
	--gm-shadow-opacity-elevated: 0.16;
	--gm-shadow-opacity-overlay: 0.26;

	/* Radius. */
	--gm-radius-sm: 0.375rem;
	--gm-radius-md: 0.5rem;
	--gm-radius-lg: 0.75rem;

	/* Primary ramp. The institutional accent #2B84A2 is the brand mark of the surface - it draws the focus
	   ring, the active-row tint and the indicators. It does NOT carry text on white (4.26:1), so the ramp's
	   usable base is one step down at #226f89 (5.67:1 with white ink, 5.98:1 as text), which is the same
	   teal the product already used as its hover tone. */
	--gm-primary-minor-2: #e3f0f5;
	--gm-primary-minor-1: #a9d0dd;
	--gm-primary: #226f89;
	--gm-primary-rgb: 34, 111, 137;
	--gm-primary-major-1: #1a5568;
	--gm-primary-major-2: #143f4e;
	--gm-primary-major-3: var(--gm-brand-navy);
	--gm-primary-contrast: #ffffff;
	/* The brand tone itself, kept for the places the accent carries a GRAPHIC rather than words - the focus
	   outline, the tick inside a checked box, the range thumb. A non-text graphic needs 3:1, which #2B84A2
	   clears comfortably; text needs 4.5:1, which it does not. That is the whole reason the two are
	   separate tokens, and why a rule must pick the one that matches what it is drawing. */
	--gm-accent: #2B84A2;

	/* Danger ramp. */
	--gm-danger-minor-2: #fbe9ec;
	--gm-danger-minor-1: #eec2ca;
	--gm-danger: #b00020;
	--gm-danger-rgb: 176, 0, 32;
	--gm-danger-major-1: #92001b;
	--gm-danger-major-2: #750016;
	--gm-danger-contrast: #ffffff;

	/* Warning ramp. */
	--gm-warning-minor-2: #fbf1e0;
	--gm-warning-minor-1: #e8cfa2;
	--gm-warning: #9c5d0a;
	--gm-warning-rgb: 156, 93, 10;
	--gm-warning-major-1: #834d08;
	--gm-warning-major-2: #6a3e06;
	--gm-warning-contrast: #ffffff;

	/* Success ramp. */
	--gm-success-minor-2: #e4f2e8;
	--gm-success-minor-1: #a9d4b8;
	--gm-success: #167a34;
	--gm-success-rgb: 22, 122, 52;
	--gm-success-major-1: #12662b;
	--gm-success-major-2: #0e5222;
	--gm-success-contrast: #ffffff;

	/* Information IS the institutional accent here - GobMail does not own a second blue, and inventing one
	   would be a colour nobody chose. The info ramp is an alias, so the two can never drift. */
	--gm-info-minor-2: var(--gm-primary-minor-2);
	--gm-info-minor-1: var(--gm-primary-minor-1);
	--gm-info: var(--gm-primary);
	--gm-info-rgb: var(--gm-primary-rgb);
	--gm-info-contrast: var(--gm-primary-contrast);
}

[data-bs-theme="dark"],
html[data-bs-theme="dark"] .gm-theme-page {
	/* The page is the darkest ordinary surface and the ELEVATED one is lighter than it, which is what makes
	   a dialog or a menu read as floating rather than as a hole cut in the page. */
	--gm-background-lowered: #0a0e12;
	--gm-background-norm: #0f1419;
	--gm-background-weak: #161d25;
	--gm-background-strong: #212a34;
	--gm-background-elevated: #1a2129;

	--gm-text-norm: #e6edf3;
	--gm-text-norm-rgb: 230, 237, 243;
	--gm-text-weak: #a5b1bd;
	--gm-text-weak-rgb: 165, 177, 189;
	--gm-text-hint: #8b949e;
	--gm-text-hint-rgb: 139, 148, 158;
	--gm-text-disabled: #6a7480;
	--gm-text-emphasis: #f2f7fb;
	--gm-text-emphasis-rgb: 242, 247, 251;

	--gm-border-norm: #333c46;
	--gm-border-weak: #242c35;
	--gm-border-translucent: rgba(230, 237, 243, 0.1);

	--gm-interaction-hover: rgba(165, 177, 189, 0.1);
	--gm-interaction-active: rgba(165, 177, 189, 0.18);
	--gm-interaction-selected: rgba(47, 143, 181, 0.22);

	--gm-focus-outline: #3ba3cc;
	--gm-focus-ring: rgba(47, 143, 181, 0.35);

	--gm-backdrop-color: #05080b;
	--gm-backdrop-opacity: 0.66;
	--gm-shadow-color: 0 0 0;
	--gm-shadow-opacity-raised: 0.32;
	--gm-shadow-opacity-elevated: 0.44;
	--gm-shadow-opacity-overlay: 0.58;

	/* On a dark surface the accent is bright enough to carry text (5.04:1 on the page, 4.3:1 on the elevated
	   surface), so the ramp's base IS the accent and the text step is one notch brighter. A solid accent fill
	   takes NEAR-BLACK ink: white on #2f8fb5 is 3.73:1 and fails, #0d1116 on it is 5.11:1 and passes. */
	--gm-primary-minor-2: #12303c;
	--gm-primary-minor-1: #1f4a5c;
	--gm-primary: #3ba3cc;
	--gm-primary-rgb: 59, 163, 204;
	--gm-primary-major-1: #57b6da;
	--gm-primary-major-2: #7fbdd6;
	--gm-primary-major-3: #a9d6e6;
	--gm-primary-contrast: #0d1116;
	--gm-accent: #2f8fb5;

	--gm-danger-minor-2: #3a1417;
	--gm-danger-minor-1: #5c2024;
	--gm-danger: #f2726e;
	--gm-danger-rgb: 242, 114, 110;
	--gm-danger-major-1: #f79490;
	--gm-danger-major-2: #fab4b0;
	--gm-danger-contrast: #0d1116;

	--gm-warning-minor-2: #3a2a10;
	--gm-warning-minor-1: #5b421c;
	--gm-warning: #f0a64d;
	--gm-warning-rgb: 240, 166, 77;
	--gm-warning-major-1: #f4bb75;
	--gm-warning-major-2: #f8cf9d;
	--gm-warning-contrast: #0d1116;

	--gm-success-minor-2: #102a17;
	--gm-success-minor-1: #1d4526;
	--gm-success: #4caf50;
	--gm-success-rgb: 76, 175, 80;
	--gm-success-major-1: #6cc270;
	--gm-success-major-2: #8ed491;
	--gm-success-contrast: #0d1116;

	/* The info ramp is an alias of the accent (see the light block) and so needs no values of its own -
	   but it does need to be RESTATED here, because a custom property is substituted where it is declared.
	   Declared only in the light block, `var(--gm-primary)` would be resolved against whatever element that
	   block matched; inside a region that declares itself dark on a light page - the top bar - the alias
	   would hand back the PAGE's accent while everything beside it answered with the region's. An alias is
	   only an alias if it is one everywhere. */
	--gm-info-minor-2: var(--gm-primary-minor-2);
	--gm-info-minor-1: var(--gm-primary-minor-1);
	--gm-info: var(--gm-primary);
	--gm-info-rgb: var(--gm-primary-rgb);
	--gm-info-contrast: var(--gm-primary-contrast);
}

/* ---------------------------------------------------------------------------------------------------
   THE BOOTSTRAP BRIDGE. Written once, for both themes and for every region (see the note above).
   --------------------------------------------------------------------------------------------------- */
:root,
[data-bs-theme],
html[data-bs-theme] .gm-theme-page {
	--gm-shadow-raised: 0 1px 3px rgb(var(--gm-shadow-color) / var(--gm-shadow-opacity-raised));
	--gm-shadow-elevated: 0 4px 16px rgb(var(--gm-shadow-color) / var(--gm-shadow-opacity-elevated));
	--gm-shadow-overlay: 0 24px 64px rgb(var(--gm-shadow-color) / var(--gm-shadow-opacity-overlay));

	--bs-body-bg: var(--gm-background-norm);
	--bs-body-color: var(--gm-text-norm);
	--bs-body-color-rgb: var(--gm-text-norm-rgb);
	--bs-emphasis-color: var(--gm-text-emphasis);
	--bs-emphasis-color-rgb: var(--gm-text-emphasis-rgb);
	--bs-secondary-color: var(--gm-text-weak);
	--bs-secondary-color-rgb: var(--gm-text-weak-rgb);
	--bs-tertiary-color: var(--gm-text-hint);
	--bs-tertiary-color-rgb: var(--gm-text-hint-rgb);
	--bs-secondary-bg: var(--gm-background-strong);
	--bs-tertiary-bg: var(--gm-background-weak);
	--bs-border-color: var(--gm-border-norm);
	--bs-border-color-translucent: var(--gm-border-translucent);

	--bs-border-radius: var(--gm-radius-sm);
	--bs-border-radius-sm: 0.25rem;
	--bs-border-radius-lg: var(--gm-radius-md);
	--bs-border-radius-xl: var(--gm-radius-lg);
	--bs-box-shadow-sm: var(--gm-shadow-raised);
	--bs-box-shadow: var(--gm-shadow-elevated);
	--bs-box-shadow-lg: var(--gm-shadow-overlay);

	--bs-primary: var(--gm-primary);
	--bs-primary-rgb: var(--gm-primary-rgb);
	--bs-primary-bg-subtle: var(--gm-primary-minor-2);
	--bs-primary-border-subtle: var(--gm-primary-minor-1);
	--bs-primary-text-emphasis: var(--gm-primary);
	--bs-danger: var(--gm-danger);
	--bs-danger-rgb: var(--gm-danger-rgb);
	--bs-danger-bg-subtle: var(--gm-danger-minor-2);
	--bs-danger-border-subtle: var(--gm-danger-minor-1);
	--bs-danger-text-emphasis: var(--gm-danger);
	--bs-warning: var(--gm-warning);
	--bs-warning-rgb: var(--gm-warning-rgb);
	--bs-warning-bg-subtle: var(--gm-warning-minor-2);
	--bs-warning-border-subtle: var(--gm-warning-minor-1);
	--bs-warning-text-emphasis: var(--gm-warning);
	--bs-success: var(--gm-success);
	--bs-success-rgb: var(--gm-success-rgb);
	--bs-success-bg-subtle: var(--gm-success-minor-2);
	--bs-success-border-subtle: var(--gm-success-minor-1);
	--bs-success-text-emphasis: var(--gm-success);
	--bs-info: var(--gm-info);
	--bs-info-rgb: var(--gm-info-rgb);
	--bs-info-bg-subtle: var(--gm-info-minor-2);
	--bs-info-border-subtle: var(--gm-info-minor-1);
	--bs-info-text-emphasis: var(--gm-info);

	--bs-link-color: var(--gm-primary);
	--bs-link-color-rgb: var(--gm-primary-rgb);
	--bs-link-hover-color: var(--gm-primary-major-1);
	--bs-focus-ring-color: var(--gm-focus-ring);
}

/* Bootstrap's PREBUILT css bakes #0d6efd into the form-control focus ring and the checked
 * checkbox/radio/switch/range (they use the compiled Sass colour, not var(--bs-primary)), so remapping the
 * token alone leaves them blue - the residual blue against the teal everywhere else. Those states are
 * re-pointed at the accent here, through the tokens, so there is no second copy of the colour. */
.form-check-input:checked { background-color: var(--gm-accent); border-color: var(--gm-accent); }
.form-range::-webkit-slider-thumb { background-color: var(--gm-accent); }
.form-range::-moz-range-thumb { background-color: var(--gm-accent); }
/* Same cause, one more place: the prebuilt css declares `--bs-nav-pills-link-active-bg:#0d6efd` as a
   LITERAL on .nav-pills, so the selected pill stayed Bootstrap blue while everything around it was
   teal. Setting the variable rather than the rule keeps Bootstrap's own selector in charge of WHEN a
   pill is active - we only say what colour that means. */
.nav-pills {
	--bs-nav-pills-link-active-bg: var(--gm-primary);
	--bs-nav-pills-link-active-color: var(--gm-primary-contrast);
}
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
	border-color: var(--gm-focus-outline);
	box-shadow: 0 0 0 0.25rem var(--gm-focus-ring);
}
/* One focus treatment for everything a keyboard can land on: a visible outline in the accent, plus the
   translucent ring that makes it readable over any surface. */
.btn:focus-visible,
.btn-close:focus-visible,
.dropdown-item:focus-visible,
.nav-link:focus-visible,
.page-link:focus-visible,
a:focus-visible {
	outline: 2px solid var(--gm-focus-outline);
	outline-offset: 1px;
	box-shadow: 0 0 0 0.2rem var(--gm-focus-ring);
}
.form-control::placeholder, .form-select::placeholder { color: var(--gm-text-hint); }
.form-control:disabled, .form-select:disabled, .form-control[readonly] {
	color: var(--gm-text-disabled);
	background-color: var(--gm-background-weak);
}
.form-label { color: var(--gm-text-norm); }
.form-text { color: var(--gm-text-hint); }

/* Buttons consume the ramps, so a hover and an active press are steps ON the signal rather than an
   improvised second colour, and the ink on a solid fill is the one that was contrast-checked for it. */
.btn-primary {
	--bs-btn-color: var(--gm-primary-contrast);
	--bs-btn-bg: var(--gm-primary);
	--bs-btn-border-color: var(--gm-primary);
	--bs-btn-hover-color: var(--gm-primary-contrast);
	--bs-btn-hover-bg: var(--gm-primary-major-1);
	--bs-btn-hover-border-color: var(--gm-primary-major-1);
	--bs-btn-active-color: var(--gm-primary-contrast);
	--bs-btn-active-bg: var(--gm-primary-major-2);
	--bs-btn-active-border-color: var(--gm-primary-major-2);
	--bs-btn-disabled-color: var(--gm-primary-contrast);
	--bs-btn-disabled-bg: var(--gm-primary);
	--bs-btn-disabled-border-color: var(--gm-primary);
}
.btn-danger {
	--bs-btn-color: var(--gm-danger-contrast);
	--bs-btn-bg: var(--gm-danger);
	--bs-btn-border-color: var(--gm-danger);
	--bs-btn-hover-color: var(--gm-danger-contrast);
	--bs-btn-hover-bg: var(--gm-danger-major-1);
	--bs-btn-hover-border-color: var(--gm-danger-major-1);
	--bs-btn-active-color: var(--gm-danger-contrast);
	--bs-btn-active-bg: var(--gm-danger-major-2);
	--bs-btn-active-border-color: var(--gm-danger-major-2);
	--bs-btn-disabled-color: var(--gm-danger-contrast);
	--bs-btn-disabled-bg: var(--gm-danger);
	--bs-btn-disabled-border-color: var(--gm-danger);
}
.btn-warning {
	--bs-btn-color: var(--gm-warning-contrast);
	--bs-btn-bg: var(--gm-warning);
	--bs-btn-border-color: var(--gm-warning);
	--bs-btn-hover-color: var(--gm-warning-contrast);
	--bs-btn-hover-bg: var(--gm-warning-major-1);
	--bs-btn-hover-border-color: var(--gm-warning-major-1);
	--bs-btn-active-color: var(--gm-warning-contrast);
	--bs-btn-active-bg: var(--gm-warning-major-2);
	--bs-btn-active-border-color: var(--gm-warning-major-2);
}
.btn-success {
	--bs-btn-color: var(--gm-success-contrast);
	--bs-btn-bg: var(--gm-success);
	--bs-btn-border-color: var(--gm-success);
	--bs-btn-hover-color: var(--gm-success-contrast);
	--bs-btn-hover-bg: var(--gm-success-major-1);
	--bs-btn-hover-border-color: var(--gm-success-major-1);
	--bs-btn-active-color: var(--gm-success-contrast);
	--bs-btn-active-bg: var(--gm-success-major-2);
	--bs-btn-active-border-color: var(--gm-success-major-2);
}
/* The QUIET button: the dismiss beside a primary action, and every neutral control in a toolbar. It is an
   outline in the region weight whose states are the interaction OVERLAYS, so it is correct on the page and
   on an elevated dialog alike without a second rule for each. */
.btn-outline-secondary, .btn-secondary {
	--bs-btn-color: var(--gm-text-norm);
	--bs-btn-bg: transparent;
	--bs-btn-border-color: var(--gm-border-norm);
	--bs-btn-hover-color: var(--gm-text-norm);
	--bs-btn-hover-bg: var(--gm-interaction-hover);
	--bs-btn-hover-border-color: var(--gm-border-norm);
	--bs-btn-active-color: var(--gm-text-norm);
	--bs-btn-active-bg: var(--gm-interaction-active);
	--bs-btn-active-border-color: var(--gm-border-norm);
	--bs-btn-disabled-color: var(--gm-text-disabled);
	--bs-btn-disabled-bg: transparent;
	--bs-btn-disabled-border-color: var(--gm-border-weak);
}
/* A toolbar toggle that is ON is not merely hovered: it takes the accent tint and the accent edge, so its
   state survives the pointer leaving it. */
.btn-check:checked + .btn-outline-secondary,
.btn-check:active + .btn-outline-secondary {
	color: var(--gm-primary);
	background-color: var(--gm-interaction-selected);
	border-color: var(--gm-primary);
}
.btn-outline-danger {
	--bs-btn-color: var(--gm-danger);
	--bs-btn-border-color: var(--gm-danger-minor-1);
	--bs-btn-hover-color: var(--gm-danger-contrast);
	--bs-btn-hover-bg: var(--gm-danger);
	--bs-btn-hover-border-color: var(--gm-danger);
	--bs-btn-active-color: var(--gm-danger-contrast);
	--bs-btn-active-bg: var(--gm-danger-major-1);
	--bs-btn-active-border-color: var(--gm-danger-major-1);
	--bs-btn-disabled-color: var(--gm-text-disabled);
	--bs-btn-disabled-border-color: var(--gm-border-weak);
}
.btn-link {
	--bs-btn-color: var(--gm-primary);
	--bs-btn-hover-color: var(--gm-primary-major-1);
	--bs-btn-active-color: var(--gm-primary-major-2);
	--bs-btn-disabled-color: var(--gm-text-disabled);
}
/* .btn-light is the QUIET control: the folder pane's "new folder", every button in the list header, the
   selection bar. Bootstrap compiles it to a fixed near-white (#f8f9fa with #000 text) and ships NO
   dark-theme variant, so in the dark theme all 73 of them rendered as white blocks with black glyphs -
   the toolbar read as a row of stickers pasted onto the pane. Re-pointed at the neutral surface tokens,
   which resolve per theme, so it is a quiet raised control in both instead of a hole in one. */
.btn-light {
	--bs-btn-color: var(--gm-text-norm);
	--bs-btn-bg: var(--gm-background-weak);
	--bs-btn-border-color: var(--gm-border-weak);
	--bs-btn-hover-color: var(--gm-text-norm);
	--bs-btn-hover-bg: var(--gm-background-strong);
	--bs-btn-hover-border-color: var(--gm-border-norm);
	--bs-btn-active-color: var(--gm-text-norm);
	--bs-btn-active-bg: var(--gm-background-strong);
	--bs-btn-active-border-color: var(--gm-border-norm);
	--bs-btn-disabled-color: var(--gm-text-disabled);
	--bs-btn-disabled-bg: var(--gm-background-weak);
	--bs-btn-disabled-border-color: var(--gm-border-weak);
}
/* The same compiled pair behind .text-bg-light, which the list's count badge wears. Bootstrap declares
   both halves !important, so the correction has to carry the same weight to land. */
.text-bg-light {
	color: var(--gm-text-norm) !important;
	background-color: var(--gm-background-strong) !important;
}
/* The signal badges - "this device", a verified signature, a failed job, a spam warning - are the same
   trap one step further in. Bootstrap lets the BACKGROUND follow `--bs-*-rgb`, which the bridge already
   re-points at our ramps, but it compiles the FOREGROUND as a literal (`#fff` on success and danger,
   `#000` on warning and info) chosen for the colours it shipped with. Ours are not those colours, and a
   signal ramp inverts between the themes precisely because its base step is tuned to be READ against the
   page - so in the dark theme white sat on a brightened green at 2.3:1 and on a brightened red at 2.8:1,
   and in the light theme black sat on the amber at 4.0:1. Three failures of the same one cause.
   The ink each ramp is legible against is already a token (`--gm-*-contrast`), declared per theme beside
   the ramp it belongs to; it simply was never bridged, because Bootstrap gives no variable to bridge it
   through. Saying it here is the whole fix, and it is the ramp that decides - so a ramp that is re-tuned
   later carries its own ink with it instead of leaving one of these to be re-measured by hand. */
.text-bg-danger { color: var(--gm-danger-contrast) !important; }
.text-bg-warning { color: var(--gm-warning-contrast) !important; }
.text-bg-success { color: var(--gm-success-contrast) !important; }
.text-bg-info { color: var(--gm-info-contrast) !important; }

/* ---------------------------------------------------------------------------------------------------
   ELEVATED SURFACES: dialogs, menus, popovers, cards, toasts.

   These are the components that must not merge into the page. They share one surface token, one shadow
   level and one radius, so a menu opened from a dialog and a dialog opened from the page look like the
   same material at two heights.
   --------------------------------------------------------------------------------------------------- */
.modal {
	--bs-modal-color: var(--gm-text-norm);
	--bs-modal-bg: var(--gm-background-elevated);
	--bs-modal-border-color: transparent;
	--bs-modal-border-width: 0;
	--bs-modal-border-radius: var(--gm-radius-lg);
	--bs-modal-inner-border-radius: var(--gm-radius-lg);
	--bs-modal-box-shadow: var(--gm-shadow-overlay);
	--bs-modal-padding: 2rem;
	/* NO divider under the header and none above the footer. A rule across a dialog is what makes it read
	   as three stacked boxes instead of one considered surface; the separation is whitespace. */
	--bs-modal-header-border-width: 0;
	--bs-modal-footer-border-width: 0;
}
.modal-backdrop {
	--bs-backdrop-bg: var(--gm-backdrop-color);
	--bs-backdrop-opacity: var(--gm-backdrop-opacity);
}
/* Generous, even padding, and a title that leads. The compose window is deliberately exempt: it is a
   WINDOW with a title bar and a docked action bar, not a dialog, and it sets its own compact geometry. */
.modal-dialog:not(.gm-compose-dialog) .modal-header { padding: 1.75rem 2rem 0.25rem; }
.modal-dialog:not(.gm-compose-dialog) .modal-body { padding: 0.75rem 2rem 1rem; }
.modal-dialog:not(.gm-compose-dialog) .modal-body:last-child { padding-bottom: 1.75rem; }
.modal-dialog:not(.gm-compose-dialog) .modal-footer { padding: 0.5rem 1.75rem 1.75rem; }
.modal-title {
	font-size: 1.25rem;
	font-weight: 600;
	line-height: 1.3;
	letter-spacing: -0.01em;
	color: var(--gm-text-norm);
}
/* The close control is a BARE icon on the title's optical line, not Bootstrap's tinted square: it sits in
   a hit target the size of a finger, shows its surface only on hover or focus, and never competes with the
   title beside it. */
.modal-header .btn-close {
	--bs-btn-close-opacity: 0.55;
	--bs-btn-close-hover-opacity: 1;
	width: 2rem;
	height: 2rem;
	padding: 0;
	margin: 0 -0.5rem 0 auto;
	align-self: flex-start;
	border-radius: var(--gm-radius-sm);
	background-size: 0.8em;
}
.modal-header .btn-close:hover { background-color: var(--gm-interaction-hover); }

.dropdown-menu {
	--bs-dropdown-bg: var(--gm-background-elevated);
	--bs-dropdown-border-color: var(--gm-border-norm);
	--bs-dropdown-border-radius: var(--gm-radius-md);
	--bs-dropdown-inner-border-radius: var(--gm-radius-sm);
	--bs-dropdown-color: var(--gm-text-norm);
	--bs-dropdown-link-color: var(--gm-text-norm);
	--bs-dropdown-link-hover-color: var(--gm-text-norm);
	--bs-dropdown-link-hover-bg: var(--gm-interaction-hover);
	--bs-dropdown-link-active-color: var(--gm-text-norm);
	--bs-dropdown-link-active-bg: var(--gm-interaction-selected);
	--bs-dropdown-link-disabled-color: var(--gm-text-disabled);
	--bs-dropdown-header-color: var(--gm-text-hint);
	--bs-dropdown-divider-bg: var(--gm-border-weak);
	--bs-dropdown-padding-x: 0.35rem;
	--bs-dropdown-padding-y: 0.35rem;
	--bs-dropdown-item-padding-x: 0.65rem;
	--bs-dropdown-item-padding-y: 0.4rem;
	box-shadow: var(--gm-shadow-elevated);
}
.dropdown-item { border-radius: var(--gm-radius-sm); }
.dropdown-header { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
.popover {
	--bs-popover-bg: var(--gm-background-elevated);
	--bs-popover-border-color: var(--gm-border-norm);
	--bs-popover-border-radius: var(--gm-radius-md);
	--bs-popover-header-bg: var(--gm-background-elevated);
	box-shadow: var(--gm-shadow-elevated);
}
.offcanvas, .offcanvas-lg, .offcanvas-md, .offcanvas-sm, .offcanvas-xl, .offcanvas-xxl {
	--bs-offcanvas-bg: var(--gm-background-elevated);
	--bs-offcanvas-border-color: var(--gm-border-norm);
}
.toast {
	--bs-toast-bg: var(--gm-background-elevated);
	--bs-toast-border-color: var(--gm-border-norm);
	--bs-toast-header-bg: var(--gm-background-elevated);
	--bs-toast-border-radius: var(--gm-radius-md);
	box-shadow: var(--gm-shadow-elevated);
}
.card {
	--bs-card-bg: var(--gm-background-elevated);
	--bs-card-border-color: var(--gm-border-norm);
	--bs-card-border-radius: var(--gm-radius-md);
	--bs-card-cap-bg: var(--gm-background-weak);
}
.list-group {
	--bs-list-group-bg: transparent;
	--bs-list-group-border-color: var(--gm-border-weak);
	--bs-list-group-action-hover-bg: var(--gm-interaction-hover);
	--bs-list-group-action-active-bg: var(--gm-interaction-active);
	--bs-list-group-active-bg: var(--gm-interaction-selected);
	--bs-list-group-active-color: var(--gm-text-norm);
	--bs-list-group-active-border-color: var(--gm-border-weak);
	--bs-list-group-disabled-color: var(--gm-text-disabled);
}

/* An alert is the minor-2 tint of its signal, edged in minor-1, with the signal itself down its leading
   edge - the same shape the backoffice uses, so a warning reads identically on both surfaces. */
.alert {
	--bs-alert-border-radius: var(--gm-radius-md);
	border-left-width: 3px;
}
.alert-danger { border-left-color: var(--gm-danger); }
.alert-warning { border-left-color: var(--gm-warning); }
.alert-success { border-left-color: var(--gm-success); }
.alert-info, .alert-primary { border-left-color: var(--gm-primary); }
.badge { border-radius: var(--gm-radius-sm); font-weight: 600; }

/* Row separators are the WEAK border. A list ruled at the region weight reads as a spreadsheet, and every
   row list in this app is drawn with Bootstrap's .border-bottom utility - which resolves --bs-border-color
   on the element itself, so re-pointing the token on the row is enough and no !important is needed. The
   controls INSIDE a dialog keep the region weight: a field with an invisible edge is not a field. */
.gm-msg-item, .modal-body { --bs-border-color: var(--gm-border-weak); }
.modal-body .form-control,
.modal-body .form-select,
.modal-body .input-group-text,
.modal-body table,
.modal-body .card { --bs-border-color: var(--gm-border-norm); }
/* A checkbox is a CONTROL, not a divider: it keeps the region weight wherever it sits, including on a
   message row and inside a dialog, where the token above is the weak one. */
.form-check-input { border-color: var(--gm-border-norm); }

/* ---------------------------------------------------------------------------------------------------
   THE CONFIRMATION.

   One dialog answers every "are you sure": confirmDialog() in app.js builds it and resolves a promise, so
   the browser's own confirm() - which cannot be styled, translated by us, or made to look like this
   product - appears nowhere in the application.

   Its shape is the shape of a decision: the question in the title, the consequence beneath it in the weak
   text with the part that matters emphasised, and the two answers separated across the foot - the way OUT
   on the left as a quiet outline, the thing that will actually happen on the right. A destructive answer
   wears the danger ramp, and the dialog opens with the SAFE choice focused.
   --------------------------------------------------------------------------------------------------- */
.gm-confirm .modal-dialog { max-width: 30rem; }
.gm-confirm-body {
	margin: 0;
	color: var(--gm-text-weak);
	font-size: 0.95rem;
	line-height: 1.55;
	/* A message assembled from an error and a question keeps its blank line: the two are not one sentence. */
	white-space: pre-line;
	overflow-wrap: anywhere;
}
.gm-confirm-body strong { color: var(--gm-text-norm); font-weight: 600; }
.gm-confirm-actions { justify-content: space-between; gap: 0.75rem; }
.gm-confirm-actions .btn { padding: 0.55rem 1.15rem; font-weight: 500; }

.gm-brand-logo { max-height: 64px; }
/* Theme-aware wordmark: the branding hangs both a light-ink and a dark-ink logo on the sign-in card and the
   loading shell, and only the one matching the resolved theme is shown. The shell stamps data-bs-theme on the
   root before first paint, so the swap is instant and flash-free - the same signal the rest of the SPA reads.
   inline-block (not block) keeps the shown logo centred inside its text-center slot. */
.gm-brand-logo-dark { display: none; }
[data-bs-theme="dark"] .gm-brand-logo-light { display: none; }
[data-bs-theme="dark"] .gm-brand-logo-dark { display: inline-block; }
/* GobMail's OWN mark and wordmark, at the top of the folder pane. This is the product's identity - the
   application chrome, like the "GobMail vX" pill in the pane's footer - and it is deliberately NOT the
   institution's: the crest an operator configures in the branding settings is a different mark, shown on
   the sign-in card, and neither stands in for the other.

   Two inks, the same convention the institutional wordmark already uses: the navy-bodied mark on the light
   pane, the pale-bodied one on the dark pane, swapped by the theme the shell stamps on the root. Sizing is
   fixed and flex: none so the mark never shrinks with the pane; the wordmark truncates instead, which is
   what keeps the header intact at the pane's 180px floor. */
.gm-app-mark { width: 24px; height: 24px; flex: none; }
.gm-mark-dark { display: none; }
[data-bs-theme="dark"] .gm-mark-light { display: none; }
[data-bs-theme="dark"] .gm-mark-dark { display: inline-block; }
/* The product wordmark: "Gob" in the page's ink, "Mail" in the institutional teal. Bold and slightly
   tightened, which is what makes two words read as one mark rather than as a sentence in the sidebar.
   Both colours are TOKENS: the first inverts with the theme on its own (a literal dark ink would vanish
   on the dark theme), and the second is the accent the design pass already contrast-checked in both.
   It is text rather than an SVG so it is set in Inter wherever it appears - see wordmarkHTML in app.js. */
.gm-wordmark { font-weight: 700; letter-spacing: -0.02em; }
.gm-wordmark > :first-child { color: var(--gm-text-norm); }
.gm-wordmark > :last-child { color: var(--gm-primary); }
/* Its size at the head of the folder pane, where it sits beside the mark in a 220px column. */
.gm-app-wordmark { font-size: 1rem; }
/* The same product mark at the top of the About dialog, where it is the subject rather than a chrome
   glyph and so is drawn at reading size. It carries gm-mark-light/gm-mark-dark like the pane's, so the
   ink follows the theme through the one rule above rather than a second mechanism. */
.gm-about-mark { width: 56px; height: 56px; }

/* The AREA SWITCHER panel: the mail/contacts/calendar/files/notes areas as a grid of icon-above-label
   tiles, opened from the grid button beside the wordmark. The menu element IS the grid, so every tile is a
   direct child of the role="menu" box; Bootstrap reveals a menu with display:block, so the .show state has
   to restore the grid. minmax(0, 1fr) lets a long label wrap rather than widen its column. */
.gm-apps-menu { width: 15rem; padding: 0.5rem; }
.gm-apps-menu.show { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.25rem; }
.gm-app-tile {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.35rem;
	padding: 0.6rem 0.2rem;
	border-radius: 0.5rem;
	text-align: center;
	/* .dropdown-item is a nowrap single line; a tile's label is two or three words under an icon. */
	white-space: normal;
}
.gm-app-tile .bi { font-size: 1.35rem; line-height: 1; color: var(--bs-primary); }
.gm-app-tile-label { font-size: 0.75rem; line-height: 1.2; }
/* The area the user is already in, marked with aria-current. Styled off the attribute rather than a class
   so the tint and the announced state cannot drift apart. */
.gm-app-tile[aria-current] { background-color: var(--gm-interaction-selected); }

/* THE UNAUTHENTICATED CANVAS: the loading shell, sign-in, forgot-password, reset and the second factor.
 *
 * These are the only screens in the application that are mostly EMPTY - one small card on a full viewport
 * - and a flat fill at that size reads as an unpainted surface. The fill is a diagonal from the RECESSED
 * surface at the top left to the PAGE surface at the bottom right: it is built from the same two tokens
 * that already describe those depths, so it resolves in both themes without a second palette to keep in
 * step, and it can only ever be as strong as the distance between two adjacent steps of the surface stack.
 * That distance is small on purpose. This is a provincial government's mail service; the backdrop's job is
 * to give the card something to sit ON, not to be looked at.
 *
 * It is NOT applied to the mailbox shell. The panes there are solid surfaces edge to edge, so a gradient
 * underneath them would be paint nobody can see - and where it did show through, it would put a moving
 * value behind panes that are deliberately flat.
 */
#app-loading,
#app-error,
.gm-canvas {
	background-image: linear-gradient(135deg, var(--gm-background-lowered) 0%, var(--gm-background-norm) 100%);
}
/* The corner chrome above the card: the PRODUCT's mark and wordmark on the left, and the room the locale
   and theme controls will occupy on the right. The card carries the INSTITUTION - its crest and the
   installation's name - which is what a person is signing in to; the product mark is chrome and belongs in
   the corner, the way it does at the head of the folder pane once they are in. */
.gm-canvas-chrome {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	padding: 1.25rem 1.5rem;
}
.gm-canvas { position: relative; }

/* Raised, not flat. On a gradient a card with no shadow reads as a hole cut in the backdrop rather than a
   sheet lying on it, and the elevation tokens already say how far above the page an elevated surface sits
   - in both themes, where the shadow's colour and its strength are not the same answer. The card wears the
   token rather than Bootstrap's .shadow utility, which is a compiled literal declared !important. */
.gm-login-card {
	width: 420px;
	max-width: 96vw;
	box-shadow: var(--gm-shadow-overlay);
}
/* On the sign-in card the focus ring follows the installation's brand colour instead of Bootstrap's
   default blue, so the one branded surface a user meets before authenticating looks like the institution. */
.gm-login-card .form-control:focus {
	border-color: var(--gm-theme-color, var(--gm-brand-navy));
	box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--gm-theme-color, var(--gm-brand-navy)) 25%, transparent);
}
/* The footer under the card: the guide, a hairline, the release. Muted, small, and never competing with
   the card - it is what you read after you have decided you are in the right place. */
.gm-canvas-footer { color: var(--gm-text-hint); }
.gm-canvas-footer-sep {
	width: var(--bs-border-width);
	height: 0.9em;
	flex: none;
	background-color: var(--gm-border-norm);
}
.gm-beacon { border: 0; }

/* A sender's avatar. object-fit keeps a non-square source (a domain favicon) from stretching, and the
   background shows through only in the moment before it loads. */
.gm-avatar { object-fit: cover; background-color: var(--bs-tertiary-bg); }

/* Raw message source: wrap long header and base64 lines rather than force a horizontal scrollbar the
   width of a MIME boundary, so the headers stay readable. */
.gm-source { white-space: pre-wrap; word-break: break-word; }

/* The mailbox top bar. It wears the institution's colour in BOTH themes, which makes it an INVERTED region
   in the light one: a control inside it that asked for the page's text colour resolved to dark ink on dark
   navy and disappeared. The markup declares the bar `data-bs-theme="dark"`, so every token a control reads
   in here - GobMail's and Bootstrap's alike - answers with the dark theme's value, and the bar looks the
   same in both themes because it IS the same surface in both. Nothing in here needs a rule of its own. */
.gm-topbar {
	height: 40px;
	background-color: var(--gm-theme-color, var(--gm-brand-navy));
	color: rgba(255, 255, 255, 0.9);
}

/* The caret in the top-right user/account menu button - smaller and dimmer than the label beside it. */
.gm-caret { font-size: 0.7em; opacity: 0.7; }

/* User-menu entries: a FIXED-WIDTH icon column so every label starts on the same x regardless of how wide
   the glyph is, which is what makes the list read as a column instead of ragged text. The icon is dimmer
   than the label so it guides the eye without competing with it. The child combinator keeps this off the
   linked-account rows inside #accounts-section, which carry their own layout and an active-account check
   that must not be dimmed. */
#main-menu > .dropdown-item { display: flex; align-items: center; gap: .55rem; }
#main-menu > .dropdown-item .bi { flex: none; width: 1.15em; text-align: center; opacity: .7; }
#main-menu > .dropdown-item:hover .bi, #main-menu > .dropdown-item:focus .bi { opacity: 1; }

/* A drag strip between two panes: a thin, transparent column that lights up on hover and shows the resize
   cursor. makeResizable() writes the new width through the CSSOM on drag. */
.gm-resize-handle { flex: 0 0 5px; cursor: col-resize; background-color: transparent; }
.gm-resize-handle:hover { background-color: var(--bs-border-color); }
/* While a drag is in progress, suppress text selection and keep the resize cursor everywhere. */
body.gm-resizing { cursor: col-resize; user-select: none; }

/* Mailbox panes. Widths and the folder indent are classes, not inline styles: style-src carries no
   'unsafe-inline', so a style attribute is silently dropped by the browser. The indent is a custom
   property because it is per-row data (the folder's depth), which a static class cannot express. */
.gm-folder-pane { width: 220px; background-color: var(--gm-background-weak); }
.gm-list-pane   { width: 380px; }

/* Reading layout - the ux.readingLayout preference, applied as a data-layout attribute on the split rather
   than a style attribute (style-src carries no 'unsafe-inline', so a style attribute is dropped).
   vsplit: the message reads BESIDE the list. hsplit: BELOW it. list: the list fills the pane on its own and
   an opened message takes the whole pane over, since there is no second column to hold it - the split then
   carries gm-reading, which hides the list and reveals the message, and a "back" control returns. */
.gm-split { display: flex; min-height: 0; }
.gm-split[data-layout="vsplit"] { flex-direction: row; }
.gm-split[data-layout="hsplit"] { flex-direction: column; }
.gm-split[data-layout="hsplit"] .gm-list-pane {
	width: auto;
	height: 45%;
	border-inline-end: 0 !important;
	border-bottom: var(--bs-border-width) solid var(--bs-border-color);
}
.gm-split[data-layout="list"] { flex-direction: column; }
.gm-split[data-layout="list"] .gm-list-pane { width: auto; flex: 1 1 auto; border-inline-end: 0 !important; }
.gm-split[data-layout="list"] #gm-message-view { display: none; }
.gm-split[data-layout="list"].gm-reading .gm-list-pane { display: none; }
.gm-split[data-layout="list"].gm-reading #gm-message-view { display: flex; }

/* The "back to the list" control only has a place in list mode, where the message replaced the list. In a
   split there is no going back - the list never left - so it stays hidden. */
.gm-msg-back { display: none; }
.gm-split[data-layout="list"] .gm-msg-back { display: inline-flex; }

/* A flex child will not shrink below its content's intrinsic width unless it is told it may, and that one
   missing declaration is why long text pushes a pane wider instead of being truncated. Anything that has
   to be able to ellipsis inside a flex row carries this. */
.gm-minw0 { min-width: 0; }

/* The folder row: [fold control] [the folder] [its menu]. The DEPTH INDENT lives on the row rather than on
   the folder button, so a folder that has children and one that does not start their names on the same x -
   the fold control's column is held open by a spacer on a leaf. */
.gm-folder-row { padding-left: calc(var(--gm-folder-depth, 0) * 0.9rem); }
.gm-folder-toggle { width: 1.15rem; line-height: 1; text-align: center; color: var(--bs-secondary-color); }
.gm-folder-toggle:hover { color: var(--bs-body-color); }
/* min-width:0 is what lets the folder NAME truncate. The unread count never does: a truncated number is a
   different number, and the whole reason the badge exists is that its value is exact. */
.gm-folder { min-width: 0; padding-left: 0.35rem; }
.gm-folder.active { background-color: var(--gm-interaction-selected); }

/* The element a right-click positions the folder menu against. Bootstrap anchors a dropdown to an ELEMENT
   and a pointer is not one, so this zero-size box is moved to the pointer and stands in for it. It is
   fixed-positioned because that is the coordinate space clientX/clientY are in. */
.gm-menu-anchor { position: fixed; width: 0; height: 0; pointer-events: none; }

/* A menu entry's icon column: a fixed width so every label starts on the same x whatever the glyph. */
.gm-menu-icon { flex: none; width: 1.15em; text-align: center; opacity: 0.7; }
.dropdown-item:hover .gm-menu-icon, .dropdown-item:focus .gm-menu-icon { opacity: 1; }

/* The reading pane's overflow menu holds two runs of timed choices plus everything else, so it scrolls
   rather than running off a short window. */
.gm-overflow-menu { max-height: 70vh; overflow-y: auto; }

/* A destructive entry is set apart by more than its colour. Colour is the one channel a reader may not
   have - it is the first thing a high-contrast mode, a greyscale screen or a colour-blind eye loses - so
   the entry also carries a rule down its leading edge and a heavier label, and its hover is the danger
   tint rather than the neutral one every other entry gets. The rule is drawn as an inset shadow so that
   marking an entry dangerous never shifts its words by two pixels out of line with the rest. */
.gm-menu-danger { box-shadow: inset 2px 0 0 var(--gm-danger); color: var(--gm-danger); font-weight: 500; }
.gm-menu-danger:hover, .gm-menu-danger:focus { background-color: var(--gm-danger-minor-2); color: var(--gm-danger); }

.gm-msg-row { padding: 0.5rem 0.75rem; min-width: 0; }
.gm-msg-row.active { background-color: var(--gm-interaction-selected); }

/* A conversation's fold control, and the messages it reveals. The members are indented and tinted so the
   list reads as "these belong to the row above" rather than as more top-level mail. */
.gm-thread-toggle { width: 1.15rem; line-height: 1; text-align: center; color: var(--bs-secondary-color); }
.gm-thread-toggle:hover { color: var(--bs-body-color); }
.gm-thread-children { background-color: var(--gm-background-weak); }
.gm-msg-child { padding-left: 1.25rem; }

/* The row's three controls - select, open, star - sit on one line, and the middle one takes whatever is
   left. min-width:0 on the button is what lets its text truncate instead of stretching the row. */
.gm-msg-item { position: relative; }
.gm-msg-item:hover { background-color: var(--gm-interaction-hover); }
.gm-msg-star { padding: 0.5rem 0.6rem; }

/* The row's right-hand controls: the persistent state toggles (star, pin) in flow, and the transient
   quick actions overlaid beside them. The container is the transient cluster's positioning context, so
   `right: 100%` puts that cluster immediately to the LEFT of the star - it can never cover it. Anchored
   to the row instead, it landed on top of the star and the pin and swallowed their clicks, which made a
   starred message impossible to un-star with a pointer. */
.gm-msg-controls { position: relative; }

/* Quick actions overlay the row's text on hover (or keyboard focus), so they cost no horizontal space
   when idle and never squeeze the subject. A pointer device reveals them on hover; a keyboard reveals
   them on focus-within; the bulk bar covers the no-hover/touch path. */
.gm-msg-actions {
	position: absolute;
	top: 0;
	right: 100%;
	display: flex;
	gap: 0.05rem;
	padding: 0.1rem;
	border-radius: var(--gm-radius-sm);
	background: var(--gm-background-elevated);
	box-shadow: var(--gm-shadow-elevated);
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.1s ease;
}
.gm-msg-item:hover .gm-msg-actions,
.gm-msg-item:focus-within .gm-msg-actions { opacity: 1; pointer-events: auto; }
.gm-msg-action:hover .bi { color: var(--bs-body-color) !important; }

/* A message being dragged to a folder, and a folder lit as a valid drop target. */
.gm-msg-item.gm-dragging { opacity: 0.5; }
.gm-folder-row.gm-drop-target { background: var(--bs-primary-bg-subtle); outline: 1px dashed var(--bs-primary); }

/* Compose: dragging files over the window highlights it as an attach target. */
.gm-drop-active { outline: 2px dashed var(--bs-primary); outline-offset: -6px; background: var(--bs-primary-bg-subtle); }

/* The empty-folder / no-results state: a large muted glyph over the message. */
.gm-empty-state .bi { font-size: 2.5rem; opacity: 0.5; }

/* Per-folder view density (data-density on the stable list pane, toggled from the list header). "compact"
   tightens the rows to scan a busy folder; the default is comfortable. */
.gm-list-pane[data-density="compact"] .gm-msg-row { padding-top: 0.15rem; padding-bottom: 0.15rem; }
.gm-list-pane[data-density="compact"] .gm-msg-check { margin-top: 0.4rem !important; }
.gm-list-pane[data-density="compact"] .gm-msg-item > img { margin-top: 0.35rem !important; }
/* Compact density drops the body-preview line: it trades the excerpt for rows-per-screen, so the two
   view options compose rather than fight. */
.gm-list-pane[data-density="compact"] .gm-msg-preview { display: none; }

/* The pager under the message list. Its controls never shrink and never wrap onto a second row - a pager
   that reflows is a pager whose buttons move under the pointer between one page and the next - so at a
   narrow column the NUMBERS are dropped instead and the position indicator ("3/172") stands in for them.
   Which of the two is showing is decided by gm-narrow, written onto the pane by its own width observer,
   because the column is dragged to any width and no media query can see that. */
.gm-pager { overflow: hidden; }
.gm-page-num { min-width: 2rem; }
.gm-pager .gm-page-at { display: none; }
.gm-list-pane.gm-narrow .gm-pager .gm-page-at { display: inline; }
.gm-list-pane.gm-narrow .gm-page-num,
.gm-list-pane.gm-narrow .gm-page-gap { display: none; }

/* The list header at a narrow column. The pane is dragged as low as 260px, and at that width the row of
   controls no longer fits on its ordinary padding. Nothing is dropped: hiding a control would make a
   capability unreachable at a width the user is free to choose, and the folder's name is what the row is
   FOR, so what gives is the space around the glyphs and between them - chrome rather than function.
   Which width this applies at is gm-narrow, written onto the pane by its own width observer, because no
   media query can see a column the user drags. */
.gm-list-pane.gm-narrow .gm-list-head { gap: 0.125rem; }
.gm-list-pane.gm-narrow .gm-list-head .btn { --bs-btn-padding-x: 0.3rem; }
.gm-list-pane.gm-narrow .gm-list-head .badge { --bs-badge-padding-x: 0.35rem; }

/* A mailbox can have many folders. The move menu scrolls rather than running off the screen. */
.gm-move-menu {
	max-height: 60vh;
	overflow-y: auto;
}

/* The reading pane's expandable details: the message's own header, drawn as a quiet inset panel so it
   reads as evidence about the message rather than as more of the message. */
.gm-msg-details {
	background-color: var(--gm-background-lowered);
	border: var(--bs-border-width) solid var(--gm-border-weak);
	border-radius: var(--gm-radius-md);
	padding: 0.65rem 0.85rem;
}
.gm-details-toggle { color: var(--gm-text-weak); line-height: 1; }
.gm-details-toggle:hover, .gm-details-toggle:focus { color: var(--bs-body-color); }

/* A trust badge explains itself on hover, on focus and to a screen reader, so it is focusable and says
   so with the cursor: it is not a link and it does not navigate, it tells you what the verdict means. */
.gm-trust-badge { cursor: help; }

/* The message body frame. It is an IFRAME on the user-content origin, not markup in this page: a
   stranger's HTML must never share an origin with the session.

   It is sized HERE, by layout, because it cannot be sized by its content. An iframe is a replaced
   element and never grows to fit what it holds, and this one is both cross-origin AND sandboxed without
   allow-same-origin or allow-scripts - so the application cannot measure the document, and the document
   has no script with which to report its own height. That is the sandbox working as intended, and no
   auto-height handshake can be added without giving a stranger's markup something back.

   So the frame FILLS the pane instead of standing at a fixed height. A fixed height is wrong in both
   directions: a short message left a band of pane background below the frame, which read as a grey gap
   under the mail, and a long one scrolled inside a box that was itself inside a scrolling pane. Filling
   removes the gap by construction - there is no leftover space for the pane to show through - and gives
   the body a single scrollbar. The floor only applies when the header and attachment rows have already
   taken the pane, at which point the whole view scrolls. */
#gm-message-view {
	display: flex;
	flex-direction: column;
}
.gm-body-frame {
	width: 100%;
	flex: 1 1 auto;
	min-height: 18rem;
	border: 0;
	display: block;
}

/* Compose. The body is EITHER a Squire rich-text editor (a contenteditable div, the ux.composeFormat
   default) OR a plain textarea, one shown at a time. The html a rich compose emits is sanitised on the
   server the same way inbound mail is (the editor's own cleaning is a convenience the server does not
   trust). Both surfaces share the size. */
/* The body is the point of the window, so it gets the room: a tall floor, and on a scrollable dialog it
   grows to fill whatever height is left after the recipient/subject/toolbar rows. The dialog is a flex
   column (Bootstrap's .modal-dialog-scrollable makes the body scroll); the compose field flexes to fill
   it and the editor/textarea flex within that, so the writing area tracks the viewport instead of sitting
   in a fixed 220px box. */
.gm-compose-body { min-height: 45vh; resize: vertical; }
.gm-compose-dialog .modal-body { display: flex; flex-direction: column; }
.gm-compose-field { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 45vh; }
.gm-compose-field .gm-compose-body { flex: 1 1 auto; resize: none; }
/* The editor is a contenteditable div, so it scrolls rather than resizes and shows a placeholder while
   empty (a contenteditable has no placeholder attribute of its own). */
#compose-editor { overflow-y: auto; }
#compose-editor:empty::before {
	content: attr(data-placeholder);
	color: var(--gm-text-hint);
}
#compose-editor blockquote {
	margin: 0 0 0 0.5rem;
	padding-left: 0.75rem;
	border-left: 2px solid var(--gm-border-norm);
	color: var(--gm-text-weak);
}
.gm-compose-toolbar { gap: 0.25rem; }

/* FIELD MEASURES.
 *
 * A control should be as wide as the data it holds and no wider. A select with four short options does
 * not need the width of the window, and stretched across it the form reads as unfinished. The shape is
 * always `width: 100%` with a `max-width`: it shrinks on a narrow window (so nothing overflows) and never
 * grows past the measure its content justifies.
 *
 * The scale is deliberately generic and lives here rather than in any one screen's block, so compose,
 * settings and the admin forms reach for the SAME four sizes and the product looks like one product. */
:root {
	--gm-field-xs: 8rem;   /* a count, a port, a two-digit scalar */
	--gm-field-sm: 12rem;  /* a time, a date, a short code */
	--gm-field-md: 20rem;  /* a select of short options, a display name */
	--gm-field-lg: 32rem;  /* a long single value: an address, a URL */
}
.gm-field-xs, .gm-field-sm, .gm-field-md, .gm-field-lg { width: 100%; }
.gm-field-xs { max-width: var(--gm-field-xs); }
.gm-field-sm { max-width: var(--gm-field-sm); }
.gm-field-md { max-width: var(--gm-field-md); }
.gm-field-lg { max-width: var(--gm-field-lg); }

/* And the compose window's own floor, so a control ADDED later inherits the discipline instead of
   defaulting to the full width of the dialog. The :not() guard means an explicit measure always wins:
   this rule simply does not match a control that carries one. Address fields, the subject and the body
   are not selects or scalars - they legitimately run the width and are untouched. */
.gm-compose-dialog .form-select:not([class*="gm-field-"]),
.gm-compose-dialog input[type="datetime-local"]:not([class*="gm-field-"]),
.gm-compose-dialog input[type="number"]:not([class*="gm-field-"]) {
	width: 100%;
	max-width: var(--gm-field-md);
}

/* THE COMPOSE WINDOW.
 *
 * It reads as a window rather than a generic dialog: a compact title bar with the window controls, then
 * the addressing as a stack of thin ruled ROWS sharing one label column - not a column of boxed inputs,
 * each with its own border, which is what made the form look like a settings page. The body gets the rest
 * of the height, and the formatting toolbar is docked at its foot, directly above the action bar. */
.gm-compose-header { border-bottom: var(--bs-border-width) solid var(--bs-border-color); }
/* The title is the message's subject once it has one, so it must be able to shrink and ellipsise rather
   than push the window controls off the bar. */
.gm-compose-header .modal-title { min-width: 0; }
/* Bootstrap clips a SCROLLABLE dialog's content, which would cut off every dropdown that opens from the
   action bar - the overflow menu, the templates, send-later. The body keeps its own scrolling; only the
   clip goes. */
.gm-compose-dialog.modal-dialog-scrollable .modal-content { overflow: visible; }
/* The window controls belong at the FAR right of the title bar, the way a window's do - not tucked against
   the end of the title, where minimise and close land under wherever the subject happens to stop and move
   every time it changes. Bootstrap's `.modal-header` is a plain flex row with no `justify-content`: what
   pushes a normal dialog's close cross to the corner is a `margin-left: auto` on `.btn-close` itself. This
   window has its own control group rather than that one button, so it has to say the same thing. */
.gm-compose-window-controls { margin-left: auto; }
.gm-compose-window-controls .btn {
	color: var(--bs-secondary-color);
	padding: 0.125rem 0.375rem;
	line-height: 1;
	text-decoration: none;
}
.gm-compose-window-controls .btn:hover,
.gm-compose-window-controls .btn:focus-visible { color: var(--bs-body-color); }

.gm-compose-rows { border-bottom: var(--bs-border-width) solid var(--gm-border-norm); }
.gm-compose-row {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	min-height: 2.5rem;
	padding: 0.125rem 0;
	border-top: var(--bs-border-width) solid var(--gm-border-weak);
}
.gm-compose-row:first-child { border-top: 0; }
.gm-compose-label {
	flex: 0 0 auto;
	width: 6rem;
	margin: 0;
	font-size: 0.875rem;
	color: var(--gm-text-weak);
}
/* min-width:0 is what stops a long recipient list from pushing the row wider than the dialog: without it
   a flex item refuses to shrink below its content and the window scrolls sideways. */
.gm-compose-value { flex: 1 1 auto; min-width: 0; }
.gm-compose-row-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 0.125rem; }
.gm-compose-row-toggle {
	padding: 0 0.25rem;
	font-size: 0.8125rem;
	text-decoration: none;
	color: var(--bs-secondary-color);
}
.gm-compose-row-toggle:hover { color: var(--bs-body-color); }
.gm-compose-row-toggle.active { color: var(--bs-primary); font-weight: 600; }
.gm-compose-row-icon { padding: 0 0.25rem; text-decoration: none; }

/* A field inside a row wears the ROW's rule, not a box of its own. */
.gm-compose-flat {
	border: 0;
	background: transparent;
	box-shadow: none;
}
/* Only a field IN A ROW gives up its horizontal padding, and only because the shared label column already
   sets the value in from the edge: left as .form-control ships it, the value would sit a further inset to
   the right of the label and no two rows would start on the same line.
 *
 * The BODY is not in a row. It has no label column to align to, and its focus ring is drawn INSIDE its own
 * box (outline-offset is negative, so the ring cannot spill past the dialog edge) - so with the padding
 * stripped there, the ring is painted over the first glyph of the first line and covers it. It keeps the
 * inset .form-control gives it, which is also what holds the empty-state placeholder off the edge. */
.gm-compose-row .gm-compose-flat {
	padding-left: 0;
	padding-right: 0;
}
/* Inside a ROW the focus ring is dropped, because the row itself shows the focus - a ring drawn around a
   box that has no border reads as a stray rectangle. The body is NOT in a row and keeps its own ring:
   removing that one would leave a keyboard user with no indication of where they are. */
.gm-compose-row .gm-compose-flat:focus,
.gm-compose-row .gm-compose-flat:focus-visible {
	border: 0;
	background: transparent;
	box-shadow: none;
	outline: 0;
}
.gm-compose-row:focus-within { box-shadow: inset 0 -2px 0 0 var(--bs-primary); }
.gm-compose-field .gm-compose-flat:focus-visible { outline: 2px solid var(--bs-primary); outline-offset: -2px; }

/* The docked formatting toolbar. It scrolls sideways within itself on a narrow window so the dialog
   never does. */
.gm-compose-toolbar-dock {
	margin-top: 0.5rem;
	padding-top: 0.5rem;
	border-top: var(--bs-border-width) solid var(--bs-border-color);
	overflow-x: auto;
	flex-wrap: nowrap;
}

/* The action bar: the icon actions on the left, the draft status and the split Send on the right. A dialog
   has no rule above its footer - the whitespace separates it - but this is a WINDOW, and its docked bar is
   part of the chrome, so it keeps the rule its title bar has. */
.gm-compose-actions {
	flex-wrap: nowrap;
	gap: 0.5rem;
	border-top: var(--bs-border-width) solid var(--gm-border-norm);
}
.gm-compose-actions .btn-link { text-decoration: none; }
.gm-compose-actions .btn-link.active { color: var(--bs-primary); }
/* Send is the one control in this bar that must never be squeezed or pushed off it. */
.gm-compose-actions .btn-group { flex: 0 0 auto; }

/* THE CLASSIFICATION, beside Send.
 *
 * Sensitivity is a property of the message at the moment it is SENT, like the encrypt and sign toggles it
 * now sits with - not an addressing field. It held a full header row above the body, with a select the
 * width of the window, for a control most messages never touch; the writing area started that much lower
 * on every compose.
 *
 * Two states, and the difference between them is the point. Unclassified is the default and the common
 * case, so it is quiet: no border, no fill, the weak text - present, readable, and not asking for
 * attention. A message that HAS been classified is the opposite: in this institution the label is the
 * whole reason the field exists, and a control that let "Confidencial" look like a placeholder would be
 * worse than the row it replaced. It takes the warning ramp's tint, edge and text, and the weight goes to
 * semibold - and the word itself is the primary channel, so the state does not rest on colour alone.
 */
.gm-compose-actions .gm-compose-sensitivity { flex: 0 1 auto; min-width: 0; }
.gm-compose-dialog .gm-compose-sensitivity .form-select {
	width: 100%;
	max-width: 12rem;
	padding-left: 0.5rem;
	border-color: transparent;
	background-color: transparent;
	color: var(--gm-text-weak);
}
.gm-compose-dialog .gm-compose-sensitivity .form-select:hover { border-color: var(--gm-border-norm); }
.gm-compose-dialog .gm-compose-sensitivity .form-select.gm-classified {
	border-color: var(--gm-warning-minor-1);
	background-color: var(--gm-warning-minor-2);
	color: var(--gm-warning);
	font-weight: 600;
}
.gm-compose-sep {
	width: var(--bs-border-width);
	align-self: stretch;
	margin: 0.25rem 0.25rem;
	background-color: var(--bs-border-color);
}
/* A menu item that carries an on/off state shows it where a menu shows it: a tick in a reserved gutter,
   so the labels stay aligned whether or not the item is on. */
.gm-compose-check { padding-left: 2rem; position: relative; }
.gm-compose-check[aria-checked="true"]::before {
	content: "\2713";
	position: absolute;
	left: 0.75rem;
}

/* A minimised compose is out of the way but not gone: the message keeps every value it had, and this bar
   is what brings the window back. */
.gm-compose-min {
	position: fixed;
	right: 1rem;
	bottom: 1rem;
	z-index: 1080;
	max-width: 22rem;
}
.gm-compose-min .btn-link { text-decoration: none; }

/* Narrow viewports: the label column gives up width before the field does, and the action bar wraps
   rather than pushing Send off the edge. */
@media (max-width: 575.98px) {
	.gm-compose-label { width: 3.5rem; font-size: 0.8125rem; }
	.gm-compose-actions { flex-wrap: wrap; }
	/* The classification gives up width before Send does; the word it carries is still the first thing in
	   the box, and the full value is in the control's accessible name either way. */
	.gm-compose-dialog .gm-compose-sensitivity .form-select { max-width: 8.5rem; }
	.gm-compose-min { left: 1rem; max-width: none; }
}

/* The send confirmation. Fixed rather than in the flow, so it does not push the mailbox around. */
.gm-toast {
	position: fixed;
	right: 1rem;
	bottom: 1rem;
	z-index: 1080;
	margin: 0;
}

/* THE UNDO-SEND COUNTDOWN: the seconds still left on the hold, and a bar that drains with them.
 *
 * It appears in two places that are coloured nothing alike - the send toast (a tinted success alert) and a
 * row of the outbox window (the page's own surface) - and it has to read in both, in both themes. So it
 * borrows rather than declares: the bar is drawn in `currentColor`, the colour of the words it stands
 * beside, and the track it drains along is the interaction overlay, which is translucent for exactly this
 * reason - one value that is correct over a tint, over the page and over an elevated surface alike.
 *
 * The width is written from script as the time runs down, so there is no CSS transition on it: a bar
 * easing towards a value it is about to be given again would lag the number next to it, and a countdown
 * whose two halves disagree is worse than either on its own. That also makes it honest under reduced
 * motion, where it is not an animation at all - it is a reading, taken ten times a second.
 */
.gm-countdown-track {
	display: inline-block;
	width: 3rem;
	height: 0.25rem;
	border-radius: var(--bs-border-radius-pill);
	background-color: var(--gm-interaction-active);
	overflow: hidden;
}
.gm-countdown-fill {
	display: block;
	height: 100%;
	background-color: currentColor;
}

/* The recipient editor. A recipient is a chip, not text: an address and a distribution LIST are not the
   same thing, and a list has no address to render as text. Flattening them into a string would lose the
   list id, which is the only thing that identifies it. */
.gm-recipient-box { min-height: 38px; cursor: text; }
/* The chip input is a bare <input> inside the themed .form-control box, so it must be transparent and
   inherit the box's colour - otherwise it keeps the UA field background and renders as a white box in dark
   mode, out of step with the subject field beside it. */
.gm-recipient-input { outline: none; min-width: 8rem; background: transparent; color: inherit; }
.gm-suggest-menu:not([hidden]) { display: block; max-height: 16rem; overflow-y: auto; }

/* A preference's control is given a fixed, modest width so the labels line up down the panel and a long
   text value does not push the switch off the row. */
.gm-pref-control {
	width: 20rem;
	max-width: 60%;
}

/* The search box takes the width the topbar can spare, but never so much that the address and the sign-out
   control are pushed off it. */
.gm-search-box { max-width: 28rem; }
.gm-search-field { width: 11rem; }

/* While a search is running the list is NOT the folder. A left edge says so without a banner stealing a
   row of the list to say it. */
.gm-searching { box-shadow: inset 3px 0 0 0 var(--bs-primary); }

/* A saved search sits in the folder pane because that is where the user looks for a place to go, and a
   saved search is the nearest thing to a place a query has. Its delete control only appears on hover, so
   a list of them does not read as a row of buttons. */
.gm-saved-remove { opacity: 0; }
.gm-saved:hover .gm-saved-remove,
.gm-saved-remove:focus-visible { opacity: 1; }

/* A rule row wraps rather than scrolls: a condition is a sentence, and a sentence that runs off the edge
   of its box is one the user cannot check before saving. */
.gm-rule-field { width: 11rem; }
.gm-rule-row > select { width: auto; min-width: 8rem; }

/* A backup code and a TOTP secret are things people copy by hand. They are set in a monospace face, kept
   selectable, and allowed to wrap - a code that runs off the edge of its box is a code that gets copied
   wrong, and a code copied wrong is a lockout. */
.gm-backup-code {
	padding: 0.15rem 0.4rem;
	border: 1px solid var(--bs-border-color);
	border-radius: 0.25rem;
	user-select: all;
}
.gm-totp-secret {
	font-size: 1.1rem;
	user-select: all;
}
.gm-totp-uri {
	word-break: break-all;
	user-select: all;
}

/* The enrolment QR. A fixed size, because a QR that is scaled by its container is a QR whose modules blur
   into each other and stops scanning. */
.gm-totp-qr {
	width: 16rem;
	height: 16rem;
	image-rendering: pixelated;
}

/* Armored key material and fingerprints: monospace, and allowed to wrap. A fingerprint that is cut off is
   a fingerprint that cannot be compared, which is the only thing a fingerprint is for. */
.gm-armored {
	font-family: var(--bs-font-monospace);
	font-size: 0.75rem;
}
.gm-fingerprint {
	font-family: var(--bs-font-monospace);
	user-select: all;
}

/* An import's progress bar. Short, because it is a status line and not the point of the screen. */
.gm-import-bar { height: 0.4rem; }
.gm-import-port { width: 7rem; }

/* The folder's action menu appears on hover, so a list of folders reads as a list of places rather than a
   column of buttons. It stays visible when focused, or it would be unreachable by keyboard. */
.gm-folder-menu { opacity: 0; }
.gm-folder-row:hover .gm-folder-menu,
.gm-folder-menu:focus-within { opacity: 1; }
.gm-folder-row .gm-folder { flex: 1 1 auto; min-width: 0; }

/* How full the mailbox is. A thin bar: it is a fact, not a feature. */
.gm-quota-bar { height: 0.4rem; }

/* A tag on a message row is a dot, not a word. Five tags spelled out would leave no room for the subject,
   which is the thing the row is for. The word is in the tooltip and in the menu. */
.gm-tag-dot {
	display: inline-block;
	width: 0.55rem;
	height: 0.55rem;
	border-radius: 50%;
	flex-shrink: 0;
}

/* A contact's value type ("work", "home"): narrow, because it is a hint and not the value. */
.gm-contact-type { width: 6rem; }

/* The month grid. Seven columns, because a week has seven days and a calendar that reflows on a narrow
   screen stops being a calendar. It scrolls sideways instead - a squashed month is unreadable, and an
   unreadable month is worse than one you have to scroll. */
.gm-cal-grid {
	display: grid;
	grid-template-columns: repeat(7, minmax(6.5rem, 1fr));
	gap: 1px;
	background-color: var(--bs-border-color);
	border: 1px solid var(--bs-border-color);
	overflow-x: auto;
}
.gm-cal-head {
	background-color: var(--bs-tertiary-bg);
	padding: 0.25rem 0.4rem;
	text-align: center;
}
.gm-cal-day {
	background-color: var(--bs-body-bg);
	min-height: 6rem;
	padding: 0.25rem;
	cursor: pointer;
}
.gm-cal-day:hover { background-color: var(--bs-tertiary-bg); }

/* A day outside the month is drawn, because the week it belongs to is on screen - but faintly, or the eye
   cannot find the month it came here for. */
.gm-cal-other { opacity: 0.45; }
.gm-cal-today { box-shadow: inset 0 0 0 2px var(--bs-primary); }

.gm-cal-event {
	display: block;
	width: 100%;
	text-align: left;
	border: 0;
	border-radius: 0.2rem;
	background-color: var(--bs-primary-bg-subtle);
	font-size: 0.75rem;
	padding: 0.1rem 0.25rem;
	margin-top: 0.15rem;
}
.gm-cal-month { min-width: 9rem; text-align: center; }

/* The time grid (week and day): a fixed header and all-day band over a scrollable 24-hour body. The hour
   gutter and each day column share the same pixel height (set from script, HOUR_PX * 24), so a gridline in
   the gutter lines up with the same hour in every column. Event blocks are absolutely positioned within
   their column - top and height from the time, left and width from the overlap packing. */
.gm-cal-tg { display: flex; flex-direction: column; height: 100%; min-height: 0; border: 1px solid var(--bs-border-color); }
.gm-cal-tg-header, .gm-cal-tg-allday { display: flex; border-bottom: 1px solid var(--bs-border-color); }
.gm-cal-tg-gutter { width: 3.5rem; flex-shrink: 0; }
.gm-cal-tg-dayhead {
	flex: 1 1 0;
	min-width: 0;
	text-align: center;
	padding: 0.25rem;
	border-left: 1px solid var(--bs-border-color);
}
.gm-cal-tg-allday .gm-cal-tg-gutter { display: flex; align-items: center; padding-left: 0.25rem; }
.gm-cal-tg-alldaycell {
	flex: 1 1 0;
	min-width: 0;
	padding: 0.15rem;
	border-left: 1px solid var(--bs-border-color);
}
.gm-cal-tg-body { display: flex; flex: 1 1 auto; overflow-y: auto; }
.gm-cal-tg-hours { position: relative; }
.gm-cal-tg-hour { position: absolute; right: 0.3rem; transform: translateY(-50%); }
.gm-cal-tg-col {
	position: relative;
	flex: 1 1 0;
	min-width: 0;
	border-left: 1px solid var(--bs-border-color);
	cursor: pointer;
}
.gm-cal-tg-todaycol { background-color: var(--bs-primary-bg-subtle); }
.gm-cal-tg-line { position: absolute; left: 0; right: 0; border-top: 1px solid var(--bs-border-color); opacity: 0.4; }
.gm-cal-tg-event {
	position: absolute;
	overflow: hidden;
	box-sizing: border-box;
	text-align: left;
	font-size: 0.72rem;
	line-height: 1.1;
	padding: 1px 3px;
	border: 1px solid var(--bs-primary);
	border-radius: 3px;
	background-color: var(--bs-primary-bg-subtle);
	color: var(--bs-primary-text-emphasis);
}

/* A share link is a thing people copy. It wraps rather than running off the edge, and it selects whole,
   because a link copied by hand and missing its last three characters is a link that silently does not
   work. */
.gm-share-link {
	display: block;
	word-break: break-all;
	user-select: all;
	margin-top: 0.25rem;
}

/* A note is written in, not filled in. It gets room. */
.gm-note-body { min-height: 18rem; }

/* Password reveal toggle. app.js wraps each password input in a .pw-field span (carrying the input's
   margin/width utilities so it keeps the same slot) and overlays an eye button on the input's right edge;
   the input gains matching right padding so its text never slides under the button. Sized in em so it
   tracks a form-control-lg field and a form-control-sm one alike. */
.pw-field { position: relative; display: block; }
.pw-field > input { width: 100%; padding-right: 2.5em; }
.pw-toggle {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	display: flex;
	align-items: center;
	padding: 0 .6em;
	border: 0;
	background: transparent;
	color: var(--gm-text-hint);
	cursor: pointer;
	line-height: 1;
	z-index: 3;
}
.pw-toggle:hover { color: var(--gm-text-norm); }
.pw-toggle:focus-visible { outline: 2px solid var(--gm-focus-outline); outline-offset: -2px; }

/* ---------------------------------------------------------------------------------------------------
   CONTAINMENT: a pane that is dragged narrow must DEGRADE, not spill.

   Both side panes are user-resizable, and every overflow bug they produce has the same cause: a flex
   child will not shrink below its content's intrinsic width unless it is told it may. Without
   `min-width: 0` on the child, `text-overflow: ellipsis` on its text never fires - the text simply
   pushes the box wider than the pane and out of it. So truncation is established HERE, on the
   containers, rather than per element as each new overflow is noticed.
   --------------------------------------------------------------------------------------------------- */

/* The panes and the columns between them. */
.gm-folder-pane, .gm-list-pane, #gm-message-view, .gm-topbar { min-width: 0; }
.gm-folder-row, .gm-msg-item, .gm-msg-row, .gm-msg-row > div { min-width: 0; }

/* The folder pane's footer strip. The quota line and the version pill are the two things that were
   spilling out of a narrowed pane: both may shorten, neither may push the pane. */
#quota-box, #quota-box > div { min-width: 0; }
#quota-box .gm-quota-used { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#app-version { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A toolbar shrinks or wraps; it never widens its pane. The list header holds a title that may truncate
   and a run of icon buttons that may not, so the title takes the loss. */
#message-actions { flex-wrap: wrap; }
#list-title { min-width: 0; }

/* The file browser's path trail. Each crumb CHANGES FOLDER - it is an action, not a URL - so it is a
   quiet button rather than link-coloured text, and a long folder name in the middle of a deep path
   shortens instead of pushing the trail off the pane. */
.gm-crumb { max-width: 12rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------------------------------------------------------------------------------------------------
   FORM MEASURE: an input is as wide as the value it holds, not as wide as the pane.

   A full-width password field is ~800px of box for ~12 characters of content: it reads as unfinished,
   and the eye has to cross the whole pane to get from a label to its field. The measure is set by TYPE,
   so a form written tomorrow inherits it without anyone remembering to. `max-width` with `width: 100%`
   is the shape that also satisfies the containment rule above - the field shrinks with a narrow pane and
   never exceeds its measure on a wide one.

   A control that genuinely holds something long opts out: a textarea, a URL, a PEM paste, the search
   box, and anything inside an .input-group (which is sized by the group, not the field).
   --------------------------------------------------------------------------------------------------- */
.modal-body .form-control,
.modal-body .form-select {
	width: 100%;
	max-width: 32rem;
}
/* Bounded values: a name, a subject line, an address. */
.modal-body input[type="text"].form-control,
.modal-body input[type="email"].form-control,
.modal-body input[type="password"].form-control,
.modal-body input[type="search"].form-control,
.modal-body .form-select { max-width: 26rem; }
/* Values with a known, short shape. */
.modal-body input[type="date"].form-control,
.modal-body input[type="time"].form-control,
.modal-body input[type="number"].form-control { max-width: 12rem; }
/* Values that are genuinely long, or sized by something else. */
.modal-body textarea.form-control,
.modal-body input[type="url"].form-control,
.modal-body .input-group .form-control,
.modal-body .input-group .form-select,
.modal-body .gm-wide-field { max-width: none; }
/* A password field is wrapped in .pw-field for the reveal toggle, so the MEASURE belongs to the wrapper -
   the input inside it is width:100% of that slot and must not be capped twice. */
.modal-body .pw-field { width: 100%; max-width: 26rem; }
.modal-body .pw-field > input.form-control { max-width: none; }

/* The composer is exempt in full. It is not a form being filled in - it is a message being written, and
   a recipient list or a subject line is as long as the writer needs it to be. */
.gm-compose-dialog .form-control,
.gm-compose-dialog .form-select,
.gm-compose-dialog .pw-field { max-width: none; }

/* Paired fields read as a form rather than a stack of bars, and fall back to one column when there is no
   room for two. */
.gm-field-pair {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
	gap: 0.75rem;
	max-width: 40rem;
}
/* Inside a pair the field fills its column: the pair carries the measure, so capping the field again
   would leave it short of the column it was given. */
.modal-body .gm-field-pair .form-control,
.modal-body .gm-field-pair .form-select,
.modal-body .gm-field-pair .pw-field { max-width: none; }

/* The bulk-selection toolbar, at content level across the full width. It appears when a selection exists
   and disappears when it does not, and the fade-in keeps that from reading as the page jumping. Its
   controls are glyphs named by title and aria-label, so the bar carries the verbs people reach for -
   report as spam among them - at any width the content area is dragged to, rather than trading them for
   a menu the moment the words stop fitting. */
.gm-selection-bar { animation: gm-bar-in 0.12s ease-out; }
@keyframes gm-bar-in {
	from { opacity: 0; transform: translateY(-0.25rem); }
	to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
	.gm-selection-bar { animation: none; }
}

/* The settings window: sections down the left, the active pane on the right. The nav collapses to its
   icons on a narrow viewport rather than squeezing the pane - the same containment rule as the panes,
   applied to the one control strip that would otherwise take the room the content needs. */
/* The settings dialog is a FIXED size, whatever section is open. A min-height let a short section sit
   at 60vh and a long one grow past it, so the dialog jumped every time the user changed section - and
   the section list moved under the pointer that was still on its way to the next entry. A constant
   height costs nothing (the tall sections scrolled anyway) and makes the nav a stable target.

   Bounded rather than simply 100%: on a tall screen a full-height settings dialog is a wall.

   The dialog no longer scrolls as one piece. The NAV stays put and the PANE scrolls, which is what a
   two-column settings dialog should do - scrolling the section list out of reach to read a long
   section is how you lose your place. Bootstrap's .modal-dialog-scrollable rule that would scroll the
   whole body is overridden here by class, so the DAV section's own nested .modal-body still scrolls
   normally. */
.gm-prefs-body {
	height: min(72vh, 44rem);
	overflow: hidden;
}
.gm-prefs-body > #prefs-pane { overflow-y: auto; }
.gm-prefs-nav {
	width: 13rem;
	padding: 0.5rem;
	background-color: var(--gm-background-weak);
	overflow-y: auto;
}
.gm-prefs-tab { color: var(--gm-text-norm); border-radius: var(--gm-radius-sm); }
.gm-prefs-tab:hover { background-color: var(--gm-interaction-hover); }
.gm-prefs-tab.active { background-color: var(--gm-interaction-selected); font-weight: 600; }
.gm-prefs-tab .bi { flex: none; width: 1.15em; text-align: center; opacity: 0.7; }
.gm-prefs-tab.active .bi { opacity: 1; }
/* A pane scrolls itself (see .gm-prefs-body), and its focus outline is the one a section switch lands
   on. */
.gm-prefs-section { min-width: 0; }
/* A section whose heading only repeated the window's own name has none, and starts at its content (see
   prefsSection in app.js). It takes the space that heading occupied, so it begins on the same line as the
   sections that kept theirs and the removal reads as a decision rather than as a missing element. */
.gm-prefs-section > .modal-body:first-child { padding-top: 1.75rem; }
.gm-prefs-section:focus { outline: none; }
.gm-prefs-section:focus-visible { outline: 2px solid var(--gm-focus-outline); outline-offset: -2px; }
@media (max-width: 768px) {
	.gm-prefs-nav { width: 3.25rem; }
	.gm-prefs-tab { justify-content: center; }
	.gm-prefs-tab-label { display: none; }
}
