/* GarudaPortal — the suite launcher.
 *
 * This is workspace chrome, not a landing page. The visual budget goes to
 * legibility and scanning speed: a quiet surface, one accent per app, and no
 * decoration that competes with the seventeen icons the page exists to present.
 *
 * Every colour, radius and duration is a named token. A card sets `--a` once
 * from the catalogue accent and its plate, glow, ring and badge are all derived
 * with `color-mix()`, so adding an app costs one line of data and zero CSS.
 *
 * LIGHT ONLY, on purpose (the whole suite is — see garuda-drive/web/style.css).
 * Every brand mark in this product is a navy eagle drawn for a white page; on a
 * dark surface the marks lose their outline and the suite stops looking like
 * one product. There is no theme toggle, no `data-theme` branch and no
 * `prefers-color-scheme` block — `color-scheme: light` also stops the browser
 * from darkening form controls and scrollbars underneath us.
 */

:root {
  color-scheme: light;

  /* --- neutral surfaces ---
     Cool, slightly blue-shifted greys rather than pure #fff/#eee: the marks are
     navy, and a neutral-warm grey next to them reads as "unfinished template". */
  --bg: #f2f5fa;
  --surface: #ffffff;
  --surface-2: #eef1f8;
  --surface-3: #e4e9f3;
  /* The rail is a light surface, not a navy slab. Every brand mark is a blue
     eagle, and a blue mark on deep navy is the one contrast failure the accent
     system cannot rescue — the icons simply disappeared. On a light rail they
     read at 26px, and the shell stops being two unrelated halves. */
  --rail: rgb(255 255 255 / .82);
  --line: #dde3ee;
  --line-soft: #e8ecf5;

  /* --- ink --- */
  --ink: #101b36;
  --ink-2: #4b5878;
  --ink-3: #7e8aa6;

  /* --- brand ---
     Sampled from the suite mark itself (#082b65 navy, #0056fd blue, #2bb48e
     jade), so the chrome and the logos are literally the same palette. */
  --navy: #0a2a63;
  --navy-2: #123a86;
  --brand: #1e46b8;
  --brand-2: #0056fd;
  --jade: #17916c;
  --focus: #2b6ce4;

  /* --- status: one colour per meaning, never per app ---
     "Ready" says the same thing on every card, so it must look the same on
     every card. Tinting it with the app accent turned one status into fifteen
     and made a row of four ready apps read as four different states. */
  --ok: #0d6f4b;
  --ok-bg: #e3f3ea;
  --ok-line: #bfe3d2;
  --warn: #8a5a06;
  --warn-bg: #fdf1dc;

  /* --- geometry --- */
  --rail-w: 72px;
  --shell-pad: 34px;
  --radius: 16px;
  --radius-sm: 11px;
  --card-h: 78px;

  /* --- motion: one duration pair, one curve, so the page moves as one --- */
  --fast: 120ms;
  --base: 180ms;
  --ease: cubic-bezier(.2, .7, .3, 1);

  /* Layered, low-alpha shadows: one tight contact shadow plus one soft ambient
     one. A single big blurry shadow is what makes flat UI look cheap — real
     depth is two shadows doing two different jobs. */
  --shadow-xs: 0 1px 2px rgb(12 24 56 / .05);
  --shadow-sm: 0 1px 2px rgb(12 24 56 / .05), 0 1px 1px rgb(12 24 56 / .03);
  --shadow-lift: 0 1px 2px rgb(12 24 56 / .06), 0 10px 22px -8px rgb(12 24 56 / .16);
  --shadow-pop: 0 2px 6px rgb(12 24 56 / .08), 0 28px 56px -16px rgb(12 24 56 / .28);
}

* { box-sizing: border-box; }

/* `hidden` is how search filters this page, and the browser's own
   `[hidden] { display: none }` lives in the UA stylesheet — which loses to any
   author rule, however unspecific. Without this line `.app { display: flex }`
   and `.clear { display: grid }` quietly win and the "hidden" element stays on
   screen. Restore the attribute's meaning once, here, for every element. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-feature-settings: "cv05" 1, "ss01" 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* --- the canvas itself ------------------------------------------------------
   Four wide, very low-alpha radial washes in the brand's own three colours,
   painted once on a fixed layer behind everything. It is the difference
   between "a flat grey page" and "a surface with light falling on it", and it
   costs one composited layer that never repaints: no animation, no blur
   filter, nothing that touches scrolling. The alphas are deliberately in the
   4–9% range — at 15% this stops being lighting and becomes wallpaper. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(58rem 34rem at 6% -6%,   rgb(0 86 253 / .16), transparent 62%),
    radial-gradient(46rem 30rem at 100% 2%,  rgb(43 180 142 / .16), transparent 60%),
    radial-gradient(54rem 40rem at 82% 104%, rgb(30 70 184 / .13), transparent 62%),
    radial-gradient(40rem 28rem at 18% 94%,  rgb(43 180 142 / .10), transparent 60%);
}
/* A single hairline of brighter light along the very top, so the sticky bar
   has something to sit against instead of floating on flat colour. */
body::after {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;
  height: 240px;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg, rgb(255 255 255 / .72), rgb(255 255 255 / 0));
}

/* Visible only to assistive technology. */
.sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* One focus treatment for the whole page, on the keyboard path only. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ============================== RAIL ============================== */

.rail {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--rail-w);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 0;
  /* Frosted rather than opaque: the aurora behind it stays faintly visible, so
     the rail reads as a pane of glass over one surface instead of a second,
     unrelated white rectangle glued to the edge. */
  background: var(--rail);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  /* A hairline, not a shadow: the rail and the canvas are now both light, so
     they need a boundary, and a shadow here would be the only soft edge on an
     otherwise crisp page. */
  border-inline-end: 1px solid var(--line-soft);
  z-index: 10;
}

/* Sixteen labelled items are taller than a laptop viewport, so the rail
   scrolls. `overscroll-behavior` keeps that scroll from continuing into the
   page once the rail hits its end — a rail that drags the whole launcher up
   with it feels broken. */
.rail {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.rail::-webkit-scrollbar { width: 6px; }
.rail::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 3px;
}

/* A fade at the foot of the rail, so a clipped item reads as "there is more
   below" instead of as the end of the list. It is needed because macOS and
   iOS use overlay scrollbars — the bar only exists while you are already
   scrolling, which is no help to someone deciding whether to scroll at all.
   On a 700px-tall window the rail cuts off mid-item and the last three apps
   are otherwise invisible and unguessable.

   `position: fixed`, matching the rail itself, so it stays pinned to the foot
   of the column instead of scrolling away with the content it is describing.
   Harmless when nothing overflows: it then sits over the rail's own bottom
   padding, where there is nothing to fade. */
.rail::after {
  content: "";
  position: fixed;
  bottom: 0;
  inset-inline-start: 0;
  width: var(--rail-w);
  height: 28px;
  background: linear-gradient(to top, rgb(255 255 255 / .92), rgb(255 255 255 / 0));
  pointer-events: none;
}

/* One shape for every rail entry, Home included: icon over word. Home used to
   be a bare 46px mark and the apps were bare 46px marks below a divider, which
   made the first item look like a logo rather than the nav item it has always
   been. */
.rail-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  width: 60px;
  padding: 6px 2px 5px;
  border-radius: var(--radius-sm);
  color: var(--ink-2, #4a5a78);
  text-decoration: none;
  transition: background var(--fast) var(--ease), transform var(--fast) var(--ease);
}

.rail-ico {
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
}
.rail-item img {
  width: 26px; height: 26px;
  /* The marks are drawn flat; a whisper of contact shadow stops them looking
     like stickers pasted on the glass. */
  filter: drop-shadow(0 1px 1px rgb(12 24 56 / .14));
}

/* 10.5px is small, and deliberately so: it has to clear "Calendar" inside 60px
   without hyphenating, and it is a label under a picture — the icon is doing
   the recognising, the word only has to confirm it. */
.rail-label {
  max-width: 100%;
  font-size: 10.5px;
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-align: center;
  /* One line, clipped rather than wrapped: a two-line label would make its
     item taller than its neighbours and leave the rail visibly ragged. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rail-home {
  margin-bottom: 10px;
}
.rail-home:hover { background: var(--surface-2); }

/* A hairline divider under Home: it separates "where you are" from "where
   you can go" without spending a heavier line on it. */
.rail-home::after {
  content: "";
  position: absolute;
  bottom: -6px;
  left: 50%;
  translate: -50% 0;
  width: 26px; height: 1px;
  background: var(--line);
}

.rail-apps {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rail-app:hover {
  /* 26% was tuned against a navy rail; on a white one the same mix lands as a
     loud pastel block. */
  background: color-mix(in srgb, var(--a) 11%, transparent);
  transform: translateY(-1px);
}

/* The accent reads as an edge marker rather than a fill, so sixteen
   near-identical eagle marks stay distinguishable without turning the rail
   into a paint chart. Now that a word sits under each one it is confirmation
   rather than the only clue, but it still marks WHICH item the pointer is on
   at a glance down the column. */
.rail-app::before {
  content: "";
  position: absolute;
  left: -6px;
  top: 50%;
  translate: 0 -50%;
  width: 3px; height: 0;
  border-radius: 0 3px 3px 0;
  background: var(--a);
  transition: height var(--base) var(--ease);
}
.rail-app:hover::before,
.rail-app:focus-visible::before { height: 26px; }

/* The label picks up the app's own colour on hover — the icon is already
   tinted, and leaving the word grey made the two look unrelated. */
.rail-app:hover .rail-label { color: color-mix(in srgb, var(--a) 72%, var(--ink-1, #1c2b4a)); }

/* ---- the "+" that opens the picker --------------------------------------
   Same shape as an app item so the rail stays one column of identical slots,
   but drawn as an outline rather than a mark: it is the empty next slot, which
   is exactly what it does. */
.rail-add {
  background: none;
  border: 0;
  font: inherit;
  cursor: pointer;
  color: var(--ink-2, #4a5a78);
  margin-top: 2px;
}
.rail-add .rail-ico {
  border: 1px dashed var(--line);
  border-radius: 8px;
  color: var(--ink-2, #4a5a78);
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.rail-add .rail-ico svg { width: 16px; height: 16px; }
.rail-add:hover { background: var(--surface-2); }
.rail-add:hover .rail-ico,
.rail-add[aria-expanded="true"] .rail-ico {
  border-color: var(--brand, #2447c9);
  border-style: solid;
  color: var(--brand, #2447c9);
  background: color-mix(in srgb, var(--brand, #2447c9) 8%, transparent);
}

/* ---- unpin, on the item itself ------------------------------------------
   Hover/focus only: a permanent row of crosses would turn a launcher into an
   edit screen. Kept off the tab order (the picker is the keyboard path to the
   same action) so Tab still walks apps, not app-and-a-delete-button. */
.rail-unpin {
  position: absolute;
  top: 1px;
  inset-inline-end: 1px;
  display: grid;
  place-items: center;
  width: 16px; height: 16px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--surface-2, #eef1f7);
  color: var(--ink-2, #4a5a78);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--fast) var(--ease), background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.rail-unpin svg { width: 8px; height: 8px; }
.rail-app:hover .rail-unpin,
.rail-app:focus-within .rail-unpin { opacity: 1; }
.rail-unpin:hover {
  background: #e0322c;
  color: #fff;
}
/* Coarse pointers have no hover to reveal it with, and a 16px target is below
   the touch floor anyway — there, the picker is the only way to unpin. */
html[data-touch="1"] .rail-unpin { display: none; }

.rail-app.dragging { opacity: .45; }

.rail-empty {
  margin: 4px 6px;
  font-size: 10px;
  line-height: 1.3;
  text-align: center;
  color: var(--ink-3, #7c8aa6);
}

/* ============================= PICKER ============================= */

.picker {
  position: fixed;
  z-index: 40;
  width: 250px;
  max-height: min(70vh, 520px);
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 18px 44px rgb(12 24 56 / .18), 0 2px 8px rgb(12 24 56 / .08);
  overflow: hidden;
}

.picker-head {
  padding: 12px 14px 8px;
  border-bottom: 1px solid var(--line-soft);
}
.picker-head h2 {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-1, #1c2b4a);
}
.picker-head p {
  margin: 2px 0 0;
  font-size: 11px;
  color: var(--ink-3, #7c8aa6);
}

.picker-list {
  overflow-y: auto;
  padding: 6px;
  overscroll-behavior: contain;
}

.pick {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 8px;
  border: 0;
  border-radius: 9px;
  background: none;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-1, #1c2b4a);
  text-align: start;
  cursor: pointer;
}
.pick:hover { background: color-mix(in srgb, var(--a) 10%, transparent); }
.pick:focus-visible { outline: 2px solid var(--brand, #2447c9); outline-offset: -2px; }
.pick[disabled] { opacity: .42; cursor: not-allowed; }
.pick[disabled]:hover { background: none; }
.pick-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The tick is always in the layout, only its ink comes and goes — a mark that
   appears from nowhere shifts every name in the list by its width. */
.pick-mark {
  display: grid;
  place-items: center;
  width: 16px; height: 16px;
  border-radius: 5px;
  border: 1.5px solid var(--line);
  color: transparent;
}
.pick-mark svg { width: 10px; height: 10px; }
.pick[aria-checked="true"] .pick-mark {
  border-color: var(--a);
  background: var(--a);
  color: #fff;
}

/* Stacked, not a tip-and-button row: "Seret item rail untuk mengurutkan" is
   half again as long as the English, and side by side it wrapped to three
   squeezed lines beside the button. A column costs one line of height and is
   correct in every language (see the ribbon-legibility rule — QA re-fitting a
   layout for one locale is not the same as the layout fitting). */
.picker-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 8px 12px 10px;
  border-top: 1px solid var(--line-soft);
}
.picker-tip {
  margin: 0;
  font-size: 10.5px;
  color: var(--ink-3, #7c8aa6);
}
.picker-reset {
  margin-inline-start: -6px; /* optically flush with the tip above it */
  border: 0;
  background: none;
  padding: 4px 6px;
  border-radius: 7px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--brand, #2447c9);
  cursor: pointer;
}
.picker-reset:hover { background: color-mix(in srgb, var(--brand, #2447c9) 10%, transparent); }

/* ============================= SHELL ============================== */

.shell {
  margin-inline-start: var(--rail-w);
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

/* ============================ TOP BAR ============================= */

.top {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 14px var(--shell-pad);
  background: rgb(242 245 250 / .72);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  backdrop-filter: blur(18px) saturate(150%);
  border-bottom: 1px solid var(--line-soft);
}

/* --- title lockup --- */
.titles { flex: 0 0 auto; }
.titles h1 {
  margin: 0;
  font-size: 17.5px;
  font-weight: 680;
  letter-spacing: -.022em;
  line-height: 1.15;
  /* A very short vertical gradient on the wordmark only. Flat #101b36 is
     correct but inert; this is two shades of the same navy and reads as
     weight, not as a coloured heading. */
  background: linear-gradient(180deg, #16264a, #0a2a63);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.titles p {
  margin: 2px 0 0;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: -.005em;
}

/* --- search: the widest thing in the bar, because it is the fast path --- */
.search {
  position: relative;
  flex: 1 1 auto;
  max-width: 520px;
  margin-inline: auto;
}

.search .mag {
  position: absolute;
  inset-inline-start: 13px;
  top: 50%;
  translate: 0 -50%;
  width: 17px; height: 17px;
  color: var(--ink-3);
  pointer-events: none;
}

.search input {
  width: 100%;
  height: 42px;
  padding-inline: 40px 68px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgb(255 255 255 / .9);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  box-shadow: var(--shadow-xs);
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease), background var(--fast) var(--ease);
}
.search input::placeholder { color: var(--ink-3); }
.search input:hover { background: #fff; }
.search input:focus {
  outline: none;
  background: #fff;
  border-color: color-mix(in srgb, var(--focus) 55%, var(--line));
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--focus) 14%, transparent), var(--shadow-xs);
}
/* The browser's own clear affordance would sit on top of ours. */
.search input::-webkit-search-cancel-button { display: none; }

.slash {
  position: absolute;
  inset-inline-end: 12px;
  top: 50%;
  translate: 0 -50%;
  min-width: 20px;
  padding: 2px 6px;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 6px;
  background: var(--surface-2);
  color: var(--ink-3);
  font: 500 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  text-align: center;
  pointer-events: none;
  transition: opacity var(--fast) var(--ease);
}
.search input:focus ~ .slash,
.search input:not(:placeholder-shown) ~ .slash { opacity: 0; }

.clear {
  position: absolute;
  inset-inline-end: 8px;
  top: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border: 0;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink-2);
  cursor: pointer;
}
.clear svg { width: 14px; height: 14px; }
.clear:hover { background: var(--line); color: var(--ink); }

/* --- tools --- */
.tools {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

.lang select {
  height: 36px;
  padding-inline: 11px 28px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: rgb(255 255 255 / .9) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%237e8aa6' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 9px center / 12px;
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  appearance: none;
  cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition: background-color var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.lang select:hover { border-color: var(--line); background-color: #fff; color: var(--ink); }

.icon-btn {
  display: grid;
  place-items: center;
  width: 36px; height: 36px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: rgb(255 255 255 / .9);
  color: var(--ink-2);
  cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.icon-btn:hover { background: #fff; color: var(--ink); }
.icon-btn svg { width: 17px; height: 17px; }

/* ========================== ACCOUNT CHIP ==========================
   Who you are signed in as, and the way out. It is a real chip — avatar,
   name, caret — not a bare "Log out" button: on a suite with one shared
   sign-in, "which account am I in right now" is the single most common
   question the chrome has to answer, and a page that never shows the
   address you are signed in with cannot answer it. The email lives one
   click away in the menu, where it can be read at full length instead of
   being truncated into the bar. */

.acct-wrap { position: relative; }

.acct {
  display: flex;
  align-items: center;
  gap: 9px;
  height: 38px;
  max-width: 210px;
  padding: 0 11px 0 5px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgb(255 255 255 / .9);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.acct:hover { background: #fff; box-shadow: var(--shadow-sm); }
.acct[aria-expanded="true"] {
  background: #fff;
  border-color: color-mix(in srgb, var(--brand) 34%, var(--line));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 10%, transparent);
}

/* The avatar carries the only saturated colour in the top bar, so it is where
   the eye lands — which is right, because it is the one control that says
   something about *you* rather than about the catalogue. */
.avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: linear-gradient(150deg, var(--av-1, #1e46b8), var(--av-2, #0a2a63));
  color: #fff;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .01em;
  text-transform: uppercase;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .28), 0 1px 2px rgb(12 24 56 / .22);
  user-select: none;
}
.avatar.lg { width: 46px; height: 46px; font-size: 17px; border-radius: 14px; }
/* With a real photo the gradient and the inset highlight would show through
   at the edges, so the plate becomes a frame instead. */
.avatar.has-photo { background: var(--surface-2); box-shadow: inset 0 0 0 1px rgb(12 24 56 / .1); overflow: hidden; }
.avatar.has-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

.acct-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
  letter-spacing: -.005em;
}
.acct-caret {
  flex: 0 0 auto;
  width: 13px; height: 13px;
  color: var(--ink-3);
  transition: transform var(--fast) var(--ease);
}
.acct[aria-expanded="true"] .acct-caret { transform: rotate(180deg); }

/* The signed-out chip: same pill, an outline icon standing in for the
   avatar's filled circle so the two states don't get mistaken for each
   other at a glance. */
.acct-signin-ic {
  flex: 0 0 auto;
  width: 17px; height: 17px;
  margin-inline-start: 3px;
  color: var(--ink-3);
}

/* --- the dropdown --- */
.acct-menu {
  position: absolute;
  inset-block-start: calc(100% + 9px);
  inset-inline-end: 0;
  z-index: 40;
  width: 302px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgb(255 255 255 / .96);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  backdrop-filter: blur(22px) saturate(160%);
  box-shadow: var(--shadow-pop);
  animation: acct-in var(--base) var(--ease);
}
@keyframes acct-in {
  from { opacity: 0; transform: translateY(-6px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

.acct-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 12px 13px;
  border-radius: 12px;
  background: linear-gradient(160deg, color-mix(in srgb, var(--brand) 7%, #fff), var(--surface-2));
}
/* The three identity lines are <span>s (they live inside a <button>, where a
   <div> is invalid), so each one has to be told to be a block — left inline
   they ran the name and the address together into one string. */
.acct-id { min-width: 0; display: block; }
.acct-fullname {
  display: block;
  font-size: 14.5px;
  font-weight: 650;
  letter-spacing: -.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The address is the identifying fact, so it gets to wrap rather than be
   truncated — a half-shown email answers nothing. */
.acct-email {
  display: block;
  margin-top: 2px;
  font-size: 12.5px;
  color: var(--ink-2);
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.acct-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px 12px 4px;
}
.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .01em;
}
.tag.ok {
  border-color: var(--ok-line);
  background: var(--ok-bg);
  color: var(--ok);
}
/* "Not verified" reuses the same amber as the "Not running" badge on an app
   card, and means the same thing: nothing is broken, a step simply has not
   happened yet. Red would read as an error the person caused. */
.tag.warn {
  border-color: color-mix(in srgb, var(--warn) 26%, transparent);
  background: var(--warn-bg);
  color: var(--warn);
}
.tag .dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
}

/* --- the phone row --------------------------------------------------------
   Sits between the identity block and the actions, so "which number is on
   this account, and has anyone proved it" is answered in the same glance as
   "which account is this". */
.acct-phone {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 2px;
  font-size: 12.5px;
  color: var(--ink-2);
}
.acct-phone svg {
  flex: 0 0 auto;
  width: 15px; height: 15px;
  color: var(--ink-3);
}
.acct-phone-num {
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  /* The number yields, the badge does not. A long number was wrapping the
     badge onto a second line, which made the row twice as tall and put the
     status below the thing it describes. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acct-phone .tag { flex: 0 0 auto; white-space: nowrap; }

/* Shared button, used by the settings page rows (account-page.css) and by
   anything in the menu that needs one. */
.acct-btn {
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.acct-btn:hover { background: var(--surface-2); }
.acct-btn.primary {
  border-color: transparent;
  background: var(--brand);
  color: #fff;
}
.acct-btn.primary:hover { filter: brightness(1.06); background: var(--brand); }
.acct-btn.danger { color: #b42318; }
.acct-btn.danger:hover { background: #fdecec; }

.acct-sep {
  height: 1px;
  margin: 8px 4px;
  background: var(--line-soft);
}

.acct-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.acct-item:hover { background: var(--surface-2); }
.acct-item svg {
  flex: 0 0 auto;
  width: 17px; height: 17px;
  color: var(--ink-3);
}
.acct-item.danger { color: #b42318; }
.acct-item.danger svg { color: currentColor; }
.acct-item.danger:hover { background: #fdecec; }

/* ============================= MAIN =============================== */

.main {
  flex: 1 0 auto;
  padding: 26px var(--shell-pad) 44px;
}

.cat + .cat { margin-top: 32px; }

.cat-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 13px;
}
.cat-head h2 {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.cat-head p {
  margin: 0;
  font-size: 13px;
  color: var(--ink-3);
}
/* A hairline that runs to the end of the band header, so each category reads as
   its own section without a heavy rule across the page. */
.cat-head::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, var(--line), transparent);
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(262px, 1fr));
  gap: 11px;
}

/* ---------------------------- APP CARD ---------------------------- */

.app {
  position: relative;
  display: flex;
  align-items: center;
  gap: 13px;
  min-height: var(--card-h);
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  transition:
    transform var(--base) var(--ease),
    border-color var(--base) var(--ease),
    box-shadow var(--base) var(--ease);
}

/* A one-pixel highlight along the top edge — the thing that separates a card
   with a light source from a rectangle with a border. */
.app::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / .9), transparent);
  pointer-events: none;
}

a.app:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--a) 45%, var(--line));
  box-shadow: var(--shadow-lift);
}
a.app:active { transform: translateY(0); }

/* The accent wash appears only on hover, so the resting grid stays neutral and
   the icons — not seventeen coloured rectangles — are what you scan. */
a.app::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(120deg, color-mix(in srgb, var(--a) 9%, transparent), transparent 62%);
  opacity: 0;
  transition: opacity var(--base) var(--ease);
  pointer-events: none;
}
a.app:hover::after { opacity: 1; }

.tile {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  border-radius: 13px;
  /* Neutral at rest, on purpose. Every brand mark here is the same blue eagle,
     so tinting the plate green/orange/red put a warm field directly behind a
     cool logo — seventeen small colour collisions, and a grid that reads as
     noise rather than as one surface. The accent is not gone, it is deferred:
     it arrives on hover, where only one card carries it at a time. */
  background: linear-gradient(160deg, #ffffff, var(--surface-2));
  border: 1px solid var(--line-soft);
  box-shadow: inset 0 1px 0 #fff, var(--shadow-xs);
  transition: background var(--base) var(--ease), border-color var(--base) var(--ease);
}
a.app:hover .tile {
  background: linear-gradient(160deg, #fff, color-mix(in srgb, var(--a) 16%, var(--surface-2)));
  border-color: color-mix(in srgb, var(--a) 28%, var(--line-soft));
}
.tile img {
  width: 30px; height: 30px;
  filter: drop-shadow(0 1px 1.5px rgb(12 24 56 / .16));
}

.meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.namerow {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.name {
  flex: 0 1 auto;
  font-size: 14.5px;
  font-weight: 620;
  letter-spacing: -.012em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.desc {
  font-size: 12.5px;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.badge {
  /* Yields before the name does. A long status ("Not running") next to a long
     name used to truncate the NAME — "GarudaMa…" — which is the one word on
     the card that has to survive: the badge is a passing state, the name is
     what the card IS. `flex-shrink: 3` makes the badge give up its space
     first, and the cap keeps it from ever taking half the row. */
  flex: 0 3 auto;
  min-width: 0;
  max-width: 46%;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 1.5px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .02em;
  white-space: nowrap;
}
.badge.live {
  background: var(--ok-bg);
  border-color: var(--ok-line);
  color: var(--ok);
}
/* Same rule as `live`: one colour per meaning, never per app. "Not running"
   is a warning about this machine, not an error in the product, so it is amber
   rather than red — nothing is broken, a server is simply not started. */
.badge.offline {
  background: var(--warn-bg);
  border-color: color-mix(in srgb, var(--warn) 22%, transparent);
  color: var(--warn);
}
/* The transient state reads as "no answer yet", so it borrows the neutral
   surface rather than claiming a status it has not established. */
.badge.checking {
  background: var(--surface-2);
  border-color: var(--line-soft);
  color: var(--ink-3);
}
.badge.here {
  background: var(--surface-2);
  border-color: var(--line-soft);
  color: var(--ink-2);
}
.badge.soon {
  background: var(--surface-2);
  border-color: var(--line-soft);
  color: var(--ink-3);
}

/* An app with no server yet is visibly inert: dimmed, flat, and not a link at
   all in the markup, so it is neither clickable nor reachable by Tab. */
.app.soon {
  cursor: default;
  background: color-mix(in srgb, var(--surface) 62%, var(--bg));
  box-shadow: none;
}
/* De-emphasis is applied to the mark and the surface, never to the text. A
   blanket `opacity: .58` on the card also faded the name and description, and
   --ink-3 at 58% over this background falls below readable contrast — the
   apps you cannot open yet became the apps you cannot read. */
.app.soon .tile { opacity: .5; box-shadow: none; }
.app.soon .name { color: var(--ink-2); }

/* ============================= EMPTY ============================== */

.empty {
  padding: 56px 0;
  text-align: center;
}
.empty .e1 {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
}
.empty .e2 {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-3);
}

/* ============================= FOOTER ============================= */

.foot {
  flex: 0 0 auto;
  padding: 18px var(--shell-pad) 28px;
  border-top: 1px solid var(--line-soft);
  color: var(--ink-3);
  font-size: 12.5px;
}

/* The /tentang link sits in the same quiet grey as the rest of the footer
   line — a resting place for crawlers and the curious, not a call to action. */
.foot-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.foot-link:hover { color: var(--ink-1, #1c2b4a); }

/* ============================ RESPONSIVE ========================== */

@media (max-width: 900px) {
  :root { --shell-pad: 20px; }

  /* The rail becomes a horizontal strip: on a phone a fixed side column costs
     more width than the shortcut is worth. */
  .rail {
    position: sticky;
    inset: 0 0 auto 0;
    width: auto;
    flex-direction: row;
    align-items: center;
    gap: 4px;
    padding: 8px 12px;
    overflow-x: auto;
    border-inline-end: 0;
    border-block-end: 1px solid var(--line-soft);
  }
  /* Sideways, the rail scrolls on X instead of Y. The bottom fade goes with
     it: pinned to the viewport foot it would sit over the page content, a
     long way from the strip it was describing. */
  .rail { overflow-x: auto; overflow-y: hidden; }
  .rail::after { display: none; }

  /* Items keep their icon-over-word shape here too — it is the same rail, and
     a horizontal strip of bare icons would put the guessing straight back. */
  .rail-item { width: 58px; flex: 0 0 auto; }
  .rail-home { margin: 0 6px 0 0; }
  .rail-home::after { display: none; }
  .rail-apps { flex-direction: row; gap: 2px; }
  .rail-add { margin-top: 0; }
  .rail-app::before {
    left: 50%;
    top: auto;
    translate: -50% 0;
    bottom: -6px;
    width: 0; height: 3px;
    border-radius: 3px 3px 0 0;
    transition: width var(--base) var(--ease);
  }
  .rail-app:hover::before,
  .rail-app:focus-visible::before { width: 22px; height: 3px; }

  .shell { margin-inline-start: 0; }

  /* Stack the bar so the search field keeps a usable width. */
  .top {
    flex-wrap: wrap;
    gap: 12px;
    padding-block: 12px;
  }
  .search {
    order: 3;
    flex: 1 0 100%;
    max-width: none;
    margin-inline: 0;
  }
  .tools { margin-inline-start: auto; }
  /* relocated: decoration — the "/" keyboard hint, for a keyboard a touch screen lacks */
  .slash { display: none; }
}

@media (max-width: 560px) {
  .grid { grid-template-columns: 1fr; }
  .cat-head { flex-direction: column; gap: 2px; align-items: flex-start; }
  .cat-head::after { display: none; }
  /* The subtitle wraps under the title rather than disappearing. */
  .titles p { white-space: normal; }
  /* relocated: the name is redundant next to the avatar once space is tight,
     but the avatar itself never goes — it is the only "who am I" signal on
     screen */
  .acct-name { display: none; }
  .acct { max-width: none; padding-inline: 5px 8px; }
  .acct-menu { width: min(302px, calc(100vw - 32px)); }
}

/* Respect a stated preference for less movement: keep the state changes, drop
   the travel. */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  a.app:hover { transform: none; }
}

/* ---- a finger (platform/adaptive) ---- */
html[data-touch="1"] .icon-btn, html[data-touch="1"] .acct { min-width: 44px; min-height: 44px; }
html[data-touch="1"] .rail-item { min-height: 44px; }
html[data-touch="1"] .pick { min-height: 44px; }

/* Drag-to-reorder is HTML5 drag and drop, which coarse pointers do not fire —
   so the tip that advertises it is hidden there rather than promising a
   gesture that does nothing. The checklist above still reorders by removing
   and re-adding, which is the touch path. */
html[data-touch="1"] .picker-tip { display: none; }

/* ---------------------------------------------------------- recovery marker
   The reminder that an account has no verified recovery address. There is no
   banner on the launcher: someone who came to open an app cannot finish this
   there, so the reminder lives on the path to the setting instead — a dot on
   the chip and a line in the menu, with the wording and the fix in the
   account panel (see account-page.css .row-attention). */

.acct-alert {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  margin-inline-start: -2px;
  border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 2px var(--surface);
}

.acct-item-note {
  flex: 0 0 auto;
  margin-inline-start: auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--warn-bg);
  color: #8a5a06;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
/* The item's own label keeps the line; the badge is what yields. Without
   this the two together wrapped "Manage your account" onto a second row. */
.acct-item > span:not(.acct-item-note) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
