/* ============================================================================
   OFFICE NORD — design system and app shell
   ----------------------------------------------------------------------------
   Mode: OPERATE. The user should complete a task without noticing the
   interface. Every decision here trades expression for throughput — the
   opposite trade from site.css, made from the same tokens.

   The shell is a Microsoft 365 register, because the people we are asking to
   switch have thirty years of muscle memory in that shape and we do not want
   to charge them for re-learning it. Six mechanisms carry it:

     1. THE SUITE BAR. A 48px band across the FULL width, above everything,
        carrying the app launcher, the product name, one centred search, and
        account chrome. It is the difference between "a web app" and "a
        suite" — it asserts that sibling applications exist and this is one.
     2. THE WAFFLE. A 3x3 dot grid at the far left opening a flyout of those
        siblings. First in the DOM, first in the tab order.
     3. A 48px ICON RAIL expanding to 240px with labels. Icons at rest.
     4. A COMMAND BAR of verbs above the content, per view.
     5. 4px RADII AND 32px CONTROLS — Fluent's proportions. Muscle memory is
        measured in pixels and these are the pixels.
     6. ROWS THAT REVEAL ACTIONS ON HOVER. Four permanent buttons per row is
        control noise at forty rows.

   Where we differ on purpose: the suite bar is DEEP PURPLE in light mode, the
   brand's one permanent surface, exactly as classic Office used each app's own
   colour. In dark it drops to a neutral surface, because a saturated band
   against a near-black canvas is a glare source at night.

   Purple acts. Deep purple grounds. Gold and teal stay rare. Most screens are
   purple plus greyscale; that restraint is what lets four saturated hues read
   as considered rather than busy.

   HARD CONSTRAINT — nothing phones home. Fonts are self-hosted. A product
   selling Canadian data residency cannot send every visitor's IP to a font CDN
   on page load. This is architecture, not preference.
   ========================================================================= */

/* --------------------------------- Fonts -------------------------------- */
@font-face {
  font-family: 'Geist';
  src: url('/fonts/Geist-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Geist';
  src: url('/fonts/Geist-Medium.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: 'Geist';
  src: url('/fonts/Geist-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: 'Geist';
  src: url('/fonts/Geist-Bold.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('/fonts/GeistMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

/* ============================== 1. TOKENS ================================ */
:root {
  /* -- Brand --------------------------------------------------------------
     PURPLE IS THE ONLY INTERACTIVE COLOUR. Every button, link, focus ring,
     selection and active state is this one hue. White on #6f27c9 measures
     7.48:1 — AA for body, AAA for large — so the rule costs nothing. */
  --purple: #6f27c9;
  --purple-deep: #3d1e7b;
  --teal: #0f7d7d;
  --gold: #c6a64f;

  /* Alpha rather than mixed hex, so one token works over any ground instead
     of needing a variant per surface. */
  --purple-wash: color-mix(in srgb, var(--purple) 8%, transparent);
  --purple-edge: color-mix(in srgb, var(--purple) 32%, transparent);

  /* -- Ground, light ------------------------------------------------------ */
  --canvas: #ffffff;
  --surface: #fafafa;
  --surface-sunk: #f4f4f5;
  --line: #e7e7ea;
  --line-strong: #d4d4d9;

  /* Ink ladder — every step ≥4.5:1 on its ground, so any of the three may
     carry body copy. 18.5:1 / 6.87:1 / 5.11:1. Near-black is warmed a few
     degrees toward purple: it belongs to the brand at a level nobody
     consciously registers. */
  --ink: #141317;
  --ink-soft: #5b5964;
  --ink-faint: #6f6c7c;

  /* On the deep-purple suite bar, secondary text is tinted FROM THE GROUND'S
     OWN HUE at 5.00:1. Grey on a coloured surface reads as dirt. */
  --on-brand: #ede9f7;
  --on-brand-soft: #a99cd0;
  /* Ink for anything filled with --purple. Flips in dark; see §2. */
  --on-purple: #ffffff;

  --ok: var(--teal);
  --warn: #9a6b00;
  --danger: #b3261e;

  /* -- Radius -------------------------------------------------------------
     Five steps. The app lives at the small end; a single radius everywhere is
     the flattest possible tell that nothing was decided. */
  --r-xs: 4px;   /* controls, chips, fields — Fluent's own default */
  --r-sm: 6px;   /* cards, menus */
  --r-md: 10px;  /* dialogs, tiles */
  --r-lg: 16px;  /* large panels */
  --r-pill: 999px;

  /* -- Elevation ----------------------------------------------------------
     Offset AND blur, always. Light comes from above; a zero-offset coloured
     halo describes no physical situation at all. */
  --lift-1: 0 1px 2px rgb(20 19 23 / 0.06), 0 1px 1px rgb(20 19 23 / 0.04);
  --lift-2: 0 4px 12px rgb(20 19 23 / 0.08), 0 1px 3px rgb(20 19 23 / 0.06);
  --lift-3: 0 16px 40px rgb(20 19 23 / 0.16), 0 4px 10px rgb(20 19 23 / 0.08);

  /* -- Space --------------------------------------------------------------- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px;
  --s6: 32px; --s7: 48px; --s8: 64px;

  /* -- Shell geometry -----------------------------------------------------
     M365's proportions, near enough that a switching user's muscle memory
     survives. */
  --suitebar-h: 48px;
  --rail-w: 48px;
  --rail-w-open: 232px;

  /* -- Density ------------------------------------------------------------
     One scale switched at the root. Governs LIST ROWS ONLY — reading surfaces
     keep their air at both settings, because a compact toggle is a request for
     more rows on screen, not a request to make a document harder to read. */
  --row-y: 11px;
  --row-x: 16px;
  --row-gap: 12px;

  /* -- Motion -------------------------------------------------------------
     One curve, two durations. --fast is a state change you should not notice;
     --slow is the single authored moment. */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --fast: 130ms;
  --slow: 220ms;
}

:root[data-density='compact'] {
  --row-y: 6px;
  --row-x: 12px;
  --row-gap: 9px;
}

/* ============================== 2. DARK ==================================
   Canvas is #141317 — near-black warmed toward purple, so it belongs to the
   brand without becoming a purple canvas that fights the action colour. It is
   the same value as light-mode --ink, which is why the two themes feel like
   one product inverted rather than two palettes.

   Purple LIFTS to #a173e8 for contrast against that canvas. The consequence:
   white on a purple fill drops below AA, so --on-purple becomes near-black.
   Any component filling with --purple must use --on-purple, never #fff.
   ======================================================================== */
:root[data-theme='dark'] {
  color-scheme: dark;
  --canvas: #141317;
  --surface: #1b1a1f;
  --surface-sunk: #201f26;
  --line: #2b2933;
  --line-strong: #3a3745;
  --ink: #f1f0f4;
  --ink-soft: #a8a5b4;
  --ink-faint: #8f8c9c;
  --purple: #a173e8;
  --purple-deep: #241b3d;
  --purple-wash: color-mix(in srgb, #a173e8 14%, transparent);
  --purple-edge: color-mix(in srgb, #a173e8 40%, transparent);
  --teal: #4bb3a8;
  --gold: #d9bd6d;
  --warn: #d3a44a;
  --danger: #e8756c;
  --on-purple: #17121f;
  --lift-1: 0 1px 2px rgb(0 0 0 / 0.4);
  --lift-2: 0 4px 14px rgb(0 0 0 / 0.45);
  --lift-3: 0 18px 44px rgb(0 0 0 / 0.55);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;
    --canvas: #141317;
    --surface: #1b1a1f;
    --surface-sunk: #201f26;
    --line: #2b2933;
    --line-strong: #3a3745;
    --ink: #f1f0f4;
    --ink-soft: #a8a5b4;
    --ink-faint: #8f8c9c;
    --purple: #a173e8;
    --purple-deep: #241b3d;
    --purple-wash: color-mix(in srgb, #a173e8 14%, transparent);
    --purple-edge: color-mix(in srgb, #a173e8 40%, transparent);
    --teal: #4bb3a8;
    --gold: #d9bd6d;
    --warn: #d3a44a;
    --danger: #e8756c;
    --on-purple: #17121f;
    --lift-1: 0 1px 2px rgb(0 0 0 / 0.4);
    --lift-2: 0 4px 14px rgb(0 0 0 / 0.45);
    --lift-3: 0 18px 44px rgb(0 0 0 / 0.55);
  }
}

/* ============================== 3. BASE ================================== */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: -0.006em;
  color: var(--ink);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Tabular figures globally: file sizes, quotas, dates and calendar numbers
     align in every list and table without a per-component opt-in. */
  font-variant-numeric: tabular-nums;
}

h1, h2, h3 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.022em;
  text-wrap: balance;
}

/* SVG keeps its INLINE display. Forcing `display:block` on every svg is the
   usual copy-paste reset and it silently breaks any icon sitting inside a line
   of text. Icons in .btn / .rail-btn are flex children, where display is
   ignored anyway, so nothing is lost by leaving this alone. */
svg { max-width: 100%; vertical-align: middle; }

/* One focus treatment for the whole product: two pixels of the action colour,
   offset so it never sits on the thing it marks. :focus-visible, not :focus —
   a mouse user clicking a button should not get a ring. */
:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

::selection { background: var(--purple-edge); }

.mono {
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 0.92em;
  letter-spacing: 0;
}
.muted { color: var(--ink-soft); }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 999;
  padding: 10px 16px;
  background: var(--purple);
  color: var(--on-purple);
  border-radius: 0 0 var(--r-sm) 0;
  font-weight: 500;
}
.skip:focus { left: 0; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ============================ 4. PRIMITIVES ============================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 12px;
  min-height: 32px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.4;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--fast) var(--ease),
              border-color var(--fast) var(--ease),
              color var(--fast) var(--ease);
}
.btn:hover { background: var(--surface-sunk); border-color: var(--ink-faint); }
.btn:active { background: var(--line); }
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }

.btn-primary {
  background: var(--purple);
  border-color: var(--purple);
  color: var(--on-purple);
}
.btn-primary:hover {
  background: color-mix(in srgb, var(--purple) 88%, #000);
  border-color: color-mix(in srgb, var(--purple) 88%, #000);
  color: var(--on-purple);
}

.btn-ghost { background: transparent; border-color: transparent; }
.btn-ghost:hover { background: var(--surface-sunk); border-color: transparent; }

.btn-icon {
  padding: 6px;
  min-width: 32px;
  min-height: 32px;
  background: transparent;
  border-color: transparent;
  color: var(--ink-soft);
}
.btn-icon:hover { background: var(--surface-sunk); border-color: transparent; color: var(--ink); }

.btn-sm { padding: 3px 8px; min-height: 26px; font-size: 12.5px; }

/* Gold cannot carry text. At 2.34:1 it fails in both directions in both
   themes. It is a SURFACE with near-black ink on it, or a non-text graphic.
   Never a text colour, never a lone border, never a link. */
.btn-upgrade {
  background: var(--gold);
  border-color: color-mix(in srgb, var(--gold) 80%, #000);
  color: #241d05;
}
.btn-upgrade:hover {
  background: color-mix(in srgb, var(--gold) 90%, #000);
  color: #241d05;
}

input, textarea, select {
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--purple);
  box-shadow: 0 0 0 3px var(--purple-wash);
}
input::placeholder, textarea::placeholder { color: var(--ink-faint); }

/* A class setting `display` outranks the UA's [hidden] rule, so without this
   the attribute is silently ignored and an unset residency badge renders as a
   bare grey pill. Same trap as .ai-dock[hidden] below. */
.badge[hidden] { display: none; }
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--surface-sunk);
  color: var(--ink-soft);
  font-size: 11.5px;
  font-weight: 500;
}
/* Teal marks sovereignty and success — its only two permitted jobs. */
.badge-sovereign {
  background: color-mix(in srgb, var(--teal) 10%, transparent);
  color: var(--teal);
}

/* ============================== 5. SHELL =================================
   Grid, not a flex chain: the suite bar spans every column, and the rail,
   pane and AI dock share the second row. 100dvh with overflow hidden means the
   chrome never scrolls — only the content does. That is what makes an app feel
   like an app rather than a long document.
   ======================================================================== */
.shell {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: var(--suitebar-h) minmax(0, 1fr);
  height: 100dvh;
  overflow: hidden;
}

/* ---------------------------- 5.1 Suite bar ---------------------------- */
.suitebar {
  grid-column: 1 / -1;
  z-index: 30;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  padding-inline: 4px 10px;
  background: var(--purple-deep);
  color: #fff;
}
:root[data-theme='dark'] .suitebar {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .suitebar {
    background: var(--surface);
    border-bottom: 1px solid var(--line);
  }
}

.suitebar-left { display: flex; align-items: center; gap: 2px; }

/* The waffle. A 3x3 grid of 3px dots on a 44px hit target — deliberately
   larger than the glyph, because this control is hit constantly and sits in
   the screen's most awkward corner. */
.waffle {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.waffle:hover { background: rgb(255 255 255 / 0.12); }
:root[data-theme='dark'] .waffle:hover { background: var(--surface-sunk); }
.waffle-dots { display: grid; grid-template-columns: repeat(3, 3px); gap: 3px; }
.waffle-dots i { width: 3px; height: 3px; border-radius: 1px; background: currentColor; }

.suite-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-radius: var(--r-xs);
  color: #fff;
  text-decoration: none;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
:root[data-theme='dark'] .suite-title { color: var(--ink); }
.suite-title svg { width: 17px; height: 20px; }

/* Centred search, capped at 560px. Because the left and right groups are
   unequal it is not centred in the viewport — M365 does the same, and the eye
   reads it as centred because the field's own symmetry dominates. */
.suite-search {
  position: relative;
  justify-self: center;
  width: min(100%, 560px);
}
.suite-search input {
  width: 100%;
  height: 32px;
  padding: 0 12px 0 34px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  background: rgb(255 255 255 / 0.14);
  color: #fff;
  font-size: 13.5px;
}
.suite-search input::placeholder { color: rgb(255 255 255 / 0.72); }
.suite-search input:focus {
  background: var(--canvas);
  color: var(--ink);
  border-color: var(--purple);
}
.suite-search input:focus::placeholder { color: var(--ink-faint); }
.suite-search .s-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  line-height: 0;
  pointer-events: none;
  opacity: 0.85;
}
.suite-search .s-icon svg { width: 16px; height: 16px; }
.suite-search input:focus ~ .s-icon { color: var(--ink-faint); opacity: 1; }
:root[data-theme='dark'] .suite-search input {
  background: var(--surface-sunk);
  color: var(--ink);
}
:root[data-theme='dark'] .suite-search input::placeholder { color: var(--ink-faint); }

.suitebar-right { display: flex; align-items: center; gap: 2px; }
.suitebar-right .btn-icon { color: rgb(255 255 255 / 0.9); min-width: 36px; min-height: 36px; }
.suitebar-right .btn-icon:hover { background: rgb(255 255 255 / 0.12); color: #fff; }
:root[data-theme='dark'] .suitebar-right .btn-icon { color: var(--ink-soft); }
:root[data-theme='dark'] .suitebar-right .btn-icon:hover {
  background: var(--surface-sunk);
  color: var(--ink);
}

/* Initials, not a generic person glyph. A silhouette in the corner of every
   screen tells the user nothing; initials confirm WHICH ACCOUNT they are in,
   which is the question this control actually answers. */
.avatar {
  width: 32px;
  height: 32px;
  margin-left: 6px;
  border-radius: 50%;
  border: 0;
  background: var(--teal);
  color: #fff;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  display: grid;
  place-items: center;
}
/* Signed out is a state the chrome should state, not imply. */
.avatar.is-anon { background: rgb(255 255 255 / 0.18); color: #fff; }
:root[data-theme='dark'] .avatar.is-anon { background: var(--surface-sunk); color: var(--ink-soft); }

/* ------------------------- 5.2 App launcher ---------------------------- */
.launcher {
  position: absolute;
  top: calc(var(--suitebar-h) + 4px);
  left: 6px;
  z-index: 40;
  width: 296px;
  padding: var(--s4);
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--lift-3);
  animation: rise var(--slow) var(--ease);
}
.launcher[hidden] { display: none; }
.launcher h3 {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink-faint);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  margin-bottom: var(--s3);
}
.launcher-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px; }
.launcher-grid button {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 12px 4px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}
.launcher-grid button:hover { background: var(--surface-sunk); }
.launcher-grid svg { width: 24px; height: 24px; color: var(--purple); }

/* ------------------------------ 5.3 Rail -------------------------------
   48px at rest. Expansion is a PINNED preference, never hover-expand: a rail
   that grows when the pointer crosses it reflows the workspace by accident
   several times a minute, which is the most disliked pattern in this class of
   app. */
.rail {
  grid-row: 2;
  width: var(--rail-w);
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s2) 4px;
  background: var(--canvas);
  border-right: 1px solid var(--line);
  overflow: hidden;
  transition: width var(--slow) var(--ease);
}
.shell[data-rail='open'] .rail { width: var(--rail-w-open); }

/* The create button. The only filled control in the whole chrome — it is the
   one thing on screen that makes something new. */
.rail-create {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 40px;
  padding: 0 11px;
  margin-bottom: var(--s2);
  border: 0;
  border-radius: var(--r-sm);
  background: var(--purple);
  color: var(--on-purple);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  flex-shrink: 0;
  transition: background var(--fast) var(--ease);
}
.rail-create:hover { background: color-mix(in srgb, var(--purple) 88%, #000); }
.rail-create svg { width: 18px; height: 18px; flex-shrink: 0; }

.rail-btn {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  height: 40px;
  padding: 0 11px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  flex-shrink: 0;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.rail-btn svg { width: 20px; height: 20px; flex-shrink: 0; }
.rail-btn:hover { background: var(--surface-sunk); color: var(--ink); }
.rail-btn.is-active { background: var(--purple-wash); color: var(--purple); }
/* Active carries a SHAPE as well as a colour: colour alone fails for the ~8%
   of men with a colour-vision deficiency, and fails again in a greyscale
   screenshot. */
.rail-btn.is-active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 0 2px 2px 0;
  background: var(--purple);
}
.rail-label { opacity: 0; transition: opacity var(--fast) var(--ease); }
.shell[data-rail='open'] .rail-label { opacity: 1; }

.rail-spacer { flex: 1; min-height: var(--s4); }

/* Storage meter, pinned above the rail's footer controls. Visible ONLY when the
   rail is expanded: at 48px the label has nowhere to go and wraps to one word
   per line, and an unlabelled bar says less than nothing. */
.rail-quota {
  display: none;
  padding: var(--s3) 11px var(--s2);
  flex-shrink: 0;
}
.shell[data-rail='open'] .rail-quota { display: block; }
.rail-quota[hidden] { display: none; }
.rail-quota p {
  margin: 0 0 7px;
  font-size: 12px;
  color: var(--ink-faint);
  white-space: nowrap;
}
.meter {
  height: 4px;
  border-radius: 2px;
  background: var(--surface-sunk);
  overflow: hidden;
}
/* Filled via transform, not width. A solid block scaled on the X axis looks
   identical to a growing one, but composites on the GPU instead of relaying
   out the rail on every frame of the transition. */
.meter i {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 2px;
  background: var(--purple);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--slow) var(--ease);
}
/* Past 90% the bar changes hue AND the label already states the number — a
   colour change with no text is decoration. */
.meter.is-full i { background: var(--warn); }

/* ============================== 6. PANE ================================== */
.pane {
  grid-row: 2;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--canvas);
}
.view {
  display: none;
  flex: 1;
  overflow: hidden;
  flex-direction: column;
  min-height: 0;
}
.view.is-active { display: flex; }

/* The command bar: a title and its verbs. Verbs live HERE, not in every row.
   No greyed-out commands — a disabled row of eight teaches nothing and takes
   the space of eight live ones. */
.pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s3) clamp(14px, 2vw, 26px);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
  flex-shrink: 0;
}
.pane-head h2 {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.024em;
}
.head-actions {
  display: flex;
  gap: var(--s1);
  flex-wrap: wrap;
  align-items: center;
}
/* Command-bar buttons are ghosts by default. A row of eight outlined buttons
   reads as eight competing decisions; the outline is reserved for the one
   that is not a verb. */
.head-actions .btn {
  border-color: transparent;
  background: transparent;
}
.head-actions .btn:hover {
  background: var(--surface-sunk);
  border-color: transparent;
}
.head-actions .btn-primary,
.head-actions .btn-primary:hover {
  background: var(--purple);
  border-color: var(--purple);
  color: var(--on-purple);
}
.head-actions .btn-primary:hover { background: color-mix(in srgb, var(--purple) 88%, #000); }

.pane-note {
  padding: var(--s4) clamp(14px, 2vw, 26px) 0;
  margin: 0;
  font-size: 13px;
  color: var(--ink-soft);
  max-width: 68ch;
}

/* --------------------------- 6.1 For-you strip -------------------------
   A peripheral suggestion row, built from the SAME /api/documents response the
   list below uses — no second endpoint, no invented ranking. It must never
   grow tall enough to push the real file list below the fold. */
.foryou {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s3);
  padding: var(--s4) clamp(14px, 2vw, 26px);
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.foryou:empty { display: none; }
.fcard {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--canvas);
  padding: var(--s3) var(--s4) var(--s4);
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition: box-shadow var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.fcard:hover { border-color: var(--line-strong); box-shadow: var(--lift-2); }
.fcard-title {
  display: flex;
  align-items: center;
  gap: 9px;
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--line);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--ink);
  min-width: 0;
}
.fcard-title span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fcard-body { display: flex; gap: var(--s3); flex: 1; min-width: 0; }
.fcard-why {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.fcard-why b {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
}
.fcard-why b svg { width: 14px; height: 14px; color: var(--ink-faint); flex-shrink: 0; }
.fcard-why time { font-size: 12px; color: var(--ink-faint); }
.fcard .btn { align-self: flex-start; }

/* A paper-proportioned plate with ruled lines standing in for a real preview.
   The aspect is LOCKED so a card without a thumbnail leaves a correctly-sized
   hole instead of collapsing and making the row jump. */
.fcard-thumb {
  width: 58px;
  aspect-ratio: 8.5 / 11;
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: 2px;
  background:
    linear-gradient(var(--line) 1px, transparent 1px) 6px 10px / calc(100% - 12px) 5px repeat-y,
    var(--canvas);
  box-shadow: var(--lift-1);
}

/* --------------------------- 6.2 Command bar ---------------------------
   Verbs, and a set that changes with selection. */
.cmdbar {
  display: flex;
  align-items: center;
  gap: var(--s1);
  flex-wrap: wrap;
  padding: var(--s2) clamp(14px, 2vw, 26px);
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
  min-height: 44px;
}
.cmdbar .btn { border-color: transparent; background: transparent; }
.cmdbar .btn:hover { background: var(--surface-sunk); border-color: transparent; }
.cmdbar .btn-primary,
.cmdbar .btn-primary:hover {
  background: var(--purple);
  border-color: var(--purple);
  color: var(--on-purple);
}
.cmdbar .btn-primary:hover { background: color-mix(in srgb, var(--purple) 88%, #000); }
.cmdbar-sep {
  width: 1px;
  height: 20px;
  margin-inline: var(--s2);
  background: var(--line);
}
.cmdbar-right { margin-left: auto; display: flex; gap: var(--s1); align-items: center; }

/* Zero-selection commands at rest; item commands once something is checked. */
.cmd-item { display: none; }
.cmdbar.has-selection .cmd-zero { display: none; }
.cmdbar.has-selection .cmd-item { display: inline-flex; }
.cmdbar.has-selection span.cmd-item { display: inline-flex; }
/* Verbs that only make sense on one file leave on a multi-selection, rather
   than silently acting on whichever row happens to be first. */
.cmdbar.multi .cmd-one { display: none; }

/* Folders cannot be opened in an editor, downloaded, shared as a file, or
   version-checked. Those verbs leave when the selection contains one; Rename,
   Move and Delete stay, because they mean the same thing for both. */
.cmdbar.has-folder #sel-open,
.cmdbar.has-folder #sel-share,
.cmdbar.has-folder #sel-download,
.cmdbar.has-folder #sel-versions { display: none; }

/* --------------------------- 6.2b Breadcrumb ---------------------------
   The path IS the title. At the root it is one disabled crumb reading
   "My files"; deeper in, each ancestor is a button back to itself. The last
   crumb is where you already are, so it is never a link. */
.crumbs {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
}
.crumb {
  border: 0;
  background: transparent;
  padding: 2px 4px;
  margin: -2px -4px;
  border-radius: var(--r-xs);
  font: inherit;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.024em;
  color: var(--ink-soft);
  cursor: pointer;
}
.crumb:hover { background: var(--surface-sunk); color: var(--ink); }
/* The current location is stated, not offered — disabled here means "you are
   here", so it keeps full ink rather than looking unavailable. */
.crumb:disabled {
  color: var(--ink);
  cursor: default;
  background: transparent;
}
.crumb-sep {
  color: var(--ink-faint);
  font-size: 17px;
  user-select: none;
}

/* Trash swaps the whole command set: creation verbs make no sense there, and
   the two that do are Restore and the irreversible one. */
.cmd-trash { display: none; }
.cmdbar.is-trash .cmd-zero { display: none; }
.cmdbar.is-trash.has-selection .cmd-item { display: none; }
.cmdbar.is-trash.has-selection .cmd-trash { display: inline-flex; }
.cmdbar.is-trash.has-selection span.cmd-item { display: inline-flex; }

/* ---------------------------- 6.3 Filter row --------------------------- */
.filterbar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  padding: var(--s3) clamp(14px, 2vw, 26px);
  flex-shrink: 0;
}
.filterbar h3 {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.022em;
  margin-right: var(--s2);
}
.filterbar .filter-input { margin-left: auto; width: 220px; height: 32px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 14px;
  min-height: 32px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--fast) var(--ease),
              border-color var(--fast) var(--ease),
              color var(--fast) var(--ease);
}
.chip:hover { background: var(--surface-sunk); }
.chip[aria-pressed='true'] {
  background: var(--purple);
  border-color: var(--purple);
  color: var(--on-purple);
}

/* ============================== 7. LISTS =================================
   The renderers in nord.js emit <li> rows; these styles are the contract with
   them. Do not change a class name here without changing it there.
   ======================================================================== */
.split { display: flex; flex: 1; overflow: hidden; min-height: 0; }
.list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}
.split .list { max-width: 360px; border-right: 1px solid var(--line); }
.list.wide { max-width: none; }

.list li {
  display: flex;
  gap: var(--row-gap);
  align-items: center;
  padding: var(--row-y) var(--row-x);
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.list li:hover { background: var(--surface); }
.list li.is-selected { background: var(--purple-wash); }
.list li.empty,
.list li.error {
  display: block;
  cursor: default;
  color: var(--ink-soft);
  padding: var(--s8) var(--s5);
  text-align: center;
  border-bottom: 0;
}
.list li.empty:hover, .list li.error:hover { background: transparent; }
.list li.error { color: var(--danger); }
/* Rows hidden by the suite-bar filter. A class, not inline display, so the
   filter never fights a renderer that rewrites the row. */
.list li.is-filtered { display: none; }

.row-main { flex: 1; min-width: 0; }
.row-title {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.row-sub {
  font-size: 12.5px;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.row-meta { font-size: 12px; color: var(--ink-faint); white-space: nowrap; }

/* Unread is a purple dot, not a heavier row — weight alone is ambiguous next
   to a selected row. */
.unread .row-title { font-weight: 600; }
.unread .row-main { position: relative; }
.unread .row-main::before {
  content: '';
  position: absolute;
  left: -10px;
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--purple);
}

/* Type glyph: a tinted square carrying the extension, one hue per family. The
   tint is ~14% so the column reads as texture at a glance and as information
   when looked at. data-ext is set by the renderer; the bare .glyph is the
   fallback for anything unrecognised. */
.glyph {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--r-xs);
  display: grid;
  place-items: center;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  background: var(--surface-sunk);
  color: var(--ink-soft);
}
.glyph[data-ext='docx'], .glyph[data-ext='doc'], .glyph[data-ext='odt'] {
  background: color-mix(in srgb, #2b579a 14%, transparent); color: #2b579a;
}
.glyph[data-ext='xlsx'], .glyph[data-ext='xls'], .glyph[data-ext='csv'], .glyph[data-ext='ods'] {
  background: color-mix(in srgb, #1d6f42 14%, transparent); color: #1d6f42;
}
.glyph[data-ext='pptx'], .glyph[data-ext='ppt'], .glyph[data-ext='odp'] {
  background: color-mix(in srgb, #c43e1c 14%, transparent); color: #c43e1c;
}
.glyph[data-ext='pdf'] {
  background: color-mix(in srgb, #b3261e 14%, transparent); color: #b3261e;
}
:root[data-theme='dark'] .glyph[data-ext='docx'],
:root[data-theme='dark'] .glyph[data-ext='doc'],
:root[data-theme='dark'] .glyph[data-ext='odt'] { color: #7ea6e0; }
:root[data-theme='dark'] .glyph[data-ext='xlsx'],
:root[data-theme='dark'] .glyph[data-ext='xls'],
:root[data-theme='dark'] .glyph[data-ext='csv'],
:root[data-theme='dark'] .glyph[data-ext='ods'] { color: #5fbd85; }
:root[data-theme='dark'] .glyph[data-ext='pptx'],
:root[data-theme='dark'] .glyph[data-ext='ppt'],
:root[data-theme='dark'] .glyph[data-ext='odp'] { color: #e8846a; }
:root[data-theme='dark'] .glyph[data-ext='pdf'] { color: #e8756c; }

/* ----------------------- 7.1 Drive: aligned columns --------------------
   Drive is the one list that is genuinely tabular, so its rows switch from a
   flex run to a grid and gain a header. Everything else stays a flex list,
   because a two-line mail row is not a table and pretending otherwise costs
   the sender's name its width. */
.filelist li,
.list-head {
  display: grid;
  grid-template-columns: 30px 28px minmax(0, 1fr) 88px 120px 190px 116px;
  gap: var(--row-gap);
  align-items: center;
}
.filelist li.is-selected { background: var(--purple-wash); }

/* Selection cell. FIXED WIDTH, and the control is only faded — never removed
   from flow — so the table does not shift sideways the moment a pointer
   enters a row. That shift is the most common bug in this pattern. */
.cell-select { display: grid; place-items: center; }
.select-dot {
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1.5px solid var(--ink-faint);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-items: center;
  opacity: 0;
  transition: opacity var(--fast) var(--ease);
}
.select-dot svg { width: 12px; height: 12px; color: var(--on-purple); opacity: 0; }
.filelist li:hover .select-dot,
.filelist li:focus-within .select-dot,
.filelist li.is-selected .select-dot { opacity: 1; }
.filelist li.is-selected .select-dot {
  background: var(--purple);
  border-color: var(--purple);
}
.filelist li.is-selected .select-dot svg { opacity: 1; }
/* Coarse pointers have no hover, so the affordance is permanent there. */
@media (hover: none) {
  .select-dot { opacity: 1; }
}

/* Activity is real or it is blank: it is populated only where a share for that
   exact file exists in /api/shares. An empty cell is the honest state. */
.cell-activity {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
}
.cell-activity svg { width: 15px; height: 15px; color: var(--purple); flex-shrink: 0; }
.list-head {
  padding: var(--s2) var(--row-x);
  border-bottom: 1px solid var(--line);
  background: var(--canvas);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-soft);
  flex-shrink: 0;
}
.list-head span { white-space: nowrap; }
.cell-meta {
  font-size: 12.5px;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A folder sits in the same table as the files, because it lives in the same
   place. Its glyph is an outline rather than a tinted extension chip — there
   is no extension to state, and the difference in weight is what makes the
   two kinds of row separable at a glance. */
.filelist li.is-folder .row-title { font-weight: 600; }
.glyph-folder {
  background: transparent;
  color: var(--ink-soft);
}
.glyph-folder svg { width: 20px; height: 20px; }

/* A version count only appears once there IS history — a "v1" on every row is
   noise that teaches nothing. */
.ver-badge {
  margin-left: 7px;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--surface-sunk);
  color: var(--ink-faint);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0;
}

.version-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 46vh;
  overflow-y: auto;
}
.version-list li {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 10px 2px;
  border-bottom: 1px solid var(--line);
}
.version-list li:last-child { border-bottom: 0; }

/* Row actions stay out of the way until the row is wanted. Four labelled
   buttons on every row is 28 controls down a short list — noise that reads as
   an unfinished table rather than a file browser. :focus-within keeps them
   reachable by keyboard; coarse pointers get them permanently, since there is
   no hover to reveal them with. */
.row-actions {
  display: flex;
  gap: 2px;
  justify-content: flex-end;
  opacity: 0;
  transition: opacity var(--fast) var(--ease);
}
.list li:hover .row-actions,
.list li:focus-within .row-actions { opacity: 1; }
@media (hover: none) {
  .row-actions { opacity: 1; }
}

/* ============================== 8. READER ================================ */
.reader {
  flex: 1;
  overflow-y: auto;
  padding: var(--s6) var(--s6);
  min-width: 0;
}
.reader h3 { font-size: 19px; margin-bottom: var(--s2); }
.reader pre {
  white-space: pre-wrap;
  overflow-wrap: break-word;
  font: inherit;
  margin-top: var(--s5);
  max-width: 72ch;
  color: var(--ink-soft);
}

/* ============================== 9. SIGN IN =============================== */
.signin {
  max-width: 360px;
  margin: 12vh auto;
  padding: 0 var(--s5);
}
.signin-mark { margin-bottom: var(--s5); line-height: 0; }
.signin-mark svg { width: 34px; height: 39px; }
.signin h1 { font-size: 25px; margin-bottom: var(--s2); }
.signin p { color: var(--ink-soft); margin: 0; font-size: 13.5px; }
.signin form {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin-top: var(--s5);
}
.signin label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
}
.signin .btn { justify-content: center; padding: 9px; font-size: 14px; }
.hint {
  font-size: 12.5px;
  color: var(--ink-faint);
  margin-top: var(--s5);
  line-height: 1.55;
}
/* A failed sign-in states what happened where the user is already looking,
   rather than in an alert() they have to dismiss before they can retry. */
.signin-error {
  margin-top: var(--s4);
  padding: 10px 12px;
  border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--danger) 8%, transparent);
  color: var(--danger);
  font-size: 13px;
  line-height: 1.5;
}
.signin-error[hidden] { display: none; }

/* ============================= 10. DIALOG ================================ */
.backdrop {
  position: fixed;
  inset: 0;
  background: rgb(20 19 23 / 0.44);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 50;
  animation: fade var(--fast) var(--ease);
}
.dialog {
  background: var(--canvas);
  border-radius: var(--r-md);
  padding: var(--s5);
  width: 100%;
  max-width: 440px;
  max-height: 90vh;
  overflow-y: auto;
  border: 1px solid var(--line);
  box-shadow: var(--lift-3);
  /* The one authored moment: dialogs rise into place. Everything else is a
     plain state change — and menus do not animate at all, because a context
     menu that takes 220ms to arrive is one that arrives late. */
  animation: rise var(--slow) var(--ease);
}
.dialog h3 { font-size: 16px; margin-bottom: var(--s4); }
.dialog label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
  margin-bottom: var(--s3);
}
.dialog textarea { min-height: 120px; resize: vertical; }
.dialog-actions {
  display: flex;
  gap: var(--s2);
  justify-content: flex-end;
  margin-top: var(--s5);
}
.check-row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s3);
  font-size: 13.5px;
}
.check-row input { width: auto; accent-color: var(--purple); }
.check-row label { margin: 0; color: var(--ink); font-weight: 400; }

/* A menu is not a dialog: no scrim, no animation, anchored to its trigger. */
.menu {
  position: absolute;
  z-index: 45;
  min-width: 212px;
  padding: var(--s1);
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: var(--lift-3);
}
.menu[hidden] { display: none; }
.menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
}
.menu button:hover { background: var(--surface-sunk); }
.menu button svg { width: 16px; height: 16px; color: var(--ink-soft); }
.menu hr { border: 0; border-top: 1px solid var(--line); margin: var(--s1) 0; }

@keyframes fade { from { opacity: 0; } }
@keyframes rise { from { opacity: 0; transform: translateY(8px) scale(0.985); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ====================== 11. DRIVE: drag and drop ========================= */
.dropzone {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
}
.drop-overlay {
  position: absolute;
  inset: 10px;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--purple-wash);
  border: 1.5px dashed var(--purple-edge);
  border-radius: var(--r-lg);
  font-size: 15px;
  font-weight: 500;
  color: var(--purple);
  pointer-events: none;
  z-index: 5;
  backdrop-filter: blur(1px);
}
.dropzone.is-dragging .drop-overlay { display: flex; }

.uploads {
  border-top: 1px solid var(--line);
  padding: 10px clamp(14px, 2vw, 26px);
  max-height: 160px;
  overflow-y: auto;
  background: var(--surface);
  flex-shrink: 0;
}
.upload-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
  padding: 3px 0;
}
.upload-row .nm {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.upload-row .st { color: var(--ink-soft); }
.upload-row.ok .st { color: var(--ok); }
.upload-row.err .st { color: var(--danger); }

/* ======================= 12. CALENDAR: month ============================= */
.cal-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0 clamp(14px, 2vw, 22px) var(--s4);
  min-height: 0;
}
.cal-dow {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding: var(--s3) 0 var(--s2);
}
.cal-dow div { text-align: center; }
.cal-grid {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-auto-rows: minmax(80px, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  min-height: 0;
}
.cal-cell {
  background: var(--canvas);
  padding: 5px 6px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 3px;
  cursor: pointer;
  min-height: 0;
  transition: background var(--fast) var(--ease);
}
.cal-cell:hover { background: var(--surface); }
.cal-cell.is-other { background: var(--surface-sunk); }
.cal-cell.is-other .cal-num { color: var(--ink-faint); }
.cal-num {
  font-size: 12px;
  font-weight: 500;
  align-self: flex-start;
  padding: 2px 6px;
  border-radius: var(--r-pill);
  color: var(--ink-soft);
}
.cal-cell.is-today .cal-num {
  background: var(--purple);
  color: var(--on-purple);
  font-weight: 600;
}

/* Timed events read as a filled pill in the wash; all-day as solid purple. No
   coloured left border — a 3px accent rule on a list item is the category's
   laziest signal for "this one is special". */
.cal-ev {
  font-size: 11px;
  line-height: 1.4;
  padding: 2px 6px;
  border-radius: var(--r-xs);
  background: var(--purple-wash);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
}
.cal-ev.all-day { background: var(--purple); color: var(--on-purple); }
.cal-ev.is-span {
  margin-left: -7px;
  margin-right: -7px;
  border-radius: 0;
  padding-left: 8px;
}
.cal-ev.is-span-start {
  margin-left: 0;
  border-radius: var(--r-xs) 0 0 var(--r-xs);
  padding-left: 6px;
}
.cal-ev.is-span-end { margin-right: 0; border-radius: 0 var(--r-xs) var(--r-xs) 0; }
.cal-ev.is-span-start.is-span-end { border-radius: var(--r-xs); }
.cal-ev.is-span-mid { min-height: 1.4em; }
.cal-more { font-size: 10.5px; color: var(--ink-faint); padding-left: 2px; }
.cal-status { padding: 10px 2px 0; font-size: 12.5px; margin: 0; }

/* The matched passage under a content-search result. */
.row-snippet {
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-faint);
  margin-top: 2px;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.row-snippet mark {
  background: var(--purple-wash);
  color: var(--ink);
  border-radius: 2px;
  padding: 0 2px;
}

/* ============================= 12a. HOME =================================
   A glance at today. Deliberately quieter than the apps it summarises: this
   is a place you pass through on the way to work, so it uses one accent
   (overdue) and otherwise stays out of the way. */
.home-scroll {
  flex: 1;
  overflow-y: auto;
  padding: clamp(20px, 3vw, 36px) clamp(16px, 3vw, 40px) var(--s6);
  min-height: 0;
}
.home-head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s5);
}
.home-avatar {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-pill);
  background: var(--purple);
  color: var(--on-purple);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.02em;
  flex: none;
}
.home-head h2 { margin: 0; font-size: clamp(20px, 2.4vw, 26px); }
.home-date { margin: 2px 0 0; font-size: 13px; color: var(--ink-faint); }

/* auto-fit rather than fixed columns: the same markup reads as one column on
   a phone, two on a laptop, three on a wide display, with no breakpoints. */
.home-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(12px, 1.6vw, 20px);
  align-items: start;
}
.home-card {
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}
.home-card-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3) var(--s3) var(--s2);
}
.home-card-title {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  text-align: left;
}
.home-card-title:hover { color: var(--purple-deep); }
.home-card-title.is-static { cursor: default; }
.home-badge {
  margin-left: auto;
  min-width: 20px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  background: var(--purple);
  color: var(--on-purple);
  font-size: 11px;
  font-weight: 600;
  text-align: center;
}
.home-card-body { padding: 0 var(--s3) var(--s3); }
.home-list { list-style: none; margin: 0; padding: 0; }

.home-row {
  display: flex;
  gap: var(--s3);
  padding: 8px 6px;
  margin: 0 -6px;
  border-radius: var(--r-xs);
  align-items: baseline;
}
.home-row.is-clickable { cursor: pointer; }
.home-row.is-clickable:hover { background: var(--surface); }
.home-row + .home-row { border-top: 1px solid var(--line); }

.home-when {
  flex: none;
  width: 74px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.home-when-time { font-size: 12.5px; font-weight: 500; color: var(--ink-soft); }
/* The only colour on the page. Overdue is the one state that asks for action
   rather than reporting a fact. */
.home-when-time.is-overdue { color: var(--danger); font-weight: 600; }
.home-when-time.is-muted { color: var(--ink-faint); font-weight: 400; }
.home-when-day { font-size: 11px; color: var(--ink-faint); }

.home-row-main { min-width: 0; flex: 1; }
.home-row-title {
  font-size: 13.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.home-row.is-unread .home-row-title { font-weight: 600; }
.home-row-sub {
  font-size: 12px;
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.home-tag {
  margin-left: 6px;
  padding: 0 5px;
  border-radius: var(--r-xs);
  background: var(--surface-sunk);
  color: var(--ink-faint);
  font-size: 10.5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.home-ext {
  flex: none;
  width: 74px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.home-empty, .home-note { margin: 0; padding: 6px 0; font-size: 13px; color: var(--ink-faint); }
.home-note { padding-top: var(--s2); font-size: 12px; }
.home-error { margin: 0; padding: 6px 0; font-size: 13px; color: var(--danger); }

/* ============================ 12b. MAIL ================================== */
.mail-body {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(140px, 190px) 1fr;
  min-height: 0;
  overflow: hidden;
}
.mail-boxes {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s3) var(--s2);
  border-right: 1px solid var(--line);
  overflow-y: auto;
}
.mail-box {
  text-align: left;
  font: inherit;
  font-size: 13px;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.mail-box:hover { background: var(--surface); color: var(--ink); }
.mail-box.is-on { background: var(--purple-wash); color: var(--purple-deep); font-weight: 600; }

.mail-listwrap { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.mail-listwrap .list { flex: 1; overflow-y: auto; }
#mail-more { margin: var(--s2); align-self: center; }

/* A thread's message count. Without it a conversation is indistinguishable
   from a single message, which is the entire point of grouping them. */
.thread-count {
  display: inline-block;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--surface-sunk);
  color: var(--ink-soft);
  font-size: 11px;
  font-weight: 600;
}
.reader-head { margin-bottom: var(--s3); }
.reader-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding-bottom: var(--s3);
  margin-bottom: var(--s3);
  border-bottom: 1px solid var(--line);
}
.attachments {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--s2);
  margin-bottom: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}
.attachment {
  font-size: 13px;
  color: var(--purple-deep);
  text-decoration: none;
  padding: 3px 0;
}
.attachment:hover { text-decoration: underline; }
.thread-rest {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: flex-start;
}
.thread-other {
  font: inherit;
  font-size: 12.5px;
  border: 0;
  background: transparent;
  color: var(--purple-deep);
  cursor: pointer;
  padding: 3px 0;
}
.thread-other:hover { text-decoration: underline; }

@media (max-width: 860px) {
  .mail-body { grid-template-columns: 1fr; }
  .mail-boxes {
    flex-direction: row;
    overflow-x: auto;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
}

/* ---- Week and day -------------------------------------------------------
   One grid drawn N columns wide; week and day differ only in `--calt-cols`,
   set per render. Header, all-day strip and body share the same template so
   the hour gutter stays aligned down the whole pane. */
.cal-views { display: flex; gap: 6px; }
.cal-month { flex: 1; display: flex; flex-direction: column; min-height: 0; }
/* `display: flex` on the class outranks the UA's `[hidden] { display: none }`,
   so the hidden view would otherwise keep its space and paint over the one
   that is meant to be on screen. */
.cal-month[hidden],
.cal-time[hidden] { display: none; }
.cal-time {
  --calt-cols: 7;
  --calt-gutter: 58px;
  --calt-hour: 46px;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  margin-top: var(--s3);
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}
.calt-head,
.calt-allday,
.calt-body {
  display: grid;
  grid-template-columns: var(--calt-gutter) repeat(var(--calt-cols), minmax(0, 1fr));
}
.calt-head { border-bottom: 1px solid var(--line); }
.calt-dayhead {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: var(--s2) 0;
  border-left: 1px solid var(--line);
}
.calt-dow {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.calt-daynum {
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  min-width: 30px;
  height: 30px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.calt-daynum:hover { background: var(--surface-sunk); }
.calt-dayhead.is-today .calt-daynum { background: var(--purple); color: var(--on-purple); }

/* The strip stays even when empty: it is the only place an all-day event can
   be created by clicking, so collapsing it would remove the target. */
.calt-allday { border-bottom: 1px solid var(--line); }
.calt-gutlbl {
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding: 7px 8px 0 0;
  text-align: right;
}
.calt-adcell {
  border-left: 1px solid var(--line);
  padding: 4px;
  min-height: 32px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.calt-adcell:hover { background: var(--surface); }

.calt-scroll { flex: 1; overflow-y: auto; min-height: 0; }
.calt-gutter { display: grid; grid-auto-rows: var(--calt-hour); }
.calt-hourlbl {
  font-size: 11px;
  color: var(--ink-faint);
  padding-right: 8px;
  text-align: right;
  /* Sit the label on its hour line rather than inside the box beneath it. */
  transform: translateY(-0.55em);
}
.calt-col {
  position: relative;
  border-left: 1px solid var(--line);
  display: grid;
  grid-auto-rows: var(--calt-hour);
}
.calt-slot {
  border-top: 1px solid var(--line);
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.calt-slot:first-child { border-top: 0; }
.calt-slot:hover { background: var(--surface); }

.calt-ev {
  position: absolute;
  z-index: 1;
  overflow: hidden;
  padding: 3px 6px;
  border-radius: var(--r-xs);
  background: var(--purple-wash);
  /* A hairline in the canvas colour keeps blocks that split a cluster's width
     from fusing into one slab. */
  box-shadow: inset 0 0 0 1px var(--canvas);
  color: var(--ink);
  font-size: 11px;
  line-height: 1.35;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 1px;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.calt-ev:hover { background: var(--purple); color: var(--on-purple); }
/* Never wrap: a wrapped time pushes the title out of a short block entirely. */
.calt-ev-time { font-weight: 500; color: var(--ink-soft); white-space: nowrap; overflow: hidden; }
.calt-ev.is-narrow .calt-ev-time { display: none; }
.calt-ev:hover .calt-ev-time { color: var(--on-purple); }
.calt-ev-title { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Too short to stack two lines — run time and title along one row instead. */
.calt-ev.is-short { flex-direction: row; align-items: baseline; gap: 5px; padding: 1px 6px; }
.calt-ev.is-short .calt-ev-title { flex: 1; min-width: 0; }

.calt-now {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 2;
  height: 0;
  border-top: 2px solid var(--danger);
  pointer-events: none;
}
.calt-now::before {
  content: '';
  position: absolute;
  left: -3px;
  top: -4px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--danger);
}

/* ============================= 13. AI DOCK =============================== */
.ai-dock {
  grid-row: 2;
  width: 380px;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  border-left: 1px solid var(--line);
  min-height: 0;
}
/* A class setting display outranks the UA's [hidden] rule; without this the
   hidden attribute is silently ignored and the toggle appears dead. */
.ai-dock[hidden] { display: none; }
.ai-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.ai-head strong { flex: 1; font-size: 13.5px; font-weight: 600; }
.ai-dock iframe { flex: 1; border: 0; width: 100%; min-height: 0; }

/* ============================== 14. MEET ================================= */
.meet-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 0 clamp(14px, 2vw, 26px) var(--s5);
  min-height: 0;
}
.meet-wrap iframe {
  flex: 1;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  margin-top: var(--s3);
}
.share-url {
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 12px;
  color: var(--purple);
}

/* =========================== 15. RESPONSIVE ==============================
   Each breakpoint is triggered by a real failure, not by a device name.
   ======================================================================== */
@media (max-width: 1180px) {
  .foryou { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Drive columns drop in order of how rarely they are read. Activity first
   (most rows have none), then Size, then Modified. Name, selection and actions
   never drop. */
@media (max-width: 1240px) {
  .filelist li, .list-head { grid-template-columns: 30px 28px minmax(0, 1fr) 88px 120px 116px; }
  .filelist .col-activity, .list-head .col-activity { display: none; }
}
@media (max-width: 1100px) {
  .ai-dock { width: 320px; }
  .filelist li, .list-head { grid-template-columns: 30px 28px minmax(0, 1fr) 120px 116px; }
  .filelist .col-size, .list-head .col-size { display: none; }
}
@media (max-width: 900px) {
  .suite-search { width: min(100%, 320px); }
  .filelist li, .list-head { grid-template-columns: 30px 28px minmax(0, 1fr) 116px; }
  .filelist .col-modified, .list-head .col-modified { display: none; }
  .filterbar .filter-input { margin-left: 0; width: 100%; }
  /* Two up, not one. A single card stretched to the full pane width is a
     banner, and this strip is meant to be peripheral. */
  .foryou { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foryou .fcard:nth-child(n + 3) { display: none; }
}
@media (max-width: 620px) {
  /* Below this the strip costs more vertical room than the two suggestions
     are worth, and the actual file list is the thing the user came for. */
  .foryou { display: none; }
}
@media (max-width: 820px) {
  /* Full-screen rather than hidden: hiding it made the AI button silently do
     nothing, which reads as broken, not as responsive. */
  .ai-dock {
    position: fixed;
    inset: 0;
    width: auto;
    z-index: 60;
    border-left: 0;
  }
}
@media (max-width: 720px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  /* The rail becomes an overlay rather than stealing 48px from a 360px
     viewport. */
  .rail {
    position: fixed;
    top: var(--suitebar-h);
    bottom: 0;
    left: 0;
    width: var(--rail-w-open);
    transform: translateX(-100%);
    box-shadow: var(--lift-3);
    transition: transform var(--slow) var(--ease);
  }
  .shell[data-rail='open'] .rail { transform: none; }
  .shell[data-rail='open'] .rail-label { opacity: 1; }
  .suite-title span:last-child { display: none; }
  .split { flex-direction: column; }
  .split .list {
    max-width: none;
    border-right: 0;
    border-bottom: 1px solid var(--line);
    max-height: 44%;
  }
  .reader { padding: 20px 18px; }
}
