/* =========================================================================
   Sentec unified app portal

   Mobile first. The mobile layout is the base; `@media (min-width: 1024px)`
   swaps in the desktop header, dropdown menus and multi-column grids.

   Deployment colours arrive as --t-* custom properties, set on <html> by
   theme.js. Every value below has a fallback so the page renders correctly
   before the first paint of the theme.
   ========================================================================= */

/* ------------------------------------------------------------------ fonts

   Geist (display) and Inter (body), self-hosted under /assets/fonts/ (SIL
   OFL 1.1; the licenses ship beside the files). These are the files Google
   Fonts served for Geist 400-700 and Inter 400-600, one variable file per
   script subset, with Google's unicode-range kept, so the browser fetches
   only the subsets a page's text needs. The comment above each face names
   the Google file it came from. Thai, Khmer, Lao, Burmese and Arabic are not
   among them and fall back to the "Noto Sans ..." names and system fonts in
   --font-display/--font-body, as they did with Google Fonts.

   assets/ is cached for a day at fixed names (deploy.yml), so each file is
   named after its own SHA-256 (test/fonts.test.mjs checks it): a changed font
   gets a new name. The paths are absolute because this sheet is served from
   /v/<commit>/src/ and the fonts are shared by every deploy. index.html
   preloads the two Latin files. */

/* Geist cyrillic-ext, Google Fonts file geist/v5/gyByhwUxId8gMEwRGFWfOw.woff2 */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-cyrillic-ext-2317fa4bb2.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* Geist cyrillic, Google Fonts file geist/v5/gyByhwUxId8gMEwYGFWfOw.woff2 */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-cyrillic-6894439694.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Geist vietnamese, Google Fonts file geist/v5/gyByhwUxId8gMEwTGFWfOw.woff2 */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-vietnamese-8fa40e5d24.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* Geist latin-ext, Google Fonts file geist/v5/gyByhwUxId8gMEwSGFWfOw.woff2 */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-latin-ext-824f485b5d.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Geist latin, Google Fonts file geist/v5/gyByhwUxId8gMEwcGFU.woff2 */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/geist-latin-19f9c92546.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Inter cyrillic-ext, Google Fonts file inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/inter-cyrillic-ext-ca15706333.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* Inter cyrillic, Google Fonts file inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/inter-cyrillic-71d5ee93cc.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Inter greek-ext, Google Fonts file inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/inter-greek-ext-6e9e020a25.woff2) format('woff2');
  unicode-range: U+1F00-1FFF;
}
/* Inter greek, Google Fonts file inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/inter-greek-1be3448e29.woff2) format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* Inter vietnamese, Google Fonts file inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/inter-vietnamese-5c66f9e07e.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* Inter latin-ext, Google Fonts file inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/inter-latin-ext-34b9c504ca.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Inter latin, Google Fonts file inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/fonts/inter-latin-3100e775e8.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Deployment palette — overwritten by theme.js on boot. */
  --t-accent: #27a5f7;
  /* The accent as a solid block behind text. Equal to --t-accent whenever
     white type already clears 4.5:1 on it; deepened only when it does not.
     Use this for filled buttons and badges; use --t-accent for dots, rules and
     other marks that carry no type. */
  --t-accent-fill: #1d7ab7;
  --t-accent-ink: #1d6493;
  --t-accent-ink-hover: #134262;
  --t-accent-tint: #eaf6fe;
  --t-accent-line: #cfe9fc;
  --t-on-accent: #ffffff;
  --t-navy: #0f2438;
  --t-navy-2: #153252;
  --t-band: #f7fafc;
  --t-edge: #de26a5;
  --t-glow-13: rgba(39, 165, 247, 0.13);
  --t-glow-16: rgba(39, 165, 247, 0.16);
  --t-glow-24: rgba(39, 165, 247, 0.24);
  --t-glow-28: rgba(39, 165, 247, 0.28);
  --t-glow-34: rgba(39, 165, 247, 0.34);

  /* Fixed neutrals — not configurable per deployment. */
  --ink: #2b2d31;
  --ink-2: #5b6570;
  --ink-3: #8a94a0;
  --ink-4: #98a2ae;
  --line-1: #eef2f6;
  --line-2: #e6ebf0;
  --line-3: #e1e8ef;
  --line-4: #d8e1e9;
  --line-dash: #dce4eb;
  --warn: #e9a33a;
  --warn-bg: #fdf7ec;
  --warn-line: #f0ddbe;
  --warn-ink: #7a5a22;
  --ok-bg: #edf8f1;
  --ok-line: #cbe8d6;
  --ok-ink: #1f6b3d;
  /* Type on the --warn amber (the callout badge): dark ink, 6.37:1, since
     white on that amber is 2.15:1. The amber is the same in both schemes. */
  --on-warn: #3d2a06;

  /* The security key illustration (src/join-key-art.js): its outlines and its
     light surfaces. Light is the approved art as drawn; dark keeps the object
     light-bodied but dims its whites and draws the outlines dark, because the
     navy outline turns pale in dark and vanishes against the light fills. */
  --key-art-line: var(--t-navy);
  --key-art-paper: #fafcfe;
  --key-art-keycap: #e4ecf3;
  --key-art-pad: #edf2f7;
  --key-art-pad-line: #e0e8ef;
  --key-art-plug: #e6ecf1;
  --key-art-skin: #ffffff;
  --key-art-skin-shade: #dceaf5;
  --key-art-nail: #eef5fb;

  --font-display: Geist, "Noto Sans Thai", "Noto Sans Myanmar", "Noto Sans Khmer", "Noto Sans Lao", "Noto Sans Arabic", Inter, system-ui, sans-serif;
  --font-body: Inter, "Noto Sans Thai", "Noto Sans Myanmar", "Noto Sans Khmer", "Noto Sans Lao", "Noto Sans Arabic", system-ui, -apple-system, "Segoe UI", sans-serif;

  --header-h: 64px;
  --pad-x: 16px;
  --shadow-card: 0 1px 2px rgba(15, 36, 56, 0.04);
  --shadow-lift: 0 18px 36px -16px rgba(15, 36, 56, 0.2);
  --shadow-drop: 0 26px 64px -22px rgba(15, 36, 56, 0.3);

  /* Grounds and the colors that were literals before dark mode. Each light
     value is exactly the literal it replaced, so light is unchanged. */
  --page: #fff;
  --surface: #fff;
  --on-navy: #fff;
  --header-bg: rgba(255, 255, 255, 0.9);
  --skeleton-ground: #f7f9fb;
  --skeleton-shine: #f6f8fa;
  --line-soft: #f2f5f8;
  --scrim: rgba(15, 36, 56, 0.28);
  --shadow-hub: 0 10px 24px rgba(15, 36, 56, 0.1);
  --shadow-invite: 0 14px 30px -18px rgba(15, 36, 56, 0.28);
  --drop-orbit: drop-shadow(0 6px 12px rgba(15, 36, 56, 0.12));
}

/* ------------------------------------------------------------- dark mode

   src/color-scheme.js puts data-scheme="dark" on <html> before the first
   paint, from the person's choice in the account menu or, under System, the
   device setting. Only colors change here; layout is untouched.

   The approved palette is scratch/dark-mode-mockup.html (2026-10-05). The
   --t-* values below are the vendor's dark palette and only paint until
   theme.js runs: applyTheme() then writes the deployment's own dark palette
   inline on <html> (resolveDarkTheme(), which lightens accent text until it
   clears 4.5:1 on these grounds).

   Contrast on --surface (#151d26), measured: --ink 14.17, --ink-2 7.82,
   --ink-3 5.71, vendor --t-accent-ink 8.12 (6.34 on its chip tint),
   --warn-ink 9.58 on --warn-bg, --ok-ink 9.15 on --ok-bg, white on the
   vendor fill 4.65. test/dark-mode.test.mjs recomputes them. */
:root[data-scheme="dark"] {
  color-scheme: dark;

  --t-accent: #27a5f7;
  --t-accent-fill: #1d7ab7;
  --t-accent-ink: #5cbcfa;
  --t-accent-ink-hover: #8fd1fc;
  --t-accent-tint: #12324a;
  --t-accent-line: #1d4a6b;
  --t-on-accent: #ffffff;
  --t-navy: #dfe7ef;
  --t-navy-2: #c9d5e1;
  --t-band: #121a22;
  --t-edge: #f04dbd;
  --t-glow-13: rgba(39, 165, 247, 0.19);
  --t-glow-16: rgba(39, 165, 247, 0.22);
  --t-glow-24: rgba(39, 165, 247, 0.3);
  --t-glow-28: rgba(39, 165, 247, 0.34);
  --t-glow-34: rgba(39, 165, 247, 0.4);

  --ink: #e6ebf0;
  --ink-2: #a7b1bc;
  --ink-3: #8b97a4;
  --ink-4: #6f7b88;
  --line-1: #1c2630;
  --line-2: #222d38;
  --line-3: #27333f;
  --line-4: #2f3c49;
  --line-dash: #2c3946;
  --warn: #e9a33a;
  --warn-bg: #2a2113;
  --warn-line: #5a4422;
  --warn-ink: #f0c27a;
  --ok-bg: #10261a;
  --ok-line: #1f4a30;
  --ok-ink: #7fd6a0;
  --on-warn: #3d2a06;

  --key-art-line: #07111c;
  --key-art-paper: #c3cfda;
  --key-art-keycap: #a9b7c4;
  --key-art-pad: #b8c5d1;
  --key-art-pad-line: #a9b7c4;
  --key-art-plug: #b9c6d2;
  --key-art-skin: #d3dde6;
  --key-art-skin-shade: #b3c4d4;
  --key-art-nail: #c6d3df;

  --page: #0e141b;
  --surface: #151d26;
  --on-navy: #0e141b;
  --header-bg: rgba(14, 20, 27, 0.9);
  --skeleton-ground: #121a22;
  --skeleton-shine: #1f2a35;
  --line-soft: #1a232d;
  --scrim: rgba(0, 0, 0, 0.55);

  /* Shadows do not read on a dark page; a card separates by its lighter
     surface and its border. Raised panels keep a deeper, black shadow. */
  --shadow-card: none;
  --shadow-lift: 0 18px 36px -16px rgba(0, 0, 0, 0.6);
  --shadow-drop: 0 26px 64px -22px rgba(0, 0, 0, 0.75);
  --shadow-hub: 0 10px 24px rgba(0, 0, 0, 0.45);
  --shadow-invite: 0 14px 30px -18px rgba(0, 0, 0, 0.6);
  --drop-orbit: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.45));

  /* The light patch a custom image logo sits on (see .mark-custom below). */
  --logo-patch: #f4f7fa;
}

*,
*::before,
*::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

a { color: var(--t-accent-ink); text-decoration: none; }
/* Hover text uses the derived hover ink, never the raw accent: the raw
   accent is the brand's color as given and can fall under 4.5:1 in either
   scheme (test/hover-contrast.test.mjs). */
a:hover { color: var(--t-accent-ink-hover); }

button,
input,
select { font: inherit; color: inherit; }
/* A native picker's list takes each option's own colors. Chrome on Windows
   paints it white under a transparent select (the sign-in page's language
   control), and the options inherit the page's ink, so in dark mode the list
   was pale text on white. Both colors are set here so the list always matches
   the scheme, whatever the select's own background. */
option { background-color: var(--surface); color: var(--ink); }
button { cursor: pointer; }

h1, h2, h3 { font-family: var(--font-display); margin: 0; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }

img { max-width: 100%; }

:focus-visible {
  outline: 2px solid var(--t-accent);
  outline-offset: 2px;
  border-radius: 4px;
}

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

/* Layout-specific elements. Hidden by media query rather than by resetting
   `display`, so each element keeps whatever display its own class sets. */
@media (max-width: 1023px) {
  .only-desktop { display: none !important; }
}
@media (min-width: 1024px) {
  .only-mobile { display: none !important; }
}

/* ------------------------------------------------------------ animations */

@keyframes st-rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes st-drift {
  0% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(2%, -2%) scale(1.06); }
  100% { transform: translate(0, 0) scale(1); }
}
@keyframes st-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 1; }
}
@keyframes st-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* The sign-in orbit. One 11s cycle shared by every product, each offset by
   its --d, so the wave of grants repeats in step. A particle's color snaps on
   the frame it lands; the two rings fly different distances, so each has its
   own pair of keyframes. */
@keyframes orbit-spin { to { transform: rotate(360deg); } }
@keyframes orbit-pulse {
  0% { transform: scale(0.55); opacity: 0.5; }
  100% { transform: scale(2.05); opacity: 0; }
}
@keyframes orbit-shoot-inner {
  0% { transform: translateY(-4.6cqw) scale(0.6); opacity: 0; }
  0.7% { opacity: 1; }
  4.2% { opacity: 1; }
  4.73% { transform: translateY(-17.4cqw) scale(1.15); opacity: 0; }
  100% { transform: translateY(-17.4cqw) scale(1.15); opacity: 0; }
}
@keyframes orbit-shoot-outer {
  0% { transform: translateY(-4.6cqw) scale(0.6); opacity: 0; }
  0.7% { opacity: 1; }
  5.1% { opacity: 1; }
  5.64% { transform: translateY(-37.4cqw) scale(1.15); opacity: 0; }
  100% { transform: translateY(-37.4cqw) scale(1.15); opacity: 0; }
}
@keyframes orbit-light-inner {
  0%, 4.72% { opacity: 0; }
  4.98%, 82% { opacity: 1; }
  93%, 100% { opacity: 0; }
}
@keyframes orbit-light-outer {
  0%, 5.63% { opacity: 0; }
  5.89%, 82% { opacity: 1; }
  93%, 100% { opacity: 0; }
}

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

/* The sign-in orbit is the one exception, and only in part (Jules,
   2026-10-02): with reduced motion it stays exactly as it is, except that
   nothing turns. The rings and their icons hold still (the rule above stops
   orbit-spin), while the pulse, the particles and the gray-to-color unlock
   keep running. They are restored here, at a higher specificity, because the
   rule above clears every animation. */
@media (prefers-reduced-motion: reduce) {
  .auth-orbit .orbit-pulse { animation: orbit-pulse 4.6s ease-out infinite !important; }
  .auth-orbit .orbit-ring-inner .orbit-particle { animation: orbit-shoot-inner 11s cubic-bezier(0.38, 0, 0.9, 0.32) var(--d) infinite !important; }
  .auth-orbit .orbit-ring-outer .orbit-particle { animation: orbit-shoot-outer 11s cubic-bezier(0.38, 0, 0.9, 0.32) var(--d) infinite !important; }
  .auth-orbit .orbit-ring-inner .orbit-lit { animation: orbit-light-inner 11s linear var(--d) infinite !important; }
  .auth-orbit .orbit-ring-outer .orbit-lit { animation: orbit-light-outer 11s linear var(--d) infinite !important; }
}

/* ---------------------------------------------------------- boot skeleton

   index.html's neutral page shape, shown from the first paint until the
   first render() (src/boot-skeleton.js removes it and the .booting class).
   It serves every brand host and route, so it is gray shapes only: no
   accent, no logo, no text. While it shows, the empty regions render() will
   fill are hidden, so the page is never a stack of blank bands. This sheet is
   render-blocking in <head>, so these rules are in place before anything
   paints; no inline <style> is needed. */
.booting > header,
.booting > main,
.booting > footer { display: none; }

/* direction: the skeleton paints before the locale is known, so an RTL locale
   would otherwise mirror every bone mid-wait (a measured layout shift). */
.boot-skeleton { min-height: 100vh; background: var(--page); direction: ltr; }
.bs-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: var(--header-h);
  padding: 0 var(--pad-x);
  border-bottom: 1px solid var(--line-2);
}
/* The Workspace shape (src/workspace.js): a phone shows the greeting, the
   Start strip and a grid of launcher tiles; from 640 the tiles become table
   rows; from 1024 the rail stands beside the list. */
.bs-workspace { padding: 20px var(--pad-x) 24px; background: var(--skeleton-ground); }
.bs-hero-text .bs-line + .bs-line { margin-top: -4px; }
.bs-hero { display: grid; gap: 16px; }
.bs-hero-text { display: grid; gap: 12px; }
.bs-table {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px 6px;
  margin-top: 16px;
}
.bs-row { display: grid; justify-items: center; gap: 6px; }
.bs-rail { display: none; }
.bs-bone {
  border-radius: 8px;
  background: linear-gradient(90deg, var(--line-1) 0%, var(--line-1) 40%, var(--skeleton-shine) 50%, var(--line-1) 60%, var(--line-1) 100%) 0 0 / 300% 100%;
}
.boot-skeleton .bs-bone { animation: boot-shimmer 1.6s ease-in-out infinite; }
.bs-logo { width: 112px; height: 28px; }
.bs-pill { width: 44px; height: 44px; border-radius: 999px; }
.bs-eyebrow { width: 40%; max-width: 220px; height: 12px; }
.bs-title { width: 80%; max-width: 520px; height: 62px; }
.bs-line { width: 92%; max-width: 560px; height: 14px; }
.bs-search { height: 52px; border-radius: 999px; }
/* A phone hero also carries the "Working at" line, and its placeholder wraps. */
.bs-hero-text { gap: 16px; }
.bs-hero .bs-search { height: 68px; margin-top: 52px; }
.bs-line-short { width: 60%; }
.bs-head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 40px; }
.bs-head-title { width: 140px; height: 20px; }
.bs-sort { flex: none; width: 190px; height: 40px; border-radius: 999px; }
.bs-strip { height: 78px; margin-top: 14px; border-radius: 14px; }
.bs-icon { width: 54px; height: 54px; border-radius: 14px; }
.bs-name { width: 70%; height: 10px; }

@media (min-width: 640px) {
  .bs-strip { display: none; }
  .bs-title { height: 34px; }
  .bs-hero-text { gap: 12px; }
  .bs-hero .bs-search { height: 52px; margin-top: 8px; }
  .bs-head { justify-content: space-between; margin-top: 56px; }
  .bs-table { display: block; margin-top: 12px; padding: 6px 14px; border-radius: 14px; background: var(--surface); border: 1px solid var(--line-2); }
  .bs-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; }
  .bs-icon { width: 32px; height: 32px; border-radius: 8px; }
  .bs-name { width: 40%; height: 12px; }
  .bs-rail { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 18px; }
  .bs-rail-card { height: 150px; border-radius: 14px; }
}

@media (min-width: 1024px) {
  .bs-header { height: 52px; padding: 0 24px; }
  .bs-pill { width: 160px; height: 32px; border-radius: 7px; }
  .bs-workspace {
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(260px, 25vw, 320px);
    align-items: start;
    gap: 20px;
    padding: 18px max(48px, calc((100% - 1240px) / 2)) 28px;
  }
  .bs-hero { grid-template-columns: 1fr minmax(0, 380px); align-items: center; gap: 32px; }
  .bs-title { height: 26px; }
  .bs-head { margin-top: 24px; }
  .bs-hero .bs-search { margin-top: 0; }
  .bs-rail { grid-template-columns: 1fr; margin: 0; }
  .bs-rail-card { height: 210px; }
}

@keyframes boot-shimmer {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .boot-skeleton .bs-bone { animation: none; }
}

/*
 * render() rewrites #hero and #apps wholesale on every state change,
 * including every keystroke in the search field (see app.js). .hero-inner,
 * .hero-search carry a one-time "rise in" entrance animation, so a naive
 * rebuild replayed it on every character typed — the whole search box
 * visibly flashed as you typed. app.js
 * adds .is-settled to <body> once the first paint has happened, so the
 * entrance only ever plays that once.
 */
body.is-settled .hero-inner,
body.is-settled .hero-search {
  animation: none;
}

/*
 * Same story for an already-open overlay: #overlays is rebuilt wholesale on
 * every render too, so typing in the mobile search sheet (or any panel's
 * search field) replayed .scrim/.panel's entrance animation every keystroke.
 * app.js adds .is-reentered when the overlay didn't just open — it's the
 * same one being redrawn with fresh content.
 */
.scrim.is-reentered,
.panel.is-reentered {
  animation: none;
}

/* ---------------------------------------------------------------- shared */

.dot {
  display: inline-block;
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--t-accent);
}
.dot-ok { background: var(--t-accent); animation: st-pulse 2.6s ease-in-out infinite; }
.dot-warn { background: var(--warn); animation: st-pulse 2.6s ease-in-out infinite; }
.dot-current { background: currentColor; }

.section-inner { max-width: 1240px; margin: 0 auto; }

/* The tile's product name is the real link, stretched over the whole tile so
   the tile stays one big click target without nesting a link inside a link.
   Anything that must stay clickable in its own right (the admin chip) lifts
   above this layer with z-index. */
.stretch-link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* Second destination on the two products whose admin console is its own host. */
.admin-chip {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  flex: none;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--t-accent-line);
  background: var(--t-accent-tint);
  /* Deliberately a step below .open-link: the card footer has to carry the use
     line, this chip and "Open →" on one line inside ~285px, and the chip is the
     secondary destination of the three. */
  font-size: 11px;
  font-weight: 600;
  color: var(--t-accent-ink);
  white-space: nowrap;
  transition: background 0.16s ease, border-color 0.16s ease;
}
.admin-chip:hover {
  background: var(--surface);
  border-color: var(--t-accent);
  color: var(--t-accent-ink);
}

.tabular { font-variant-numeric: tabular-nums; }

.is-fresh { animation: st-fade 0.34s ease both; }

.avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--t-accent-tint);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 700;
  color: var(--t-accent-ink);
}
.avatar-lg { width: 44px; height: 44px; font-size: 15px; }

.caret { display: block; flex: none; color: var(--ink-3); }

/* Product code chip — the mark used where a 64px tile does not fit. */
.code-chip {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--chip-size, 34px);
  height: var(--chip-size, 34px);
  border-radius: calc(var(--chip-size, 34px) * 0.25);
  background: var(--chip-bg);
  color: var(--chip-fg);
  font-family: var(--font-display);
  font-size: calc(var(--chip-size, 34px) * 0.3);
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* --------------------------------------------------------------- buttons */

.button-primary,
.button-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 52px;
  padding: 15px 26px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.16s ease,
    background 0.18s ease, color 0.16s ease;
}

.button-primary {
  background: var(--t-accent-fill);
  color: var(--t-on-accent);
  box-shadow: 0 6px 18px var(--t-glow-28);
}
.button-primary:hover {
  color: var(--t-on-accent);
  transform: translateY(-2px);
  box-shadow: 0 12px 26px var(--t-glow-34);
}
.button-primary.button-static { pointer-events: none; min-height: 0; padding: 12px 20px; font-size: 14px; }

.button-ghost {
  background: var(--surface);
  border-color: var(--line-4);
  color: var(--ink-2);
}
.button-ghost:hover { border-color: var(--t-accent); color: var(--t-navy); }

.button-sm { min-height: 44px; padding: 10px 18px; font-size: 14px; }

.icon-button {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line-3);
  font-size: 15px;
  color: var(--ink-2);
}
.icon-button:hover { border-color: var(--t-accent-line); color: var(--t-navy); }
.icon-button-sm { width: 30px; height: 30px; font-size: 13px; }

/* ---------------------------------------------------------------- header */

.header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--header-bg);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line-2);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: var(--header-h);
  padding: 0 var(--pad-x);
}

/* flex: none. It used to be the header's only shrinkable item (min-width: 0
   plus overflow: hidden), so when the controls ran out of room the logo was
   what gave, down to zero width: the "no logo on desktop" of UI audit round 1. */
.header-brand {
  display: flex;
  flex: none;
  align-items: center;
  height: var(--header-h);
  overflow: hidden;
}

/* Three forms of the brand mark, sized independently.
   .mark-sentinel  the supplied lockup — lots of internal padding, so it is
                   scaled past the bar height and pulled back to its optical edge
   .mark-custom    a lockup dropped in via Brand setup — unknown padding, so it
                   is simply fitted to the bar
   .mark-text      no file supplied: the deployment name set in Geist */
.brand-mark { display: block; flex: none; }

.header-mark.mark-sentinel { height: 126px; width: auto; margin-inline-start: -26px; }
/* At 360 the 126px lockup plus the status dot and menu button came to 374px:
   a horizontal scroll on the smallest phones (UI audit round 1, item 5). */
@media (max-width: 389px) {
  .header-mark.mark-sentinel { height: 100px; margin-inline-start: -21px; }
}
/* A phone header that also carries the labeled status chip (UI audit round 2,
   item 10) gives the chip its room from the lockup, not from the page width. */
@media (max-width: 1023px) {
  .header-inner:has(.status-chip-mobile) .header-mark.mark-sentinel { height: 100px; margin-inline-start: -21px; }
}
/* Round 3 split the phone menu button in two (avatar = account, glyph =
   apps), so with the chip the lockup gives a little more. */
@media (max-width: 439px) {
  .header-inner:has(.status-chip-mobile) .header-mark.mark-sentinel { height: 80px; margin-inline-start: -17px; }
}
@media (max-width: 389px) {
  .header-inner:has(.status-chip-mobile) .header-mark.mark-sentinel { height: 70px; margin-inline-start: -15px; }
  .header-inner:has(.status-chip-mobile) .header-mobile { gap: 6px; }
  .status-chip-mobile { padding: 0 10px; }
}
.header-mark.mark-custom { height: 40px; width: auto; max-width: 200px; object-fit: contain; }
/* Square and tall logos (data-shape, set by app.js from the image's own size)
   get more height than a wide lockup, so they are not shrunk to a 32px
   square or a 16px sliver. Tall ones take what the bar allows, with a
   slimmer dark patch so logo plus patch still fit (test/logo-shape.test.mjs). */
.header-mark.mark-custom[data-shape="compact"] { height: 44px; }
.header-mark.mark-custom[data-shape="tall"] { height: 52px; }
.header-mark.mark-text {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.026em;
  line-height: 1.1;
  color: var(--t-navy);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A name-only brand is the one mark that can safely give: it ellipsizes and
   stays legible, where a lockup would vanish. Without this a long name
   ("Grand Nusantara Hotels & Resorts", 263px) kept flex: none and pushed the
   375 phone header's controls past the edge into a horizontal scroll. The
   floor keeps a few readable letters before the controls start to give.
   Phone only: on desktop the name is capped at 300px instead (desktop block)
   and the header-fit levels compact the controls. The shrink weight is high so the name gives way
   first: .header-mobile is min-width: 0 (for its status chip), and at equal
   weights a 60-character name and the controls shrank in proportion, leaving
   the menu button 17px past a 375px viewport (test/long-brand-name.test.mjs). */
@media (max-width: 1023px) {
  .header-brand:has(.mark-text) { flex: 0 100 auto; min-width: 96px; }
  .header-brand .mark-text { flex: 0 1 auto; min-width: 0; }
}

/* min-width: 0 so a long status label ("1 pemberitahuan") shrinks the chip
   instead of pushing the menu button past the edge at 390 px; the chip's
   accessible name carries the full status line either way. */
.header-mobile { display: flex; align-items: center; gap: 8px; min-width: 0; }

.status-dot-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  border: 1px solid var(--line-3);
}
.status-dot-button.is-warn { border-color: var(--warn-line); }
.status-dot-button .dot { width: 8px; height: 8px; }

.menu-button {
  display: flex;
  align-items: center;
  gap: 9px;
  height: 44px;
  padding: 0 6px 0 5px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line-3);
}
.avatar-sm { width: 34px; height: 34px; }
/* The phone header's account entry point (UI audit round 3, item 2). */
.avatar-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line-3);
}
.menu-button .menu-glyph { padding-inline: 8px; }
.panel-account-sheet .account-head { padding: 4px 0 14px; }
.panel-sheet .panel-divider + .panel-links { margin-top: 0; border-top: 0; }
.menu-glyph { padding-inline-end: 8px; font-size: 15px; color: var(--t-navy); }

.header-rule { flex: none; width: 1px; height: 20px; background: var(--line-2); }
.header-spacer { flex: 1; }

/* Option A3 (Jules, 2026-10-04): a slim bar at the page's own type scale.
   The home had gone compact (13-14px text, 38px rows) and a 104px header of
   15px pills sat on top of it like a different product. Every control is now
   a quiet 32px button: no border, a tint on hover, 14px text. */
.scope-button,
.scope-static {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 9px;
  border-radius: 7px;
  background: transparent;
  border: 0;
  text-align: start;
  transition: background 0.18s ease;
}
.scope-button:hover { background: var(--t-band); }
/* "Working at" is the button's accessible name only (.visually-hidden in
   the markup): the bar has no room for a label line, and the caret plus the
   menu say what the button does. A long name truncates at 22em rather than
   push the tabs; the title attribute has it in full. */
.scope-value {
  display: block;
  max-width: 22em;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
}

/* flex: none for the same reason as .header-brand: a shrinkable nav wrapped
   "All apps" onto two lines inside its pill. The nav stretches to the bar's
   full height so a tab's underline sits on the header's bottom border. */
.header-nav { flex: none; display: flex; align-self: stretch; align-items: stretch; margin-inline-start: 6px; }

/* No header control wraps its label (Jules saw "All apps" break in two in a
   screenshot, 2026-10-04): every control below is nowrap, and
   test/header-a3.test.mjs reads this list. When the row does not fit,
   fitHeader() in app.js sets data-fit on .header-inner, one level at a time,
   and each level gives space back in this order (each keeps the ones before):
     1  the status text truncates (full text stays its accessible name)
     2  the account button drops the name, keeping avatar and caret
     3  Support leaves the bar (it is in the account menu)
     4  the status button shows only its dot
     5  the property name truncates */
.header-inner .scope-button,
.header-inner .scope-static,
.header-inner .scope-value,
.header-inner .header-tab,
.header-inner .status-pill,
.header-inner .account-button,
.header-inner .account-name,
.header-inner .account-role { white-space: nowrap; }
.status-pill-text { overflow: hidden; text-overflow: ellipsis; }
.header-inner:is([data-fit="1"], [data-fit="2"], [data-fit="3"], [data-fit="4"], [data-fit="5"]) .status-pill-text { max-width: 12em; }
.header-inner:is([data-fit="2"], [data-fit="3"], [data-fit="4"], [data-fit="5"]) .account-body { display: none; }
.header-inner:is([data-fit="3"], [data-fit="4"], [data-fit="5"]) .nav-link { display: none; }
.header-inner:is([data-fit="4"], [data-fit="5"]) .status-pill-text { display: none; }
.header-inner:is([data-fit="4"], [data-fit="5"]) .status-pill { width: 32px; justify-content: center; padding: 0; }
.header-inner[data-fit="5"] .scope-button,
.header-inner[data-fit="5"] .scope-static { flex: 0 1 auto; min-width: 0; }
.header-inner[data-fit="5"] .scope-value { min-width: 0; }

/* The two tabs (A3): plain text with a 2px underline under the current one,
   in place of the filled "All apps" pill. */
.header-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  background: none;
  border: 0;
  border-radius: 0;
  font-size: 14px;
  font-weight: 400;
  color: var(--ink-2);
  box-shadow: inset 0 -2px 0 transparent;
  transition: color 0.16s ease, box-shadow 0.16s ease;
}
.header-tab:hover { color: var(--ink); }
.header-tab.is-current { color: var(--t-accent-ink); font-weight: 500; box-shadow: inset 0 -2px 0 var(--t-accent); }
.header-tab[aria-expanded="true"] { background: var(--t-band); }
.tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  border-radius: 9px;
  background: var(--t-accent-tint);
  font-size: 11px;
  font-weight: 600;
  color: var(--t-accent-ink);
}

.status-pill {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 9px;
  border-radius: 7px;
  background: none;
  border: 0;
  font-size: 13px;
  font-weight: 400;
  color: var(--ink-2);
  transition: background 0.18s ease, color 0.16s ease;
}
.status-pill.is-warn { color: var(--warn-ink); }
.status-pill:hover { background: var(--t-band); color: var(--ink); }
.status-pill .dot { width: 7px; height: 7px; }

.account-button {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 8px 0 4px;
  border-radius: 7px;
  background: none;
  border: 0;
  text-align: start;
  transition: background 0.18s ease;
}
.account-button:hover { background: var(--t-band); }
.account-button .avatar { width: 28px; height: 28px; font-size: 11px; }
.account-body { line-height: 1.15; }
.account-name {
  display: block;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
}
.account-role { display: block; margin-top: 1px; font-size: 11px; color: var(--ink-3); }

/* ------------------------------------------------------------------ hero */

.hero {
  position: relative;
  overflow: hidden;
  padding: 20px var(--pad-x) 22px;
  background: linear-gradient(180deg, var(--t-band) 0%, var(--page) 100%);
  border-bottom: 1px solid var(--line-1);
}

.hero-glow {
  position: absolute;
  top: -260px;
  /* Deliberately physical, not logical: a decorative glow position, not
     reading-direction-dependent content — mirroring it under RTL would look
     wrong, not right. */
  right: -180px;
  width: 520px;
  height: 520px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--t-glow-13) 0%, rgba(39, 165, 247, 0) 68%);
  animation: st-drift 20s ease-in-out infinite;
  pointer-events: none;
}

.hero-inner {
  position: relative;
  max-width: 1240px;
  margin: 0 auto;
  animation: st-rise 0.5s ease 0.04s both;
}

.eyebrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 9px;
  font-size: 13px;
  font-weight: 600;
  color: var(--t-accent-ink);
}
/* One line, ellipsized: a 62-character brand name made "<name> portal" wrap
   to two lines at 390 and push the clock to a third (round 2026-10-05,
   test/long-brand-name.test.mjs). The title carries the full text. */
.eyebrow-live { display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%; }
.eyebrow-live .dot { flex: none; }
.eyebrow-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.eyebrow-live .dot { width: 6px; height: 6px; }
.eyebrow-clock { font-weight: 400; color: var(--ink-4); }

.hero-title {
  font-size: clamp(22px, 6.4vw, 27px);
  font-weight: 600;
  line-height: 1.16;
  letter-spacing: -0.026em;
  margin-top: 10px;
  color: var(--t-navy);
  text-wrap: balance;
}

.hero-shift { margin-top: 9px; font-size: 15px; line-height: 1.55; color: var(--ink-2); text-wrap: pretty; }

.hero-scope {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-top: 14px;
  font-size: 13px;
  color: var(--ink-3);
}
.hero-scope-name { font-weight: 600; color: var(--t-navy); }
.hero-scope a { font-size: 13px; font-weight: 600; }

.search-proxy {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 52px;
  margin-top: 16px;
  padding: 13px 18px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line-4);
  box-shadow: var(--shadow-card);
  text-align: start;
  font-size: 16px;
  color: var(--ink-4);
}
.search-proxy:hover { border-color: var(--t-accent-line); }
/* One line high: the example list is a hint, so it truncates rather than wrap. */
.search-proxy > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.search-field {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px 18px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line-4);
  box-shadow: var(--shadow-card);
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}
.search-field:focus-within {
  border-color: var(--t-accent);
  box-shadow: 0 0 0 3px var(--t-glow-16);
}
.search-field input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font-size: 16px;
  color: var(--ink);
}
.search-hint { display: block; padding-inline-start: 18px; font-size: 13px; color: var(--ink-3); }

/* The laptop's slim search, on the "Your apps" row (option A3; markup in
   src/workspace.js deskSearch). 34px, the page's 14px type, a "/" key hint. */
.ws-search {
  flex: 0 1 260px;
  min-width: 180px;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  margin-inline-start: auto;
  padding: 0 8px 0 10px;
  border-radius: 8px;
  background: var(--t-band);
  border: 1px solid var(--line-3);
  color: var(--ink-3);
  transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
}
.ws-head .segmented ~ .ws-search { margin-inline-start: 0; }
.ws-search:focus-within {
  background: var(--surface);
  border-color: var(--t-accent);
  box-shadow: 0 0 0 3px var(--t-glow-16);
}
.ws-search-icon { flex: none; }
.ws-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font-size: 14px;
  color: var(--ink);
}
.ws-search input::placeholder { color: var(--ink-3); }
.ws-search-key {
  flex: none;
  padding: 0 5px;
  border-radius: 4px;
  border: 1px solid var(--line-3);
  background: var(--surface);
  font: 500 11px/16px var(--font-body);
  color: var(--ink-3);
}
/* The hint is for an empty field; once the person is in it, it is noise. */
.ws-search:focus-within .ws-search-key { display: none; }

/* ------------------------------------------------------------- workspace

   The signed-in home since 2026-10-04 (option C, "Workspace"; markup in
   src/workspace.js and app.js). Three shapes:

   - phone (< 640): a "Start with" strip, the person's apps as launcher
     tiles, a one-line status note, the other apps as faded tiles, then the
     rail's coming-soon chips and a slim support block. The device numbers
     move to the footer.
   - tablet (640-1023): the list becomes a table, and the rail's cards sit
     under it, two to a row.
   - laptop (>= 1024): the table in a main column with the greeting above
     it, and the rail beside it.

   The same row markup is a tile and a table row, so each app has one launch
   link in each layout; the started app's tile steps aside for the strip on
   a phone (.is-started), and the strip is hidden from 640 up. */

.workspace {
  padding-bottom: 24px;
  background: var(--t-band);
  border-bottom: 1px solid var(--line-1);
}
.ws-col { min-width: 0; }

.apps { padding: 14px var(--pad-x) 4px; }

.ws-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
.ws-title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.016em;
  line-height: 1.2;
  color: var(--t-navy);
}
.ws-note { flex: 0 1 auto; font-size: 13px; color: var(--ink-3); }
.ws-head .segmented { margin-inline-start: auto; }

.segmented {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  flex: none;
  padding: 3px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line-3);
}
.segmented-thumb {
  position: absolute;
  top: 3px;
  inset-inline-start: 3px;
  bottom: 3px;
  width: calc(50% - 3px);
  border-radius: 999px;
  background: var(--t-navy);
  transition: transform 0.18s ease;
  pointer-events: none;
}
.segmented button {
  position: relative;
  min-height: 34px;
  padding: 5px 14px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  color: var(--ink-2);
  transition: color 0.18s ease;
}
.segmented button[aria-pressed='true'] { color: var(--on-navy); }
/* Three labels of very different lengths ("Paling sering digunakan"): each
   button keeps its own width and the pressed one carries the fill, so no
   sliding thumb has to match them. */
.segmented-3 { display: inline-flex; flex-wrap: wrap; max-width: 100%; }
.segmented-3 button[aria-pressed='true'] { background: var(--t-navy); }

/* The phone's strip for the hour's top pick. */
.ws-start {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-top: 12px;
  padding: 11px 13px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--t-accent-line);
  box-shadow: 0 0 0 3px var(--t-accent-tint);
  color: var(--ink);
}
.ws-start:hover { color: var(--ink); border-color: var(--t-accent); }
.ws-start img { display: block; flex: none; width: 40px; height: 40px; }
.ws-start-text { flex: 1; min-width: 0; }
.ws-start-title {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--t-navy);
}
.ws-start-reason {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  margin-top: 2px;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--ink-2);
}
.ws-start-open {
  flex: none;
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--t-accent-fill);
  font-size: 13px;
  font-weight: 600;
  color: var(--t-on-accent);
}

/* Phone: tiles. */
.ws-th { display: none; }
.ws-list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px 6px;
  margin-top: 16px;
}
.ws-row {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  min-width: 0;
  text-align: center;
}
.ws-row.is-started { display: none; }
/* The name link is stretched over the whole row or tile, so the focus ring
   goes on the row, where the click target actually is. */
.ws-row:has(.ws-name:focus-visible) { outline: 2px solid var(--t-accent); outline-offset: 3px; border-radius: 12px; }
/* Written with :has() too, so a browser without :has() keeps the link's own ring. */
.ws-row:has(.ws-name:focus-visible) .ws-name:focus-visible { outline: none; }
.ws-icon { display: block; width: 54px; height: 54px; }
.ws-badge {
  position: absolute;
  top: -3px;
  inset-inline-end: calc(50% - 31px);
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--t-band);
}
.ws-main { display: flex; flex-direction: column; align-items: center; min-width: 0; max-width: 100%; }
.ws-name-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 6px; min-width: 0; max-width: 100%; }
.ws-name {
  min-width: 0;
  max-width: 100%;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  overflow-wrap: anywhere;
  color: var(--t-navy);
}
.ws-name:hover, .ws-row:hover .ws-name { color: var(--t-navy); }
.ws-full { display: none; }
.ws-tag,
.ws-reason,
.ws-status,
.ws-opens,
.ws-act,
.ws-locked-note { display: none; }
.ws-last { font-size: 11px; line-height: 1.2; color: var(--ink-3); }

.ws-dot-ok { background: var(--t-accent); }
.ws-dot-warn { background: var(--warn); }
.ws-dot-preview { background: var(--ink-3); }

.ws-row.is-locked { opacity: 0.6; }
.ws-row.is-locked .ws-name { font-weight: 500; color: var(--ink-2); }
.ws-row.is-locked .code-chip { margin-block: 5px; }
/* The admin console link sits on the name line from 640 up; a tile has no room for it, and the phone's apps sheet carries it. */
.ws-row .admin-chip { display: none; }

.ws-empty { margin-top: 14px; font-size: 14px; color: var(--ink-3); }

.ws-status-note {
  margin-top: 16px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-2);
  text-wrap: pretty;
}
.ws-status-note.is-warn {
  padding: 9px 11px;
  border-radius: 10px;
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  color: var(--warn-ink);
}

.ws-group { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; margin-top: 20px; }
.ws-group-title { font-size: 15px; font-weight: 600; color: var(--t-navy); }
.ws-group-note { font-size: 12.5px; line-height: 1.4; color: var(--ink-3); }
.ws-list-locked,
.ws-list-separate { margin-top: 12px; }

.empty-card {
  margin-top: 14px;
  padding: 20px 18px;
  border-radius: 14px;
  border: 1px dashed var(--line-4);
  background: var(--surface);
}
.empty-title { font-family: var(--font-display); font-size: 16px; font-weight: 600; color: var(--t-navy); text-wrap: balance; }
.empty-body { margin-top: 8px; font-size: 14px; line-height: 1.55; color: var(--ink-2); text-wrap: pretty; }

/* ------------------------------------------------------------------ rail */

.rail { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; padding: 0 var(--pad-x); }
.rail-card {
  min-width: 0;
  padding: 11px 14px 12px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--line-2);
}
.rail-title {
  margin-bottom: 6px;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--t-navy);
  text-wrap: balance;
}

/* The phone keeps its status note in the list and the device line in the
   footer, so those two cards start from 640. Coming soon comes before
   support there, as in the mockup. */
.rail-live-card,
.rail .device { display: none; }
.rail .upcoming { order: 1; }
.rail .support { order: 2; }

.rail-live { display: flex; align-items: center; gap: 8px; font-size: 13.5px; font-weight: 600; color: var(--t-navy); }
.rail-live.is-warn { color: var(--warn-ink); }
.rail-notices { margin-top: 8px; display: grid; gap: 6px; }
.rail-notices li {
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  font-size: 12.5px;
  color: var(--warn-ink);
}
.rail-notices small { display: block; margin-top: 2px; font-size: 12px; line-height: 1.35; }
.rail-sub { margin-top: 7px; font-size: 12.5px; line-height: 1.45; color: var(--ink-3); text-wrap: pretty; }
.rail-link { display: inline-block; margin-top: 8px; font-size: 13px; font-weight: 600; }
.rail-body { font-size: 13px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }

/* Coming soon: chips on a phone, a list with a line each from 640. */
.rail-upcoming { display: flex; flex-wrap: wrap; gap: 6px; }
.rail-upcoming li {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  padding: 5px 10px 5px 6px;
  border-radius: 999px;
  background: var(--t-band);
  border: 1px solid var(--line-2);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--t-navy);
}
.rail-upcoming img { display: block; flex: none; width: 20px; height: 20px; }
.rail-upcoming small { display: none; }

/* Support: slim on a phone (no body line), the full compact card from 640. */
.support .rail-body { display: none; }
/* Two lines, name then hours, with no separator glyph: a separator that
   wrapped started a line with a stray "·" (UI audit round 5, item 2). */
.support-line { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.support-line .support-sub { padding-inline-start: 16px; } /* under the name, past the dot */
.support-name { display: inline-flex; align-items: center; gap: 9px; font-size: 13.5px; font-weight: 600; color: var(--t-navy); }
.support-sub { font-size: 12.5px; color: var(--ink-3); }
.support-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.support-actions > * { flex: 1 1 auto; min-height: 38px; padding: 7px 14px; font-size: 13.5px; line-height: 1.3; box-shadow: none; }

.rail .stats { display: flex; flex-wrap: wrap; gap: 8px 22px; }
.rail .stat-value { font-family: var(--font-display); font-size: 20px; font-weight: 600; line-height: 1.1; color: var(--t-navy); }
.rail .stat-label { margin-top: 2px; font-size: 12px; color: var(--ink-3); }

/* The footer carries this device's numbers on a phone only. */
.footer-device { flex: 1 1 100%; font-size: 12.5px; line-height: 1.45; color: var(--ink-2); }

@media (min-width: 640px) {
  .ws-start { display: none; }
  .ws-row.is-started { display: grid; }
  .ws-status-note { display: none; }
  .footer-device { display: none; }

  .apps { padding-top: 18px; }

  /* The list becomes one table: the person's apps, then the other apps. */
  .ws-table {
    margin-top: 12px;
    overflow: hidden;
    border-radius: 14px;
    background: var(--surface);
    border: 1px solid var(--line-2);
    box-shadow: var(--shadow-card);
  }
  .ws-th,
  .ws-row {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr) 112px 96px 72px;
    align-items: center;
    gap: 12px;
    padding: 9px 14px;
    text-align: start;
  }
  .ws-th {
    padding-block: 8px;
    background: var(--t-band);
    font-size: 11.5px;
    font-weight: 600;
    color: var(--ink-3);
  }
  .ws-th > :first-child { grid-column: 1 / 3; }
  /* Opens is the column a narrow table gives up first. */
  .ws-th > :nth-child(3),
  .ws-opens { display: none !important; }
  .ws-list { display: block; margin: 0; }
  .ws-row { border-top: 1px solid var(--line-1); font-size: 13px; }
  .ws-row:hover { background: var(--t-band); }
  .ws-row:has(.ws-name:focus-visible) { outline-offset: -2px; border-radius: 0; }
  .ws-icon { width: 32px; height: 32px; }
  .ws-badge { display: none; }
  .ws-main { display: block; }
  .ws-name-line { display: flex; }
  .ws-name { font-size: 14px; line-height: 1.25; overflow-wrap: normal; }
  .ws-full { display: inline; }
  .ws-short { display: none; }
  .ws-tag {
    display: inline-block;
    flex: none;
    padding: 2px 7px;
    border-radius: 999px;
    background: var(--t-accent-tint);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--t-accent-ink);
  }
  .ws-tag.is-first { background: var(--t-accent-fill); color: var(--t-on-accent); }
  .ws-reason {
    display: block;
    margin-top: 1px;
    overflow: hidden;
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink-3);
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .ws-status { display: inline-flex; align-items: center; gap: 6px; min-width: 0; font-size: 12.5px; line-height: 1.3; color: var(--ink-2); }
  .ws-status .dot { animation: none; }
  .ws-status > span:last-child::first-letter { text-transform: uppercase; }
  .ws-status > span:last-child { display: inline-block; }
  .ws-opens,
  .ws-last { display: block; font-variant-numeric: tabular-nums; font-size: 12.5px; color: var(--ink-2); }
  .ws-act { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
  .ws-open {
    padding: 4px 12px;
    border-radius: 999px;
    border: 1px solid var(--t-accent-line);
    background: var(--t-accent-tint);
    font-size: 12.5px;
    font-weight: 600;
    white-space: nowrap;
    color: var(--t-accent-ink);
  }
  .ws-row:hover .ws-open { border-color: var(--t-accent); }

  .ws-empty { margin: 0; padding: 14px; border-top: 1px solid var(--line-1); }
  .ws-group {
    margin: 0;
    padding: 8px 14px;
    border-top: 1px solid var(--line-1);
    background: var(--t-band);
  }
  .ws-group-title { font-size: 12px; color: var(--ink-3); }
  .ws-group-note { font-size: 12px; }
  .ws-list-locked,
  .ws-list-separate { margin: 0; }
  /* A separate app's status says how it signs in, so it reads as a note, not a state. */
  .ws-status-separate { color: var(--ink-3); }
  .ws-row.is-locked { opacity: 1; color: var(--ink-3); }
  /* Off-account rows are secondary: about 38 px each. */
  .ws-row.is-locked { padding-block: 5px; }
  .ws-row.is-locked .code-chip { width: 26px; height: 26px; margin: 0 3px; border-radius: 7px; font-size: 8.5px; opacity: 0.55; }
  .ws-row .admin-chip { display: inline-flex; padding: 1px 8px; font-size: 10.5px; }
  .ws-row.is-locked .ws-name { font-size: 13px; font-weight: 500; color: var(--ink-2); }
  .ws-locked-note { display: block; grid-column: 3 / -1; font-size: 12.5px; color: var(--ink-3); }

  .search-results .empty-card { margin: 14px; }

  /* The rail's cards under the table, two to a row. */
  .rail {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    margin-top: 18px;
  }
  .rail-live-card,
  .rail .device { display: block; }
  .rail[hidden], .rail-card[hidden] { display: none; }
  .rail .upcoming,
  .rail .support { order: 0; }
  .support .rail-body { display: block; margin-bottom: 8px; }
  .rail-upcoming { display: grid; gap: 2px; }
  .rail-upcoming li {
    display: flex;
    gap: 9px;
    padding: 4px 0;
    border: 0;
    border-radius: 0;
    background: none;
    font-size: 13px;
  }
  .rail-upcoming img { width: 26px; height: 26px; }
  .rail-upcoming small { display: block; font-size: 11.5px; font-weight: 400; line-height: 1.3; color: var(--ink-3); }
}

@media (min-width: 1024px) {
  /* Greeting and list on the start side, the rail beside them. */
  .workspace {
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(260px, 25vw, 320px);
    align-items: start;
    gap: 20px;
    padding: 14px max(var(--pad-x), calc((100% - 1240px) / 2)) 20px;
  }
  .ws-th > :nth-child(3) { display: block !important; }
  .ws-opens { display: block !important; }
  .ws-th,
  .ws-row { grid-template-columns: 32px minmax(0, 1fr) 112px 56px 100px 72px; }
  .rail { display: flex; align-items: stretch; margin: 0; padding: 0; }
  .apps { padding: 0; }
}

/* --------------------------------------------------------------- footer */

/* Compact since the Workspace layout (round 2, 2026-10-04): one row of
   small mark, blurb, device line and badges, then the copyright. It wraps
   on a phone; from 1024 it is one line. */
.footer { padding: 16px var(--pad-x) 14px; background: var(--t-band); border-top: 1px solid var(--line-2); }
.footer-top { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 20px; }
.footer-brand { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; min-width: 0; flex: 1 1 260px; }
.footer-mark.mark-sentinel { height: 52px; width: auto; margin: -8px -4px -8px -6px; }
.footer-mark.mark-custom { height: 28px; width: auto; max-width: 160px; object-fit: contain; }
.footer-mark.mark-text {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--t-navy);
  /* A long name ran 150px off a 390 phone, scrolling the whole page. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.footer-blurb { flex: 1 1 240px; min-width: 0; font-size: 12.5px; line-height: 1.45; color: var(--ink-2); text-wrap: pretty; max-width: 46em; }
.certs { display: flex; flex-wrap: wrap; gap: 6px; }
.cert {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 10px 4px 5px;
  border-radius: 7px;
  background: var(--surface);
  border: 1px solid var(--line-3);
  font-size: 12px;
  font-weight: 600;
  color: var(--t-navy);
}
.cert-tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  background: var(--t-accent-tint);
  color: var(--t-accent-ink);
  font-size: 10px;
  font-weight: 700;
}
.runs-on { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line-3); font-size: 12.5px; line-height: 1.45; color: var(--ink-2); text-wrap: pretty; }
.runs-on-logo { display: block; height: 28px; width: auto; margin-inline-start: -3px; }
.runs-on > div:not(.certs) { flex: 1 1 220px; min-width: 0; }
.footer-legal {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--line-3);
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-3);
  text-wrap: pretty;
}

/* ------------------------------------------------------ overlays: shared */

.scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: transparent;
}
.scrim-brand { z-index: 55; }

.panel {
  position: fixed;
  z-index: 50;
  background: var(--surface);
  animation: st-fade 0.2s ease both;
}

/* Full-screen sheet — the phone form. */
.panel-sheet {
  inset: 0;
  display: flex;
  flex-direction: column;
}
.panel-brand { z-index: 60; }

.panel-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: var(--header-h);
  padding: 0 var(--pad-x);
  border-bottom: 1px solid var(--line-1);
}
.panel-head-tall { height: auto; align-items: flex-start; padding: 18px var(--pad-x) 14px; }
.panel-title { font-family: var(--font-display); font-size: 17px; font-weight: 600; letter-spacing: -0.02em; color: var(--t-navy); }

.panel-body { flex: 1; overflow: auto; padding: 16px var(--pad-x) 40px; }
.brand-body { display: flex; flex-direction: column; gap: 26px; padding-top: 18px; }

.panel-count { margin: 9px 0 0; padding-inline-start: 16px; font-size: 13px; color: var(--ink-3); }
.panel-list { display: flex; flex-direction: column; gap: 2px; margin-top: 14px; }
.panel-block { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line-1); }
.panel-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink-4);
}
.panel-label-pad { padding: 10px 12px 8px; }
.panel-hint { margin-top: 9px; font-size: 13px; line-height: 1.5; color: var(--ink-4); text-wrap: pretty; }

/* Locked products appear as chips in two places — the phone sheet and the
   desktop launcher's foot — so the chip itself lives outside both breakpoints.
   Only the containers below are per-layout. */
.panel-locked { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.locked-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px 5px 6px;
  border-radius: 999px;
  border: 1px dashed var(--line-dash);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-3);
  transition: border-color 0.16s ease, color 0.16s ease;
}
.locked-chip:hover { border-color: var(--t-accent-line); color: var(--t-accent-ink); }
.panel-hint-tight { margin-top: 6px; }
.panel-hint-top { margin: 6px 4px 4px; padding: 12px 8px 6px; border-top: 1px solid var(--line-1); }
.panel-warn { margin-top: 8px; font-size: 13px; line-height: 1.5; color: var(--warn-ink); }
.panel-meta { font-size: 13px; color: var(--ink-4); }
.panel-divider { height: 1px; margin: 6px 4px; background: var(--line-1); }
.panel-empty {
  margin-top: 14px;
  padding: 16px 18px;
  border-radius: 12px;
  border: 1px dashed var(--line-4);
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-2);
  text-wrap: pretty;
}

.app-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 13px;
  min-height: 60px;
  padding: 10px 8px;
  border-radius: 12px;
  border: 1px solid transparent;
  color: var(--ink);
  transition: background 0.18s ease, border-color 0.16s ease, transform 0.18s ease;
}
.app-row:hover {
  background: var(--t-band);
  border-color: var(--t-accent-line);
  color: var(--ink);
}
.app-row-icon { display: block; flex: none; width: 40px; height: 40px; }
.app-row-body { flex: 1; min-width: 0; }
.app-row-name { display: block; font-family: var(--font-display); font-size: 16px; font-weight: 600; color: var(--t-navy); }
/* The row already signals hover with its own background; the name staying navy
   keeps that from reading as two separate hover states. */
.app-row:hover .app-row-name, .app-row-name:hover { color: var(--t-navy); }
/* Wraps rather than truncating beside the Admin chip: the same defect, and the
   same fix, as the app card's use line (UI audit round 2, item 8). */
.app-row-sub {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  color: var(--ink-4);
}
.app-row-go { flex: none; font-size: 14px; font-weight: 600; color: var(--t-accent-ink); }

.scope-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 10px 12px;
  border-radius: 12px;
  color: var(--ink);
  transition: background 0.18s ease;
}
.scope-row:hover { background: var(--t-band); color: var(--ink); }
.scope-tick { flex: none; width: 16px; text-align: center; font-size: 13px; font-weight: 700; color: var(--t-accent-ink); }
.scope-body { flex: 1; min-width: 0; }
.scope-name { display: block; font-family: var(--font-display); font-size: 15px; font-weight: 600; color: var(--t-navy); }
.scope-sub { display: block; margin-top: 2px; font-size: 13px; color: var(--ink-3); }

.panel-links { display: flex; flex-direction: column; gap: 2px; margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--line-1); }
.panel-links > * {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 52px;
  padding: 12px 8px;
  border-radius: 12px;
  background: transparent;
  border: 0;
  text-align: start;
  font-size: 16px;
  color: var(--ink);
  transition: background 0.18s ease;
}
.panel-links > *:hover { background: var(--t-band); color: var(--t-navy); }
.panel-strong { font-weight: 600; color: var(--t-accent-ink); }
.panel-links > .panel-strong { justify-content: flex-start; }
.panel-links .dot { flex: none; }
.panel-links > button[data-menu="status"] { justify-content: flex-start; gap: 10px; }

/* The language control: one row, the current language visible in it, the
   full list in the native picker (UI audit round 2, item 1). */
.panel-block-tight { margin-top: 6px; padding-top: 0; border-top: 0; }
.menu-lang {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 8px;
  font-size: 15px;
  color: var(--ink);
}
.menu-lang select {
  min-height: 40px;
  padding: 6px 10px;
  border: 1px solid var(--line-3);
  border-radius: 10px;
  background: var(--surface);
  font: inherit;
  color: var(--t-navy);
  cursor: pointer;
}
.panel-sheet .panel-account { margin-top: 0; padding-top: 0; border-top: 0; justify-content: flex-start; }
.panel-sheet .panel-links { margin-top: 10px; padding-top: 6px; }
.panel-sheet .panel-links > * { min-height: 46px; padding: 10px 8px; }

.link-button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--t-accent-ink);
  cursor: pointer;
}
.preview-link { font-size: 14px; font-weight: 600; color: var(--t-accent-ink); }

.status-chip-mobile {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 44px;
  padding: 0 12px;
  border-radius: 999px;
  border: 1px solid var(--warn-line);
  background: var(--surface);
  font-size: 13px;
  font-weight: 600;
  min-width: 0;
  color: var(--ink-2);
  white-space: nowrap;
}
.status-chip-mobile > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.panel-account {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line-1);
}
.panel-account .account-name { font-size: 16px; }
.panel-account .account-role { font-size: 13px; color: var(--ink-3); }
.panel-account a { flex: none; font-size: 15px; font-weight: 600; }

.account-head { display: flex; align-items: center; gap: 12px; padding: 12px 12px 14px; }
.account-head .account-role { margin-top: 3px; font-size: 13px; line-height: 1.45; }

/* -------------------------------------------------------- overlays: status */

.status-headline { display: flex; align-items: baseline; gap: 9px; }
.status-headline .dot { width: 9px; height: 9px; margin-bottom: 1px; }
.status-headline h2 {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.026em;
  line-height: 1.16;
  color: var(--t-navy);
  text-wrap: balance;
}
.status-sub { margin-top: 10px; font-size: 15px; line-height: 1.6; color: var(--ink-2); text-wrap: pretty; }
.status-list { display: flex; flex-direction: column; margin-top: 20px; padding-top: 6px; border-top: 1px solid var(--line-1); }
.status-row {
  display: flex;
  align-items: center;
  gap: 13px;
  min-height: 62px;
  padding: 11px 4px;
  border-bottom: 1px solid var(--line-soft);
}
.status-row img { display: block; flex: none; width: 32px; height: 32px; }
.status-row-body { flex: 1; min-width: 0; }
.status-row-name { display: block; font-family: var(--font-display); font-size: 15px; font-weight: 600; color: var(--t-navy); }
.status-row-note { display: block; margin-top: 2px; font-size: 13px; line-height: 1.45; color: var(--ink-4); }
.status-row-state {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: 42%;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}
.status-stats { display: flex; gap: 18px; margin-top: 22px; }
.status-stats > div { flex: 1; }
.status-stats .stat-value { font-size: 22px; }
.status-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 24px; }
.status-actions .panel-hint { padding: 0 4px; }

/* --------------------------------------------------------- overlays: brand */

.brand-eyebrow {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  /* The ink, not the raw accent: the accent is a mark color and fell under
     4.5:1 as text (2.69:1 for the vendor blue on white). */
  color: var(--t-accent-ink);
}
.brand-title {
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.022em;
  line-height: 1.14;
  margin-top: 9px;
  color: var(--t-navy);
}

.callout {
  display: flex;
  gap: 12px;
  padding: 14px 16px;
  border-radius: 11px;
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  font-size: 13px;
  line-height: 1.55;
  color: var(--warn-ink);
  text-wrap: pretty;
}
.callout-mark {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--warn);
  color: var(--on-warn);
  font-size: 12px;
  font-weight: 700;
}

.tenant-row {
  display: flex;
  align-items: center;
  gap: 13px;
  min-height: 58px;
  padding: 10px 8px;
  border-radius: 12px;
  border: 1px solid transparent;
  color: var(--ink);
  transition: background 0.18s ease, border-color 0.16s ease;
}
.tenant-row:hover { background: var(--t-band); border-color: var(--t-accent-line); color: var(--ink); }
.tenant-swatch { flex: none; width: 28px; height: 28px; border-radius: 8px; border: 1px solid rgba(15, 36, 56, 0.12); }
.tenant-body { flex: 1; min-width: 0; }
.tenant-name { display: block; font-family: var(--font-display); font-size: 15px; font-weight: 600; color: var(--t-navy); }
.tenant-sub { display: block; margin-top: 2px; font-size: 13px; color: var(--ink-3); }

.logo-slot {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 116px;
  margin-top: 11px;
  padding: 10px;
  border-radius: 11px;
  border: 1px dashed var(--line-dash);
  background: var(--t-band);
  text-align: center;
}
.logo-slot.is-over { border-color: var(--t-accent); background: var(--t-accent-tint); }
.logo-slot img { max-height: 96px; width: auto; object-fit: contain; }
.logo-slot-hint { font-size: 13px; line-height: 1.5; color: var(--ink-4); }
.logo-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.logo-actions label { cursor: pointer; }

.hex-field {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  padding: 10px 14px 10px 10px;
  border-radius: 11px;
  background: var(--surface);
  border: 1px solid var(--line-4);
  box-shadow: var(--shadow-card);
}
.hex-field:focus-within { border-color: var(--t-accent); box-shadow: 0 0 0 3px var(--t-glow-16); }
.hex-swatch { flex: none; width: 34px; height: 34px; border-radius: 8px; border: 1px solid rgba(15, 36, 56, 0.14); }
.hex-field input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--t-navy);
}
.hex-state { flex: none; font-size: 12px; font-weight: 600; color: var(--ink-3); }
.hex-state.is-ok { color: var(--t-accent-ink); }

.swatch-row { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 13px; }
.swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 11px;
  border: 1px solid rgba(15, 36, 56, 0.14);
  font-size: 14px;
  font-weight: 700;
}
.swatch-dark { color: #fff; }

.preview-box {
  margin-top: 16px;
  padding: 16px 16px 18px;
  border-radius: 11px;
  border: 1px solid var(--line-2);
  background: var(--t-band);
}
.preview-row { display: flex; flex-wrap: wrap; align-items: center; gap: 11px; margin-top: 13px; }
.preview-row a { font-size: 14px; font-weight: 600; }
.preview-input {
  margin-top: 14px;
  padding: 11px 14px;
  border-radius: 9px;
  background: var(--surface);
  border: 1px solid var(--t-accent);
  box-shadow: 0 0 0 3px var(--t-glow-16);
  font-size: 13px;
  color: var(--ink-3);
}

.derived-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 12px; }
.derived-cell {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 11px;
  border-radius: 9px;
  background: var(--surface);
  border: 1px solid var(--line-2);
}
.derived-swatch { flex: none; width: 20px; height: 20px; border-radius: 5px; border: 1px solid rgba(15, 36, 56, 0.1); }
.derived-label { display: block; font-size: 12px; font-weight: 600; color: var(--t-navy); }
.derived-hex { display: block; margin-top: 1px; font-family: var(--font-display); font-size: 11px; text-transform: uppercase; color: var(--ink-4); }

.locked-brand { display: flex; flex-direction: column; gap: 2px; margin-top: 11px; }
.locked-brand li {
  display: flex;
  gap: 11px;
  padding: 8px 2px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
  text-wrap: pretty;
}
.locked-brand li::before { content: '·'; flex: none; color: var(--ink-4); }

.brand-actions { display: flex; flex-direction: column; gap: 10px; }
.brand-export { margin-top: 12px; padding: 12px; border-radius: 10px; background: var(--t-band); border: 1px solid var(--line-2); font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre; overflow-x: auto; color: var(--ink-2); }

/* --------------------------------------------------------------- pre-auth */

/* Every page a person meets before they are signed in: sign in, each /join
   phase and /not-invited. One white card on the deployment's tinted ground,
   the deployment's own mark above it, and a quiet foot below it with the
   disclosure, the attribution on a customer host, and the language choice.
   The card's contents are start-aligned (it is a form, and it is read in
   twelve scripts, two of them right-to-left); the mark and the foot are
   centered. The signed-in header, sections and footer stay out of the
   document (render() hides them), so nothing of the portal's layout shows
   through under the card as pale bands.

   The sign-in page adds a brand block. On a phone it is the mark and the
   deployment's purpose line above the card; on a wide screen it becomes its
   own panel on the brand tint, beside the card, with the eight product tiles.

   Every color here is a theme token, so a customer palette needs nothing new. */
body.is-signed-out { background: var(--t-band); }
body.is-signed-out main > section:not(.signed-out),
body.is-signed-out .workspace { display: none; }
body.is-signed-out .controls-toggle { opacity: .5; }
.signed-out { display: block; }

.auth {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: max(28px, 6vh) var(--pad-x) 20px;
}
.auth-main {
  flex: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.auth-head { display: flex; justify-content: center; margin-bottom: 22px; }

.auth-mark.mark-sentinel { height: 120px; width: auto; margin: -20px 0 -16px; }
.auth-mark.mark-custom { height: 44px; width: auto; max-width: 220px; object-fit: contain; }
.auth-mark.mark-text {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--t-navy);
}

/* The sign-in brand block. */
.auth-brand { width: 100%; max-width: 27rem; margin-bottom: 22px; }
.auth-brand-inner { display: grid; gap: 10px; justify-items: center; text-align: center; }
.auth-purpose {
  max-width: 26ch;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--t-navy);
  text-wrap: balance;
}
/* The orbit is the one thing on the page that says what is behind the door.
   Wide screens only, so on a phone the form stays above the fold. */
.auth-orbit { display: none; }

.auth-card {
  width: 100%;
  max-width: 27rem;
  padding: 28px 22px 26px;
  border-radius: 20px;
  border: 1px solid var(--line-3);
  background: var(--surface);
  box-shadow: var(--shadow-lift);
  text-align: start;
}
.auth-title { font-size: 25px; line-height: 1.2; letter-spacing: -0.015em; text-wrap: balance; }
.auth-body { margin-top: 10px; color: var(--ink-2); line-height: 1.55; text-wrap: pretty; }

/* The one colored block in a card: what happened, in the tone of the news
   (src/pre-auth.js chooses the tone). */
.auth-notice {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 18px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid;
  font-size: 14px;
  line-height: 1.5;
  text-wrap: pretty;
}
.auth-notice svg { flex: none; width: 18px; height: 18px; margin-top: 1px; }
.auth-notice p { margin: 0; }
.auth-notice-info { background: var(--t-accent-tint); border-color: var(--t-accent-line); color: var(--t-accent-ink); }
.auth-notice-warn { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-ink); }
.auth-notice-ok { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok-ink); }

/* A request in flight: a pulsing dot and the line that says what is being
   waited for. It replaces the button rather than disabling it, so the card
   never carries a control that looks pressable and is not. The pulse is the
   page's own st-pulse keyframe, so the prefers-reduced-motion rule at the top
   of this file already stops it; it is named here rather than given a
   private animation for exactly that reason. */
.auth-busy {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
}
.auth-busy::before {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--t-accent);
  animation: st-pulse 1.6s ease-in-out infinite;
}

.auth-actions,
.join-actions { display: grid; gap: 12px; margin-top: 22px; }
.auth-actions .button-primary,
.auth-actions .button-ghost,
.join-actions .button-primary,
.join-actions .button-ghost,
.auth-form .button-primary,
.auth-form .button-ghost { width: 100%; }
.auth-google { width: 18px; height: 18px; flex: none; }
/* "Back to" the app a refused sign-in started from: secondary to every button above it. */
.auth-back { margin: 18px 0 0; text-align: center; font-size: 14px; }
.auth-back a { font-weight: 600; color: var(--t-accent-ink); }
.auth-back a:hover { color: var(--t-accent-ink-hover); text-decoration: underline; }

/* The sign-in form. */
.auth-form { display: grid; margin-top: 22px; }
.auth-form .button-primary { margin-top: 14px; }
.auth-field { display: grid; gap: 6px; }
.auth-field > span { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.auth-input {
  width: 100%;
  min-height: 52px;
  padding: 12px 16px;
  border-radius: 12px;
  border: 1px solid var(--line-4);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  font-size: 16px;
  color: var(--ink);
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}
.auth-input::placeholder { color: var(--ink-4); }
.auth-input:focus { outline: none; border-color: var(--t-accent); box-shadow: 0 0 0 3px var(--t-glow-24); }

.auth-or { display: flex; align-items: center; gap: 12px; margin: 16px 0 12px; font-size: 13px; color: var(--ink-3); }
.auth-or::before,
.auth-or::after { content: ''; flex: 1; height: 1px; background: var(--line-3); }

.auth-check { margin-top: 20px; }
.auth-check-choice { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; font-size: 14px; color: var(--ink); }
.auth-check-choice input { flex: none; width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--t-accent-fill); cursor: pointer; }
/* The consequence is always on the page: a person reads what checking the box
   does before they check it, not after. The checkbox's aria-describedby points
   at it, so a screen reader hears it with the box. */
.auth-check-help { margin: 6px 0 0; padding-inline-start: 28px; font-size: 13px; line-height: 1.5; color: var(--ink-3); text-wrap: pretty; }

/* The foot under the card. */
.auth-foot {
  width: 100%;
  max-width: 27rem;
  margin-top: auto;
  padding-top: 28px;
  display: grid;
  gap: 10px;
  justify-items: center;
  text-align: center;
}
.auth-disclosure,
.auth-legal,
.auth-attribution { font-size: 13px; line-height: 1.55; color: var(--ink-3); text-wrap: pretty; }
.auth-lang { position: relative; display: inline-flex; align-items: center; gap: 6px; color: var(--ink-3); }
.auth-lang > svg { width: 16px; height: 16px; }
.auth-lang::after {
  content: '';
  position: absolute;
  inset-inline-end: 6px;
  top: calc(50% - 5px);
  width: 6px;
  height: 6px;
  /* Physical sides on purpose: a downward chevron is the same in either
     direction, and a logical side would turn it sideways under Arabic. */
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg);
  pointer-events: none;
}
.auth-lang select {
  appearance: none;
  field-sizing: content; /* as wide as the chosen language, not the longest one */
  border: 0;
  padding: 6px 4px;
  padding-inline-end: 20px;
  background: transparent;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  cursor: pointer;
}
.auth-lang select:hover { color: var(--t-navy); }

/* The join page is a form, not a notice: the eye lands on the one thing that
   matters, the address the invitation was sent to, then the two ways to
   continue, each with its consequence right under it. R216's sentences are
   all still here; each is placed where it is read. */
.join-sent-to { margin: 18px 0 6px; font-size: 13px; font-weight: 600; color: var(--ink-2); }
.join-address {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--line-4);
  background: var(--t-band);
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.join-address svg { flex: none; width: 16px; height: 16px; color: var(--ink-3); }
.join-address-help { margin: 8px 0 0; font-size: 13px; line-height: 1.5; color: var(--ink-3); }
.join-actions:has(.join-choice) { gap: 18px; }
.join-choice { display: grid; gap: 8px; }
.join-choice-help { margin: 0 4px; font-size: 13px; line-height: 1.5; color: var(--ink-3); text-wrap: pretty; }
/* A restore's security-key step: it follows whichever button is pressed, so it
   is a line of its own after both choices, in body ink rather than help-line gray. */
.join-key-step { margin: 16px 0 0; font-size: 14px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }
/* The small-type foot under the actions. `.join-already` carries the "already
   have an account" link on the start screen and Skip on the key screen;
   `.join-key-required` carries the sentence that stands in for Skip where a
   key is required. They are the same slot on the page, so they are one rule
   (fix round 1, N6) rather than two that can drift apart. */
.join-already,
.join-key-required { margin: 16px 0 0; font-size: 13px; line-height: 1.5; color: var(--ink-3); text-wrap: pretty; }
.join-already a { color: var(--t-accent-ink); font-weight: 600; }
.join-already a:hover { color: var(--t-accent-ink-hover); text-decoration: underline; text-underline-offset: 2px; }

/* A join offer: who is asking and for which position, in the same sealed
   block the claim's address uses, and the access it grants listed under it. */
.join-offer {
  display: grid;
  gap: 4px;
  margin-top: 18px;
  padding: 14px 16px;
  border-radius: 12px;
  border: 1px solid var(--line-4);
  background: var(--t-band);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}
.join-offer strong { font-family: var(--font-display); font-size: 16px; font-weight: 600; color: var(--ink); }
.join-offer-grants-label { margin-top: 10px; font-size: 13px; font-weight: 600; color: var(--ink-2); }
.join-offer-grants { display: grid; gap: 4px; }
.join-offer-grants li { display: flex; gap: 8px; color: var(--ink); }
.join-offer-grants li::before { content: ''; flex: none; width: 6px; height: 6px; margin-top: 8px; border-radius: 50%; background: var(--t-accent); }

.join-key-error { margin-top: 18px; }

/* The security key illustration on the key screen (src/join-key-art.js). The
   art runs full bleed inside its tinted panel; the caption says what it shows,
   since the SVG is aria-hidden. The gold ring pulses only while the ceremony
   is waiting for a touch (`join-key-waiting`, set from vm.keyBusy); under
   reduced motion it holds still and visible instead. */
.join-key-figure {
  margin: 6px 0 22px;
  padding: 0 0 16px;
  border-radius: 16px;
  background: var(--t-accent-tint);
  border: 1px solid var(--t-accent-line);
  text-align: center;
  overflow: hidden;
}
.join-key-art { display: block; width: 100%; height: auto; }
.join-key-figure figcaption { margin: 4px auto 0; padding: 0 16px; max-width: 34ch; font-size: 14px; line-height: 1.5; color: var(--t-navy); text-wrap: pretty; }
.join-key-art-pulse { opacity: 0; transform-box: view-box; }
.join-key-waiting .join-key-art-pulse { animation: join-key-touch 1.6s ease-out infinite; }
@keyframes join-key-touch {
  0% { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.7); }
}
@media (prefers-reduced-motion: reduce) {
  .join-key-waiting .join-key-art-pulse { animation: none; opacity: 0.9; }
}

@media (min-width: 640px) {
  .auth { padding-top: max(36px, 9vh); }
  .auth-card { padding: 36px 36px 32px; }
  .auth-title { font-size: 27px; }
}

/* Sign-in on a wide screen: the brand block becomes its own panel on the
   brand tint, and the card sits beside it on the ground. The columns follow
   the document's direction, so under Arabic the brand is on the right. */
@media (min-width: 1024px) {
  .auth-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: stretch;
    padding: 0;
  }
  .auth-split .auth-brand {
    max-width: none;
    margin: 0;
    display: flex;
    align-items: center;
    padding: clamp(40px, 6vw, 72px) var(--pad-x);
    background: var(--t-accent-tint);
  }
  .auth-split .auth-brand-inner {
    width: min(100%, 480px);
    margin-inline-start: auto;
    padding-inline-end: clamp(24px, 4vw, 56px);
    gap: 18px;
    justify-items: start;
    text-align: start;
  }
  .auth-split .auth-mark.mark-sentinel { height: 128px; margin: -24px 0 -18px; margin-inline-start: -26px; }
  .auth-split .auth-mark.mark-custom { height: 52px; max-width: 260px; }
  .auth-split .auth-purpose { max-width: 18ch; font-size: 30px; line-height: 1.2; letter-spacing: -0.02em; }

  /* The orbit (src/sign-in-screen.js, signInOrbit). Every length is a
     fraction of the orbit's own width (cqw), so it scales with the panel and
     keeps the design's proportions: rings at 0.457 and 0.85 of the width, the
     hub at 0.236. The rings turn in opposite directions; each icon turns back
     against its ring, so it stays upright. */
  .auth-orbit {
    display: block;
    position: relative;
    width: min(100%, 440px);
    aspect-ratio: 1;
    margin-top: 6px;
    container-type: inline-size;
    --r-inner: 22.86cqw;
    --r-outer: 42.5cqw;
  }
  .orbit-track,
  .orbit-pulse,
  .orbit-ring,
  .orbit-hub,
  .orbit-spoke,
  .orbit-slot { position: absolute; }
  .orbit-track {
    left: 50%;
    top: 50%;
    border: 1px dashed var(--t-accent-line);
    border-radius: 50%;
    transform: translate(-50%, -50%);
  }
  .orbit-track-inner { width: calc(2 * var(--r-inner)); height: calc(2 * var(--r-inner)); }
  .orbit-track-outer { width: calc(2 * var(--r-outer)); height: calc(2 * var(--r-outer)); }
  .orbit-pulse {
    left: 50%;
    top: 50%;
    width: 32cqw;
    height: 32cqw;
    margin: -16cqw 0 0 -16cqw;
    border: 2px solid var(--t-accent);
    border-radius: 50%;
    animation: orbit-pulse 4.6s ease-out infinite;
  }
  .orbit-ring { inset: 0; }
  .orbit-ring-inner { animation: orbit-spin 72s linear infinite; }
  .orbit-ring-outer { animation: orbit-spin 104s linear infinite reverse; }
  .orbit-ring-inner .orbit-upright { animation: orbit-spin 72s linear infinite reverse; }
  .orbit-ring-outer .orbit-upright { animation: orbit-spin 104s linear infinite; }

  .orbit-slot {
    left: 50%;
    top: 50%;
    width: var(--icon);
    height: var(--icon);
    margin: calc(var(--icon) / -2) 0 0 calc(var(--icon) / -2);
    transform: rotate(var(--a)) translateY(calc(-1 * var(--r))) rotate(calc(-1 * var(--a)));
  }
  .orbit-ring-inner { --r: var(--r-inner); --icon: 12cqw; }
  .orbit-ring-outer { --r: var(--r-outer); --icon: 9.5cqw; }
  .orbit-upright {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    filter: var(--drop-orbit);
  }
  .orbit-upright img { display: block; width: 100%; height: 100%; }
  .orbit-dim { filter: grayscale(1); opacity: 0.45; }
  .orbit-lit {
    position: absolute;
    inset: 0;
    opacity: 0;
    animation: orbit-light-inner 11s linear var(--d) infinite;
  }
  .orbit-ring-outer .orbit-lit { animation-name: orbit-light-outer; }

  /* A spoke is a zero-size point at the center, turned to face its product;
     the particle travels up it. */
  .orbit-spoke { left: 50%; top: 50%; width: 0; height: 0; transform: rotate(var(--a)); }
  .orbit-particle {
    position: absolute;
    left: -5px;
    top: -5px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--t-accent);
    box-shadow: 0 0 12px 4px color-mix(in srgb, var(--t-accent) 55%, transparent);
    opacity: 0;
    animation: orbit-shoot-inner 11s cubic-bezier(0.38, 0, 0.9, 0.32) var(--d) infinite;
  }
  .orbit-ring-outer .orbit-particle { animation-name: orbit-shoot-outer; }

  .orbit-hub {
    left: 50%;
    top: 50%;
    width: 23.6cqw;
    height: 23.6cqw;
    margin: -11.8cqw 0 0 -11.8cqw;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    border-radius: 50%;
    border: 1px solid var(--t-accent-line);
    background: var(--surface);
    box-shadow: var(--shadow-hub);
  }
  .orbit-hub-mark { width: 7.4cqw; height: auto; }
  .orbit-hub-name { font-size: 12px; font-weight: 700; letter-spacing: 0.03em; color: var(--t-navy); white-space: nowrap; }

  .auth-split .auth-main {
    justify-content: center;
    align-items: flex-start;
    padding: clamp(40px, 6vw, 72px) var(--pad-x) 24px;
    padding-inline-start: clamp(40px, 5vw, 80px);
  }
  .auth-split .auth-foot { margin-top: 0; }
}

/* ------------------------------------------------------ preview controls */

/* Deliberately physical, not logical: this is the prototype affordance that
   stands in for the account service (see index.html), not shipped product
   chrome — it stays pinned to a physical screen corner rather than mirroring
   under RTL. */
.controls { position: fixed; right: 12px; bottom: 12px; z-index: 35; }
.controls-toggle {
  padding: 9px 16px;
  border-radius: 999px;
  background: var(--t-navy);
  border: 0;
  color: var(--on-navy);
  font-size: 13px;
  font-weight: 600;
  box-shadow: var(--shadow-drop);
  opacity: 0.85;
}
.controls-toggle:hover { opacity: 1; }

.controls-panel {
  width: min(280px, calc(100vw - 24px));
  padding: 14px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--line-3);
  box-shadow: var(--shadow-drop);
}
.controls-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.controls-field { display: block; margin-top: 12px; font-size: 12px; font-weight: 600; color: var(--ink-3); }
.controls-field select {
  display: block;
  width: 100%;
  margin-top: 5px;
  padding: 9px 10px;
  border-radius: 9px;
  border: 1px solid var(--line-3);
  background: var(--surface);
  font-size: 14px;
  font-weight: 400;
  color: var(--ink);
}
.controls-check { display: flex; align-items: center; gap: 9px; margin-top: 10px; font-size: 14px; color: var(--ink-2); }
.controls-panel .button-sm { width: 100%; margin-top: 12px; }
.controls-note { margin-top: 11px; font-size: 11px; line-height: 1.5; color: var(--ink-4); }
.controls-note code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; }

/* =========================================================================
   Tablet — between the two designs

   The phone layout stretched to 900px reads as one very wide column, so this
   band keeps the phone's structure (sheets, one search field, stacked hero)
   and only widens the gutters and lets the card grids find a second column.
   ========================================================================= */

@media (min-width: 640px) and (max-width: 1023px) {
  :root { --pad-x: clamp(24px, 4vw, 44px); }

  .section-inner { max-width: 860px; }

  .hero { padding: 28px var(--pad-x) 30px; }
  .hero-title { font-size: clamp(26px, 3.6vw, 32px); }


  .brand-actions,
  .status-actions { flex-direction: row; flex-wrap: wrap; }
  .status-actions .panel-hint { flex: 1 1 100%; }

  /* Sheets stay full-height but stop stretching text across the whole tablet. */
  .panel-sheet .panel-head,
  .panel-sheet .panel-body { max-width: 720px; width: 100%; margin-inline: auto; }
  .derived-grid { grid-template-columns: repeat(3, 1fr); }
}

/* =========================================================================
   Desktop
   ========================================================================= */

@media (min-width: 1024px) {
  :root {
    --header-h: 52px;
    --pad-x: clamp(20px, 5vw, 48px);
  }

  .header-inner { gap: 6px; justify-content: flex-start; padding-inline: clamp(16px, 2vw, 24px); }
  .header-rule { margin-inline: 8px; }
  /* The lockup carries a lot of padding of its own, so it is drawn past the
     52px bar and the brand box clips it (overflow: hidden). */
  .header-mark.mark-sentinel { height: 76px; margin-inline: -18px -14px; }
  .header-mark.mark-custom { height: 32px; max-width: 200px; }
  .header-mark.mark-custom[data-shape="compact"] { height: 40px; }
  .header-mark.mark-custom[data-shape="tall"] { height: 44px; }
  /* A 60-character name kept its full 505px and pushed the bar to fit level
     4 to make room. Capped, it ellipsizes; the title has it in full. 300px
     still shows a normal long name ("Grand Nusantara Hotels & Resorts",
     263px) whole (test/long-brand-name.test.mjs). */
  .header-mark.mark-text { max-width: 300px; }

  /* ---- hero: copy left, live search right ---- */
  /* Inside the Workspace main column: no band of its own. */
  .hero { overflow: visible; padding: 0 0 14px; background: none; border: 0; }
  .hero-glow { display: none; }
  /* Deliberately physical, not logical: a decorative glow position, not
     reading-direction-dependent content — mirroring it under RTL would look
     wrong, not right. */
  .hero-glow { top: -320px; right: -140px; width: 660px; height: 660px; }
  .hero-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 16px 32px;
  }
  .hero-copy { flex: 1 1 360px; min-width: min(320px, 100%); }
  .hero-title { margin-top: 6px; font-size: clamp(20px, 2vw, 24px); line-height: 1.18; letter-spacing: -0.022em; max-width: 26em; }
  .hero-shift { margin-top: 8px; font-size: 14px; line-height: 1.5; color: var(--ink-3); max-width: 44em; }
  /* The search moved to the "Your apps" row (A3); the hero keeps only the
     phone's proxy button, which is hidden here. */
  .hero-search { display: none; }

  /* ---- footer ---- */

  /* ---- overlays become dropdowns ---- */
  .scrim { top: var(--header-h); }

  .panel {
    top: calc(var(--header-h) + 8px);
    border-radius: 16px;
    border: 1px solid var(--line-3);
    box-shadow: var(--shadow-drop);
    animation: st-rise 0.28s ease both;
  }

  .panel-drop {
    padding: 8px;
    width: min(360px, calc(100vw - 40px));
    max-height: calc(100vh - var(--header-h) - 24px);
    overflow-y: auto;
  }
  .panel-drop-left { inset-inline-start: var(--pad-x); }
  .panel-drop-right { inset-inline-end: var(--pad-x); width: min(340px, calc(100vw - 40px)); }
  .panel-drop .panel-links { margin-top: 0; padding-top: 0; border-top: 0; }
  .panel-drop .panel-links > * { min-height: 0; padding: 11px 12px; border-radius: 11px; font-size: 15px; }

  .panel-wide {
    top: var(--header-h);
    inset-inline-start: 0;
    inset-inline-end: 0;
    border-radius: 0;
    border-width: 0 0 1px;
    padding: 24px 0 26px;
  }
  .panel-wide .section-inner { padding: 0 var(--pad-x); }
  .launcher-head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; }
  .launcher-head .search-field { flex: 1 1 360px; padding: 11px 18px; }
  .launcher-head .search-hint { padding-inline-start: 0; }
  .launcher-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(304px, 1fr));
    gap: 8px;
    margin-top: 18px;
  }
  .launcher-grid .app-row { min-height: 0; padding: 11px 14px; border-radius: 11px; }
  .launcher-grid .app-row:hover { transform: translateX(4px); }
  .launcher-grid .app-row-name { font-size: 15px; }
  .launcher-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 20px;
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--line-1);
    font-size: 13px;
  }
  .launcher-locked { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

  /* Status and brand stay modal, but centred rather than full-bleed. */
  /* Centred with auto margins rather than a transform: `st-rise` animates
     `transform`, and an animation's value beats a plain declaration. */
  .panel-status,
  .panel-brand {
    inset: 0;
    margin: auto;
    width: min(560px, calc(100vw - 48px));
    height: fit-content;
    max-height: min(84vh, 900px);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: st-fade 0.2s ease both;
  }
  .panel-status .panel-body,
  .panel-brand .panel-body { padding: 20px 24px 28px; }
  .panel-brand .panel-head-tall { padding: 20px 24px 16px; }
  .panel-status .panel-head { padding: 0 24px; }
  /* A dropdown leaves the header clickable; a modal covers and dims the page. */
  .scrim-modal,
  .scrim-brand {
    top: 0;
    z-index: 55;
    background: var(--scrim);
  }

  .brand-actions { flex-direction: row; }
  .brand-actions > * { flex: 1; }
  .status-actions { flex-direction: row; flex-wrap: wrap; }
  .status-actions .button-primary,
  .status-actions .button-ghost { flex: 1 1 220px; min-height: 0; padding: 13px 22px; font-size: 15px; }
  .status-actions .panel-hint { flex: 1 1 100%; }

  .swatch-row .swatch { width: 44px; height: 44px; }
  .derived-grid { grid-template-columns: repeat(3, 1fr); }

  /* Deliberately physical, not logical: this is the prototype affordance that
     stands in for the account service (see index.html), not shipped product
     chrome — it stays pinned to a physical screen corner rather than
     mirroring under RTL. */
  .controls { right: 20px; bottom: 20px; }
}

/* The status sheet needs a scrim behind it on desktop too. */
@media (min-width: 1024px) {
  .panel-status { z-index: 60; }
}

/* ----------------------------------------------------- pending invitations */

/* An invitation waiting for a person who already has apps (src/invitation-banner.js).
   It was one line of hero-colored type and the eye skipped it. It is the one
   thing on the page the person has to act on, and it expires, so it is a white
   card lifted off the hero's band, with the organization on its own line and
   the hero's only filled button. Colors are the theme's own tokens, so a
   customer deployment gets its accent here too. */
.hero-invitations { margin-top: 16px; }
.hero-invitation-list { display: grid; gap: 10px; }

.hero-invitation {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 14px;
  align-items: start;
  padding: 16px;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--t-accent-line);
  box-shadow: var(--shadow-invite);
}

.hero-invitation-mark {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--t-accent-tint);
  color: var(--t-accent-ink);
}

.hero-invitation-label { font-size: 13px; font-weight: 600; line-height: 1.4; color: var(--t-accent-ink); }
.hero-invitation-org {
  margin-top: 2px;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.016em;
  color: var(--t-navy);
  overflow-wrap: anywhere;
  text-wrap: balance;
}
.hero-invitation-text { margin-top: 4px; font-size: 14px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }
/* --ink-2, not the hero's lighter --ink-3: this line is information the person needs, at 13px, and --ink-3 is under 4.5:1 on white. */
.hero-invitation-expiry { margin-top: 6px; font-size: 13px; font-weight: 600; line-height: 1.4; color: var(--ink-2); }

/* Phone: the button spans the card under the text, where a thumb finds it. */
.hero-invitation-action { grid-column: 1 / -1; margin-top: 14px; }

@media (min-width: 640px) {
  .hero-invitations { max-width: 44em; }
  .hero-invitation { grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; padding: 16px 18px; }
  .hero-invitation-mark { align-self: start; }
  .hero-invitation-action { grid-column: auto; margin-top: 0; white-space: nowrap; }
}

/* Skip-link targets (UI audit round 3, item 6) take focus without a ring on pointer use. */
#apps:focus:not(:focus-visible), #auth-main:focus:not(:focus-visible) { outline: none; }

/* ------------------------------------------------- appearance (dark mode)

   Light / Dark / System in the account menu (desktop dropdown and phone
   sheet), under the language row and laid out like it: label at the start,
   the control at the end. */
.menu-scheme {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 8px;
  font-size: 15px;
  color: var(--ink);
}
.menu-scheme-options {
  display: inline-flex;
  flex: none;
  padding: 3px;
  border-radius: 999px;
  border: 1px solid var(--line-3);
  background: var(--surface);
}
.menu-scheme-options button {
  min-height: 34px;
  padding: 4px 12px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  color: var(--ink-2);
}
.menu-scheme-options button:hover { color: var(--t-navy); }
.menu-scheme-options button[aria-pressed='true'] { background: var(--t-navy); color: var(--on-navy); }

/* A customer's image logo is drawn for a light page. On a dark one it sits on
   a small light patch, unless the brand file supplies its own dark logo
   (wordmark.js adds .mark-on-dark then). App icons keep full color. */
:root[data-scheme="dark"] .mark-custom:not(.mark-on-dark) {
  /* Around the logo, not inside it: as border-box padding the patch shrank
     the logo itself by 8px (a 32px square came out 24px). */
  box-sizing: content-box;
  padding: 4px 8px;
  border-radius: 8px;
  background: var(--logo-patch);
}
:root[data-scheme="dark"] .header-mark.mark-custom[data-shape="tall"]:not(.mark-on-dark) { padding: 2px 6px; }

/* Brand setup's color swatches: their navy hairline vanishes on dark. */
:root[data-scheme="dark"] .tenant-swatch,
:root[data-scheme="dark"] .hex-swatch,
:root[data-scheme="dark"] .derived-swatch,
:root[data-scheme="dark"] .swatch { border-color: rgba(255, 255, 255, 0.16); }
