/* GridStack item wrapper is transparent; the existing .panel keeps its look. */
.grid-stack { background: transparent; min-height: 0; }

/* ── 24-column positioning ── GridStack ships attribute-based positioning CSS
   only for its default 12-column grid (.gs-12 in vendor/gridstack.min.css);
   the dashboard runs 24 columns for fine-grained, near-free tile placement, so
   the matching .gs-24 rules live here (same pattern as the vendor block). */
.gs-24 > .grid-stack-item { width: 4.1667%; }
.gs-24 > .grid-stack-item[gs-x="1"] { left: 4.1667%; }
.gs-24 > .grid-stack-item[gs-w="2"] { width: 8.3333%; }
.gs-24 > .grid-stack-item[gs-x="2"] { left: 8.3333%; }
.gs-24 > .grid-stack-item[gs-w="3"] { width: 12.5%; }
.gs-24 > .grid-stack-item[gs-x="3"] { left: 12.5%; }
.gs-24 > .grid-stack-item[gs-w="4"] { width: 16.6667%; }
.gs-24 > .grid-stack-item[gs-x="4"] { left: 16.6667%; }
.gs-24 > .grid-stack-item[gs-w="5"] { width: 20.8333%; }
.gs-24 > .grid-stack-item[gs-x="5"] { left: 20.8333%; }
.gs-24 > .grid-stack-item[gs-w="6"] { width: 25%; }
.gs-24 > .grid-stack-item[gs-x="6"] { left: 25%; }
.gs-24 > .grid-stack-item[gs-w="7"] { width: 29.1667%; }
.gs-24 > .grid-stack-item[gs-x="7"] { left: 29.1667%; }
.gs-24 > .grid-stack-item[gs-w="8"] { width: 33.3333%; }
.gs-24 > .grid-stack-item[gs-x="8"] { left: 33.3333%; }
.gs-24 > .grid-stack-item[gs-w="9"] { width: 37.5%; }
.gs-24 > .grid-stack-item[gs-x="9"] { left: 37.5%; }
.gs-24 > .grid-stack-item[gs-w="10"] { width: 41.6667%; }
.gs-24 > .grid-stack-item[gs-x="10"] { left: 41.6667%; }
.gs-24 > .grid-stack-item[gs-w="11"] { width: 45.8333%; }
.gs-24 > .grid-stack-item[gs-x="11"] { left: 45.8333%; }
.gs-24 > .grid-stack-item[gs-w="12"] { width: 50%; }
.gs-24 > .grid-stack-item[gs-x="12"] { left: 50%; }
.gs-24 > .grid-stack-item[gs-w="13"] { width: 54.1667%; }
.gs-24 > .grid-stack-item[gs-x="13"] { left: 54.1667%; }
.gs-24 > .grid-stack-item[gs-w="14"] { width: 58.3333%; }
.gs-24 > .grid-stack-item[gs-x="14"] { left: 58.3333%; }
.gs-24 > .grid-stack-item[gs-w="15"] { width: 62.5%; }
.gs-24 > .grid-stack-item[gs-x="15"] { left: 62.5%; }
.gs-24 > .grid-stack-item[gs-w="16"] { width: 66.6667%; }
.gs-24 > .grid-stack-item[gs-x="16"] { left: 66.6667%; }
.gs-24 > .grid-stack-item[gs-w="17"] { width: 70.8333%; }
.gs-24 > .grid-stack-item[gs-x="17"] { left: 70.8333%; }
.gs-24 > .grid-stack-item[gs-w="18"] { width: 75%; }
.gs-24 > .grid-stack-item[gs-x="18"] { left: 75%; }
.gs-24 > .grid-stack-item[gs-w="19"] { width: 79.1667%; }
.gs-24 > .grid-stack-item[gs-x="19"] { left: 79.1667%; }
.gs-24 > .grid-stack-item[gs-w="20"] { width: 83.3333%; }
.gs-24 > .grid-stack-item[gs-x="20"] { left: 83.3333%; }
.gs-24 > .grid-stack-item[gs-w="21"] { width: 87.5%; }
.gs-24 > .grid-stack-item[gs-x="21"] { left: 87.5%; }
.gs-24 > .grid-stack-item[gs-w="22"] { width: 91.6667%; }
.gs-24 > .grid-stack-item[gs-x="22"] { left: 91.6667%; }
.gs-24 > .grid-stack-item[gs-w="23"] { width: 95.8333%; }
.gs-24 > .grid-stack-item[gs-x="23"] { left: 95.8333%; }
.gs-24 > .grid-stack-item[gs-w="24"] { width: 100%; }
.grid-stack-item-content {
  inset: 0; overflow: visible; display: flex;
  /* GridStack's default item-content adds a square drop-shadow + opacity:.8;
     the square shadow peeks out behind our rounded .panel corners. Off. */
  box-shadow: none; opacity: 1; background: transparent;
}
.grid-stack-item-content > .dashboard-widget,
.grid-stack-item-content > .panel { flex: 1 1 auto; width: 100%; height: 100%; min-width: 0; min-height: 0; }

/* ── Tile drop shadow ───────────────────────────────────────────────────────
   Painted by the WRAPPER, not by .panel. GridStack's vendor rule gives
   .grid-stack-item-content `overflow-x: hidden; overflow-y: auto` at a
   specificity a single class here cannot beat, and .panel fills that node
   exactly — so .panel's outer shadow has always been clipped away, on every
   theme. Dashboard tiles have never had a drop shadow, which is also why the
   per-tile "shadow" slider read as doing nothing.
   The wrapper is the same box and is clipped by nobody. .panel keeps its own
   box-shadow untouched (its inset highlight still paints; its outer half stays
   clipped and invisible), so every skin's highlight survives.
   --panel-drop is derived from --panel-shadow-alpha by applyTheme() in
   settings.js, and per tile inline on this wrapper by applyTileStyle(). */
.grid-stack-item { border-radius: var(--radius-tile, 16px); box-shadow: var(--panel-drop); }

/* A tile asked to be fully transparent loses the panel MATERIAL — the top-lit
   wash and the hairline highlight are part of the fill, and a "transparent"
   card that still carried them read as a faint white sheet over the wallpaper.
   The border and the shadow are separate controls and are deliberately NOT
   touched here: an outline with nothing inside it is a legitimate look. */
.grid-stack-item[data-tile-plain] > .grid-stack-item-content > :is(.panel, .dashboard-widget) {
  background-image: none;
  --panel-topline: transparent;
}

/* ── Per-tile glass (opt-in) ────────────────────────────────────────────────
   backdrop-filter is a per-frame GPU cost and the Edge composites on the
   integrated adapter, so this is NEVER on by default: applyTileStyle() sets
   data-tile-glass only when the tile actually asks for a blur. It is also
   dropped on the hybrid-GPU freeze, where every frame is already being paid
   for twice. Blur behind an opaque panel is invisible — the editor says so
   rather than quietly lowering the opacity. */
.grid-stack-item[data-tile-glass] > .grid-stack-item-content > :is(.panel, .dashboard-widget) {
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
}
body.low-power-gpu .grid-stack-item[data-tile-glass] > .grid-stack-item-content > :is(.panel, .dashboard-widget) {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Per-tile custom font (Settings → tile style). Only styled tiles get the rule,
   so an unstyled tile keeps inheriting the dashboard font. --tile-font is set
   inline on the .grid-stack-item wrapper by applyTileStyle(). */
.grid-stack-item[data-tile-style] .panel,
.grid-stack-item[data-tile-style] .dashboard-widget { font-family: var(--tile-font, inherit); }

/* Drag/resize affordances only in edit mode. */
.layout-editing .grid-stack-item-content { outline: 1px dashed rgba(var(--accent-rgb), 0.5); outline-offset: -4px; }

/* Merge target: the tile a dragged tile will be grouped INTO (creating a tab). */
.grid-stack-item.gs-merge-target > .grid-stack-item-content {
  outline: 2px solid var(--accent) !important; outline-offset: -4px;
  box-shadow: inset 0 0 0 9999px rgba(var(--accent-rgb), 0.16);
  border-radius: var(--radius);
}

/* ── Edit overlay ── in edit mode the WHOLE tile is the drag surface: a
   transparent overlay covers the content so its inner controls (tabs, inputs,
   scrolling lists) can't swallow the gesture. The real GridStack drag handle is
   the content node itself (stable across re-renders — see mountPageGrid); the
   overlay is the hit-surface plus a grip pill as the visual affordance. All
   tile edit controls (size / +Tab / save / × / .layout-controls, z 58-62) and
   the tab bars (below) sit above it. */
.gs-edit-overlay { display: none; }
.layout-editing .gs-edit-overlay {
  display: block; position: absolute; inset: 0; z-index: 40;
  cursor: grab; touch-action: none; border-radius: var(--radius);
}
.layout-editing .gs-edit-overlay:active { cursor: grabbing; }
.gs-edit-grip {
  position: absolute; left: 50%; top: 6px; transform: translateX(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 16px; border-radius: 999px; pointer-events: none;
  background: rgba(var(--accent-rgb), 0.18); color: var(--accent);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}
.gs-edit-grip svg { width: 12px; height: 12px; transform: rotate(90deg); opacity: 0.9; }
/* Tiles whose top strip is a tab bar (tab-groups, System, Agenda) hide the
   pill: the bar is translucent and sits above the overlay (z 45 > 40), so the
   pill would bleed through it half-covered. The dashed outline + grab cursor
   remain the edit cue there — the whole tile still drags. */
.layout-editing .grid-stack-item-content:has(.tabgroup-bar, .system-tabs, .cal-task-toggle) .gs-edit-grip { display: none; }

/* Tab bars stay interactive above the overlay while editing (switch the active
   member, per-tab ×-remove); their buttons never start a drag (GridStack
   ignores mousedown on buttons/inputs). */
.layout-editing .tabgroup-bar,
.layout-editing .cal-task-toggle,
.layout-editing .system-tabs { position: relative; z-index: 45; }

/* Custom-widget setup controls stay clickable while editing. A freshly added
   Custom tile lands in edit mode showing its package picker, so the drag overlay
   (z 40) would otherwise swallow every "Add" / state-button tap and the widget
   could never be assigned without first leaving edit mode. Lift just the buttons
   above the overlay (GridStack ignores mousedown on buttons, so the tile still
   drags from any non-button part of the picker). The permission dialog itself is
   a body-level fixed backdrop, already above everything. */
.layout-editing .cw-body .cw-btn,
.layout-editing .cw-swap-btn { position: relative; z-index: 45; }

/* Media/Chat panels normally isolate their blending (`isolation: isolate` for
   the z-index:-1 album-ambient backdrop). That makes the panel one atomic
   stacking unit, which would trap its OWN edit controls (.layout-controls
   z 60, .cal-task-toggle z 45) BELOW the sibling drag overlay (z 40) — taps on
   hide/move-page would start a drag instead. Lift the isolation while editing;
   the only cost is the ambient backdrop hiding behind the panel background for
   the duration of the edit session. */
.layout-editing .media-panel,
.layout-editing .chat-panel { isolation: auto; }

/* Live drag feedback: lift the dragged tile, and mark the drop slot with a
   crisp accent placeholder so it's always obvious where the tile will land. */
.grid-stack-item.ui-draggable-dragging > .grid-stack-item-content {
  opacity: 0.92;
  filter: drop-shadow(0 18px 34px rgba(0, 0, 0, 0.5));
}
.grid-stack-placeholder > .placeholder-content {
  background: rgba(var(--accent-rgb), 0.08);
  border: 2px dashed rgba(var(--accent-rgb), 0.55);
  border-radius: var(--radius);
}

/* ── Resize, two ways ──
   1) Drag the bottom-right CORNER handle (precise).
   2) Tap the size button just above it (cycles preset sizes; reliable on touch). */
.grid-stack > .grid-stack-item > .ui-resizable-handle { display: none; }
.layout-editing .grid-stack > .grid-stack-item > .ui-resizable-se {
  display: block; width: 22px; height: 22px; right: 4px; bottom: 4px; z-index: 62;
  border: 1px solid rgba(var(--accent-rgb), 0.55); border-radius: 7px;
  background: rgba(6, 10, 10, 0.9); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  transform: none; touch-action: none;
}
.layout-editing .grid-stack > .grid-stack-item > .ui-resizable-se::after {
  content: ""; position: absolute; right: 5px; bottom: 5px; width: 7px; height: 7px;
  border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
}

.gs-size-cycle { display: none; }
.layout-editing .gs-size-cycle {
  display: grid; place-items: center; position: absolute; right: 5px; bottom: 32px;
  width: 26px; height: 26px; z-index: 61; padding: 0; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 7px;
  background: rgba(6, 10, 10, 0.82); color: var(--accent);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  transition: background 0.15s ease, transform 0.1s ease;
}
.layout-editing .gs-size-cycle:hover { background: rgba(var(--accent-rgb), 0.2); }
.layout-editing .gs-size-cycle:active { transform: scale(0.92); }
.gs-size-cycle svg { width: 14px; height: 14px; }

/* Delete (×) on a COPY tile — top-right, always red (touch has no hover). */
.gs-remove { display: none; }
.layout-editing .gs-remove {
  display: grid; place-items: center; position: absolute; right: 6px; top: 6px;
  width: 26px; height: 26px; z-index: 61; padding: 0; cursor: pointer; line-height: 1;
  border: 1px solid rgba(231, 76, 60, 0.5); border-radius: 7px;
  background: rgba(231, 76, 60, 0.18); color: var(--red);
  font-size: 18px; font-weight: 700;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  transition: background 0.15s ease, transform 0.1s ease;
}
.layout-editing .gs-remove:hover { background: rgba(231, 76, 60, 0.3); }
.layout-editing .gs-remove:active { transform: scale(0.92); }

/* ── "+ Tab" — bottom-centre pill: add another component to THIS tile as a tab. */
.gs-add-tab { display: none; }
.layout-editing .gs-add-tab {
  display: inline-flex; align-items: center; gap: 4px; position: absolute;
  left: 50%; bottom: 6px; transform: translateX(-50%); z-index: 61;
  padding: 4px 10px 4px 7px; cursor: pointer;
  border: 1px solid rgba(var(--accent-rgb), 0.5); border-radius: 999px;
  background: rgba(var(--accent-rgb), 0.14); color: var(--accent);
  font: inherit; font-size: 11px; font-weight: 850; letter-spacing: 0.3px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.38);
  transition: background 0.15s ease, transform 0.1s ease;
}
.layout-editing .gs-add-tab:hover { background: rgba(var(--accent-rgb), 0.26); }
.layout-editing .gs-add-tab:active { transform: translateX(-50%) scale(0.95); }
.gs-add-tab svg { width: 13px; height: 13px; }

/* "Save preset" — top-left, clear of the size / remove handles. */
.gs-save-preset { display: none; }
.layout-editing .gs-save-preset {
  display: grid; place-items: center; position: absolute; left: 6px; top: 6px;
  width: 26px; height: 26px; z-index: 61; padding: 0; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 7px;
  background: rgba(6, 10, 10, 0.82); color: var(--accent);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  transition: background 0.15s ease, transform 0.1s ease;
}
.layout-editing .gs-save-preset:hover { background: rgba(var(--accent-rgb), 0.2); }
.layout-editing .gs-save-preset:active { transform: scale(0.92); }
.gs-save-preset svg { width: 14px; height: 14px; }

/* "Tile style" — top-left, just below Save preset. */
.gs-tile-style { display: none; }
.layout-editing .gs-tile-style {
  display: grid; place-items: center; position: absolute; left: 6px; top: 38px;
  width: 26px; height: 26px; z-index: 61; padding: 0; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 7px;
  background: rgba(6, 10, 10, 0.82); color: var(--accent);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  transition: background 0.15s ease, transform 0.1s ease;
}
.layout-editing .gs-tile-style:hover { background: rgba(var(--accent-rgb), 0.2); }
.layout-editing .gs-tile-style:active { transform: scale(0.92); }
.gs-tile-style svg { width: 15px; height: 15px; }

/* ── Per-tile style editor popover ── */
/* A side panel, NOT a blurred cover — so the tile updates live behind it and you
   can see the style apply to the real widget while you edit it. Docked to the
   right; only a faint dim (no blur) keeps the dashboard readable and sharp. */
.tile-style-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: flex-end; padding: 20px;
  background: rgba(2, 5, 6, 0.18);
}
.tile-style-pop {
  width: min(340px, calc(100vw - 32px)); max-height: calc(100vh - 40px); overflow: auto;
  display: flex; flex-direction: column; gap: 12px; padding: 16px;
  border: 1px solid var(--panel-border); border-radius: 16px;
  background: rgba(10, 13, 14, 0.96); color: var(--text);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
}
.tile-style-head { font-size: 15px; font-weight: 800; letter-spacing: 0.2px; }
/* Draggable header — move the panel off the widget to watch it update live. */
.tile-style-head.tile-style-drag { cursor: move; user-select: none; touch-action: none; display: flex; align-items: center; gap: 8px; }
.tile-style-head.tile-style-drag::before {
  content: '⠿'; font-size: 15px; line-height: 1; opacity: 0.5; letter-spacing: -2px;
}
.tile-style-seg {
  display: grid; grid-template-columns: 1fr 1fr; gap: 4px; padding: 3px;
  border-radius: 11px; background: rgba(255, 255, 255, 0.05);
}
.tile-style-seg button {
  padding: 8px 6px; cursor: pointer; border: 0; border-radius: 8px;
  background: transparent; color: var(--text); font: inherit; font-weight: 700; font-size: 12.5px;
  opacity: 0.7; transition: background 0.15s ease, opacity 0.15s ease;
}
.tile-style-seg button.active { background: rgba(var(--accent-rgb), 0.22); color: var(--accent); opacity: 1; }
.tile-style-body[hidden] { display: none; }
.tile-style-body { display: flex; flex-direction: column; gap: 10px; }
.tile-style-row { display: flex; align-items: center; gap: 10px; }
.tile-style-row input[type="checkbox"] { width: 17px; height: 17px; accent-color: var(--accent); flex: 0 0 auto; }
.tile-style-label { flex: 1 1 auto; font-size: 13px; }
.tile-style-row input[type="color"] {
  width: 40px; height: 28px; padding: 0; border: 1px solid var(--panel-border);
  border-radius: 7px; background: transparent; cursor: pointer; flex: 0 0 auto;
}
.tile-style-row input[type="color"]:disabled { opacity: 0.4; cursor: default; }
.tile-style-row input[type="range"] { flex: 1 1 auto; accent-color: var(--accent); }
/* Gradient control: two swatches that read as a pair, with the angle below. */
.tile-style-gradblock { display: flex; flex-direction: column; gap: 7px; }
.tile-style-gradblock .tile-style-row { gap: 8px; }
.tile-style-grad-sw {
  width: 30px; height: 26px; padding: 0; border: 1px solid var(--panel-border);
  border-radius: 7px; background: transparent; cursor: pointer; flex: 0 0 auto;
}
.tile-style-grad-sw:disabled { opacity: 0.4; cursor: default; }
.tile-style-grad-angle { width: 100%; accent-color: var(--accent); }
.tile-style-grad-angle:disabled { opacity: 0.4; }
.tile-style-font {
  flex: 0 0 auto; min-width: 150px; padding: 6px 8px; font: inherit; font-size: 12.5px;
  border: 1px solid var(--panel-border); border-radius: 8px;
  background: rgba(255, 255, 255, 0.06); color: var(--text); cursor: pointer;
}
/* The font/fit pickers are upgraded to the shared custom dropdown when the panel
   opens (custom-select.js hides the native <select> and inserts a .cs-wrap before
   it), so the sizing above has to carry over to the wrapper. */
.tile-style-row > .cs-wrap { flex: 0 0 auto; min-width: 150px; }
.tile-style-row > .cs-wrap .cs-trigger { width: 100%; font-size: 12.5px; }
.tile-style-foot { display: flex; justify-content: space-between; gap: 8px; margin-top: 2px; }
.tile-style-btn {
  padding: 8px 16px; cursor: pointer; border: 1px solid var(--panel-border);
  border-radius: 9px; background: rgba(255, 255, 255, 0.05); color: var(--text);
  font: inherit; font-weight: 700; font-size: 12.5px;
  transition: background 0.15s ease, transform 0.1s ease;
}
.tile-style-btn:active { transform: scale(0.96); }
.tile-style-btn.primary { border-color: transparent; background: var(--accent); color: var(--on-accent); }

/* ── Tabbed tile-style editor (Colours / Background / Decor / Effects) ── */
.tile-style-pop.wide { width: min(420px, calc(100vw - 32px)); }
.tile-style-tabs {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; padding: 3px;
  border-radius: 11px; background: rgba(255, 255, 255, 0.05);
}
.tile-style-tabs button {
  padding: 8px 4px; cursor: pointer; border: 0; border-radius: 8px;
  background: transparent; color: var(--text); font: inherit; font-weight: 700; font-size: 12px;
  opacity: 0.7; transition: background 0.15s ease, opacity 0.15s ease;
}
.tile-style-tabs button.active { background: rgba(var(--accent-rgb), 0.22); color: var(--accent); opacity: 1; }
.tile-style-body[hidden] { display: none; }
.tile-style-subhead { font-size: 12px; font-weight: 800; letter-spacing: 0.4px; text-transform: uppercase; opacity: 0.7; margin-top: 4px; }
/* A one-line explanation under a control, for the cases where a setting is
   inert for a reason the user can fix (blur behind an opaque panel). */
.tile-style-hint { font-size: 11.5px; line-height: 1.45; opacity: 0.72; }
.tile-style-hint[hidden] { display: none; }
.tile-style-hint-err { color: var(--color-danger); opacity: 1; }
.tile-style-row-col { flex-direction: column; align-items: stretch; gap: 6px; }
/* Shape picker: the silhouette itself is the thumbnail, drawn from the same
   path the tile will be clipped with — no asset to ship or keep in step. */
.tile-shape-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
}
.tile-shape-chip {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 7px 4px 5px; cursor: pointer; border: 1px solid var(--panel-border);
  border-radius: 9px; background: rgba(255, 255, 255, 0.04); color: var(--text);
  font: inherit; font-size: 10px; font-weight: 700;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.tile-shape-chip svg { width: 100%; height: 34px; opacity: 0.85; }
.tile-shape-chip span { overflow: hidden; max-width: 100%; text-overflow: ellipsis; white-space: nowrap; }
.tile-shape-chip:hover { background: rgba(255, 255, 255, 0.08); }
.tile-shape-chip.active { border-color: var(--accent); background: rgba(var(--accent-rgb), 0.16); color: var(--accent); }
.tile-shape-path {
  width: 100%; padding: 8px 10px; border: 1px solid var(--panel-border);
  border-radius: 8px; background: var(--input-bg); color: var(--text);
  font: inherit; font-size: 11.5px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.decor-btnrow { display: flex; gap: 8px; }
.decor-btnrow .tile-style-btn { flex: 1 1 auto; }
.decor-preview {
  width: 100%; aspect-ratio: 16 / 8; border-radius: 10px;
  border: 1px solid var(--panel-border); background: rgba(255, 255, 255, 0.04) center/cover no-repeat;
}
.decor-preview.empty { background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.06) 0 8px, transparent 8px 16px); }
.decor-thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.decor-thumb {
  width: 46px; height: 46px; padding: 0; cursor: pointer; flex: 0 0 auto;
  border: 1px solid var(--panel-border); border-radius: 9px;
  background: rgba(255, 255, 255, 0.05) center/78% no-repeat;
  color: var(--text); font-size: 20px; line-height: 1;
  transition: border-color 0.15s ease, transform 0.1s ease;
}
.decor-thumb.upload { background-size: cover; display: grid; place-items: center; }
.decor-thumb.none { background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.12) 0 6px, transparent 6px 12px); }
.decor-thumb:hover { border-color: rgba(var(--accent-rgb), 0.6); }
.decor-thumb.active { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(var(--accent-rgb), 0.4); }
.decor-thumb:active { transform: scale(0.94); }
.decor-overlay-list { display: flex; flex-direction: column; gap: 10px; }
.decor-overlay-item {
  display: grid; grid-template-columns: 52px 1fr auto; gap: 10px; align-items: start;
  padding: 10px; border: 1px solid var(--panel-border); border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
}
.decor-overlay-thumb {
  width: 52px; height: 52px; border-radius: 9px;
  border: 1px solid var(--panel-border); background: rgba(255, 255, 255, 0.05) center/78% no-repeat;
}
.decor-overlay-ctrls { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
/* Free-placement pad: drag the marker to set the overlay's x/y (tile percent). */
.decor-pospad {
  position: relative; width: 100%; aspect-ratio: 16 / 10; min-height: 56px;
  border: 1px solid var(--panel-border); border-radius: 8px; cursor: crosshair;
  background:
    linear-gradient(rgba(var(--accent-rgb), 0.05), rgba(var(--accent-rgb), 0.05)),
    repeating-linear-gradient(0deg, transparent 0 24%, rgba(255, 255, 255, 0.06) 25%),
    repeating-linear-gradient(90deg, transparent 0 24%, rgba(255, 255, 255, 0.06) 25%);
  touch-action: none;
}
.decor-pospad-dot {
  position: absolute; width: 22px; height: 22px; transform: translate(-50%, -50%);
  border-radius: 6px; border: 2px solid var(--accent); background: center/contain no-repeat;
  background-color: rgba(0, 0, 0, 0.35); box-shadow: 0 0 0 2px rgba(var(--accent-rgb), 0.35); pointer-events: none;
}
.decor-overlay-del {
  align-self: start; width: 26px; height: 26px; padding: 0; cursor: pointer;
  border: 1px solid var(--panel-border); border-radius: 7px; background: transparent;
  color: var(--red, #ff6b6b); font-size: 17px; font-weight: 700; line-height: 1;
}
.decor-overlay-del:hover { background: rgba(255, 80, 80, 0.15); }
.decor-add { display: flex; flex-direction: column; gap: 8px; }

/* ── Per-tile DECOR (images + effects) ──────────────────────────────────────
   Layers built by buildTileDecor() inside .grid-stack-item-content:
     .tile-decor-bg       picture behind the widget (z 0)
     widget content       raised to z 1 while decor is present
     .tile-decor-frame    ornamental border art, non-interactive (z 4)
     .tile-decor-overlays decorative corner images, non-interactive (z 5)      */
.grid-stack-item[data-tile-decor] > .grid-stack-item-content {
  padding: var(--tile-frame-inset, 0);
  border-radius: var(--radius-tile, 16px);
}
/* Raise the real widget content above the background/glow layers (z 0). The
   exclusion list is load-bearing: the edit-mode affordances (drag overlay,
   size/remove/add-tab/save/style buttons) are absolutely positioned by their own
   rules — if this rule forced them `position: relative` they would rejoin the
   content flex row and squash the widget while editing. Excluding them (and the
   decor layers) keeps them absolute. */
.grid-stack-item[data-tile-decor] > .grid-stack-item-content
  > :not(.tile-decor-bg):not(.tile-decor-frame):not(.tile-decor-overlays):not(.tile-shape-outline):not(.gs-edit-overlay):not(.gs-size-cycle):not(.gs-add-tab):not(.gs-save-preset):not(.gs-tile-style):not(.gs-remove) {
  position: relative; z-index: 1;
}
.tile-decor-bg {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border-radius: var(--radius-tile, 16px); overflow: hidden;
}
.tile-decor-bg::after {
  content: ''; position: absolute; inset: 0; background: #000;
  opacity: var(--tile-bg-dim, 0);
}
.tile-decor-frame {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  border-style: solid; border-color: transparent;
  border-image-slice: 33%;
  border-image-repeat: stretch;
}
.tile-decor-overlays {
  position: absolute; inset: 0; z-index: 5; pointer-events: none;
  border-radius: var(--radius-tile, 16px); overflow: hidden;
}
.tile-decor-overlay {
  position: absolute; height: auto; max-width: 100%;
  object-fit: contain; user-select: none;
}

/* ── Per-tile SHAPE (the silhouette) ────────────────────────────────────────
   The clip goes on the PAINTED layers, never on .grid-stack-item-content: the
   edit-mode buttons live in that node, in the corners — exactly what a shape
   cuts away — so clipping there would put them out of reach on every shaped
   tile. --tile-clip points at a <clipPath clipPathUnits="objectBoundingBox">
   in the shared #tile-shape-defs sprite; both are written by applyTileStyle(). */
.grid-stack-item[data-tile-shape] > .grid-stack-item-content
  > :is(.panel, .dashboard-widget, .tile-decor-bg, .tile-decor-overlays) {
  clip-path: var(--tile-clip);
  border-radius: 0;
}
/* Keep content clear of the cut edges. This is a transparent BORDER rather than
   padding on purpose: padding here would beat (and erase) the padding every
   widget root declares for itself, while the border is the one .panel already
   owns — and on a shaped tile the rectangular border is replaced by the stroked
   outline below anyway. --tile-shape-pad is a four-value width. */
.grid-stack-item[data-tile-shape] > .grid-stack-item-content > :is(.panel, .dashboard-widget) {
  border-style: solid;
  border-color: transparent;
  border-width: var(--tile-shape-pad, 0px);
}
/* A box-shadow follows the border box, not the clip: on a shaped tile it would
   draw a rectangle floating behind the silhouette. */
.grid-stack-item[data-tile-shape] { box-shadow: none; }
/* The ornamental frame is a 9-slice border, rectangular by construction. */
.grid-stack-item[data-tile-shape] > .grid-stack-item-content > .tile-decor-frame { display: none; }
/* The outline — the same path, stroked. z 6, above the decorative overlays, so
   the edge of the tile is never covered by a corner image. */
.tile-shape-outline {
  position: absolute; inset: 0; z-index: 6; pointer-events: none;
  width: 100%; height: 100%; overflow: visible;
}
.tile-shape-outline path {
  fill: none;
  stroke: var(--panel-border);
  stroke-width: var(--tile-shape-stroke, 1);
  vector-effect: non-scaling-stroke;
}

/* "My presets" dock chips: an insert chip with a small inline delete. */
.layout-preset-row { position: relative; display: inline-flex; }
.layout-preset-chip { padding-right: 30px; }
.layout-preset-del {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; display: grid; place-items: center; padding: 0;
  border: none; border-radius: 6px; background: transparent;
  color: var(--red); font-size: 15px; font-weight: 700; line-height: 1; cursor: pointer;
}
.layout-preset-del:hover { background: rgba(231, 76, 60, 0.18); }

/* "+" add affordance: a Corsair-style drop-zone over the page's free space.
   Its position/size are set in JS (refreshPageAddAffordances) to cover the
   largest empty grid rectangle; the rules here are the look + the fallbacks. */
.page-add-widget { display: none; }
.layout-editing .page-add-widget {
  display: grid; place-items: center; position: absolute; right: 16px;
  /* fallback (JS overrides): float in the bottom-right corner */
  bottom: 16px;
  width: 44px; height: 44px; border-radius: 14px; z-index: 40;
  border: 1.5px dashed rgba(var(--accent-rgb), 0.5); background: rgba(var(--accent-rgb), 0.07);
  color: var(--accent); font-size: 38px; font-weight: 300; line-height: 1; cursor: pointer;
  box-sizing: border-box;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.layout-editing .page-add-widget:hover { background: rgba(var(--accent-rgb), 0.16); border-color: rgba(var(--accent-rgb), 0.75); }
/* When the page grid has no items, blow the "+" up into a centred placeholder. */
.layout-editing .pager-page:not(:has(.grid-stack-item)) .page-add-widget {
  position: absolute; inset: 0; margin: auto; width: min(420px, 60%); height: 180px; font-size: 48px;
}

/* Palette — a centered modal (backdrop + card) listing the addable widgets,
   categorised and icon'd. A dialog, not an anchored popover, so it can never be
   clipped by the layout dock or minimal-mode chrome and always reads tidy. */
.widget-palette-overlay {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center; padding: 18px;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  animation: widget-palette-fade 0.16s ease;
}
@keyframes widget-palette-fade { from { opacity: 0; } to { opacity: 1; } }
.widget-palette-modal {
  width: min(760px, 94vw); max-height: min(86vh, 660px);
  display: flex; flex-direction: column; gap: 12px; padding: 16px 18px 18px;
  border-radius: 18px; border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.1));
  /* Opaque surface (like ColorPicker / preset-modal): actionable content must stay
     readable regardless of the user's panel-transparency setting. */
  background: rgb(var(--panel-rgb, 14, 17, 22));
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6);
  animation: widget-palette-pop 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes widget-palette-pop { from { opacity: 0; transform: translateY(8px) scale(0.985); } to { opacity: 1; transform: none; } }
.widget-palette-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.widget-palette-title { margin: 0; font-size: 1.02rem; font-weight: 800; color: var(--text, #e7e9ee); }
.widget-palette-close {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  color: var(--text-dim, #8b91a1); padding: 7px; border-radius: 9px;
  display: inline-flex; transition: background 0.14s ease, color 0.14s ease;
}
.widget-palette-close svg { width: 18px; height: 18px; }
.widget-palette-close:hover { background: rgba(255, 255, 255, 0.08); color: var(--text, #e7e9ee); }
.widget-palette-body {
  min-height: 0; overflow-y: auto; scrollbar-width: thin;
  /* Reserve the scrollbar's width even when it's absent, so a category that only
     needs the bar at some sizes can't shift the column flow sideways as the bar
     appears/disappears (which reflowed "Custom widget" between columns). */
  scrollbar-gutter: stable;
  display: flex; flex-direction: column; gap: 3px;
}
.widget-palette-cat {
  font-size: 0.62rem; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-dim, #8b91a1); padding: 9px 4px 5px;
}
.widget-palette-cat:first-child { padding-top: 2px; }
.widget-palette-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }

/* Mega-menu layout (normal "+" add): each category is a self-contained COLUMN laid
   side by side, so the whole list fits on the wide-but-short Xeneon Edge without a
   tall scroll. Columns are independent — a category's heading and its items never
   split across columns, so nothing is ever orphaned — and they top-align, so the
   layout reads as clean parallel lists. On a narrow viewport whole columns wrap.
   Sizing/scroll is owned by the modal card; here we only set the column flow. */
.widget-palette-body.widget-palette--cols {
  flex-direction: row; flex-wrap: wrap; align-items: flex-start;
  gap: 4px 14px;
}
.widget-palette-section {
  display: flex; flex-direction: column; min-width: 0;
  flex: 1 1 168px; max-width: 240px;
}
.widget-palette--cols .widget-palette-cat { padding-top: 4px; }
.widget-palette--cols .widget-palette-grid { grid-template-columns: 1fr; gap: 6px; }
.widget-palette-item {
  display: flex; align-items: center; gap: 9px; min-width: 0; text-align: left;
  padding: 9px 11px; border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.04);
  color: inherit; font: inherit; font-weight: 700; font-size: 12.5px; cursor: pointer;
  transition: background 0.14s ease, border-color 0.14s ease, transform 0.06s ease;
}
.widget-palette-item:hover { background: rgba(var(--accent-rgb), 0.18); border-color: rgba(var(--accent-rgb), 0.42); }
.widget-palette-item:active { transform: translateY(1px); }
.widget-palette-ico { display: inline-flex; flex: 0 0 auto; color: rgb(var(--accent-rgb)); }
.widget-palette-ico svg { width: 18px; height: 18px; }
.widget-palette-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.widget-palette-empty { opacity: 0.7; padding: 12px; text-align: center; }

/* ── Tab-group tile (generic) ── */
.tabgroup { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; }
/* Matches the Agenda/System tab bars (.cal-task-toggle / .cal-task-btn). */
.tabgroup-bar {
  display: flex; gap: 2px; margin: 8px 8px 4px; padding: 3px; flex: 0 0 auto;
  overflow-x: auto; scrollbar-width: none;
  background: rgba(255, 255, 255, 0.035); border: 1px solid rgba(255, 255, 255, 0.055);
  border-radius: 22px;
}
.tabgroup-bar::-webkit-scrollbar { height: 0; }
.tabgroup-tab {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  font: inherit; font-size: 10.5px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase;
  border: none; border-radius: 18px; padding: 6px 10px; cursor: pointer;
  background: transparent; color: var(--dim-text); white-space: nowrap;
  transition: background 0.22s ease, color 0.22s ease, box-shadow 0.22s ease;
}
.tabgroup-tab.active {
  background: rgba(var(--accent-rgb), 0.14); color: var(--accent);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
/* Edit mode: a tab can be dragged sideways to reorder the group's tabs.
   touch-action: none is what makes that work on the touchscreen — without it the
   bar's own horizontal scroll claims the gesture and cancels the pointer. */
.layout-editing .tabgroup-tab { cursor: grab; touch-action: none; }
.layout-editing .tabgroup-tab.is-dragging {
  cursor: grabbing; opacity: 0.92; position: relative; z-index: 2;
  background: rgba(var(--accent-rgb), 0.2); color: var(--accent);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
/* No colour/position transition while reordering: the tabs are being moved in
   the DOM on every pointermove, so an animated background would smear. */
.tabgroup-bar.is-reordering .tabgroup-tab { transition: none; }
/* Tab glyph reused from the widget palette (svg carries no width/height). */
.tabgroup-tab-ico { display: inline-flex; flex: 0 0 auto; }
.tabgroup-tab-ico svg { width: 15px; height: 15px; }
/* Per-tab remove (×) — shown only in edit mode, always red (touch has no hover),
   with a comfortable hit area so a normal tab tap doesn't catch it. */
.tabgroup-remove { display: none; }
.layout-editing .tabgroup-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-left: 6px; flex-shrink: 0;
  border-radius: 6px; font-size: 15px; line-height: 1; font-weight: 700;
  color: var(--red); background: rgba(231, 76, 60, 0.16);
  border: 1px solid rgba(231, 76, 60, 0.4);
}
.layout-editing .tabgroup-remove:hover { background: rgba(231, 76, 60, 0.3); }
.tabgroup-body { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; }
.tabgroup-body > .dashboard-widget[data-dashboard-hidden="true"] { display: none; }
.tabgroup-body > .dashboard-widget { flex: 1 1 auto; width: 100%; height: 100%; min-height: 0; overflow: hidden; }
/* The relocated AI chat pane must fill the chat tab and let the log scroll.
   Both the primary pane (#media-pane-chat) and copies ([data-chatf="pane"],
   which have the ID stripped) are targeted so copies get the same layout. */
.tabgroup-body > .chat-panel,
.tabgroup-body > .chat-panel > #media-pane-chat,
.tabgroup-body > .chat-panel > [data-chatf="pane"] { display: flex; flex-direction: column; min-height: 0; }
