/* ============================================================
   LAYOUT.CSS — Sidebar Navigation, Messenger Panes, and Pages
   ============================================================ */

/* --- App Shell (Full Screen Wrapper) --- */
.app-layout {
  display: flex;
  flex-direction: row;
  height: 100vh;
  width: 100vw;
  overflow: hidden;
  background: var(--bg-body);
  /* Dark background shows through the gaps */
}

/* ===== FIX: The SPA Wrapper needs to expand to fill the screen ===== */
#spa-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
  min-width: 0;
  /* Crucial: Prevents flex children from overflowing horizontally */
}

/* ============================================================
   1. COLLAPSIBLE SIDEBAR (Instagram/Messenger Style)
   ============================================================ */
.app-sidebar {
  width: 72px;
  min-width: 72px;
  background: var(--bg-nav-solid);
  border-right: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  z-index: 100;
  overflow: hidden;
}

/* Suppress transition on initial page load to prevent layout jump */
.app-sidebar.no-transition {
  transition: none !important;
}

/* Expand when toggle class is applied */
.app-sidebar.expanded {
  width: 260px;
  box-shadow: var(--shadow-lg);
}

.sidebar-top {
  padding: 1rem 0.5rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  display: flex;
  align-items: center;
  height: 72px;
  flex-shrink: 0;
}

.sidebar-header-wrapper {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  overflow: hidden;
}

/* Toggle button is now strictly sized and positioned */
.sidebar-toggle-btn {
  background: transparent;
  border: none;
  color: var(--text-tertiary);
  cursor: pointer;
  min-width: 40px;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
  margin-left: 8px;
  /* Centers the 40px button in the 56px padded area */
}

.sidebar-toggle-btn:hover {
  background: rgba(148, 163, 184, 0.15);
  color: var(--text-primary);
}

.sidebar-toggle-btn svg,
.sidebar-toggle-btn i {
  min-width: 20px;
}

/* Brand Link hidden when collapsed to prevent overlap */
#brand-link {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  white-space: nowrap;
}

.app-sidebar.expanded #brand-link {
  opacity: 1;
  pointer-events: auto;
}

#brand-link span {
  background: linear-gradient(135deg, #e2e8f0, #a5b4fc);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  font-size: 1.1rem;
  font-weight: 800;
}

.sidebar-menu {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  /* Fixes internal scrollwheels */
  padding: 1rem 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.sidebar-bottom {
  display: flex;
  flex-direction: column;
  padding: 0;
}

/* Sidebar Links */
/* An <a> without href is not a link as far as the browser is concerned, so it
   loses the pointer it used to get for free. The href went so hovering the nav
   stops printing the route in the browser's status bar. */
.sidebar-link,
#brand-link {
  cursor: pointer;
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 16px;
  height: 48px;
  color: #94a3b8;
  text-decoration: none;
  border-radius: var(--radius-md);
  white-space: nowrap;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 0.95rem;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  cursor: pointer;
  background: transparent;
  border: none;
  text-align: left;
  width: 100%;
  overflow: hidden;
  position: relative;
}

/* FIX: Ensure SVGs don't get squished to 0px */
.sidebar-link svg,
.sidebar-link i {
  min-width: 24px;
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.sidebar-link:hover {
  background: rgba(148, 163, 184, 0.15);
  color: #e2e8f0;
  transform: translateX(3px);
}

.sidebar-link:hover svg,
.sidebar-link:hover i {
  transform: scale(1.12);
}

.sidebar-link:active {
  transform: translateX(1px) scale(0.98);
}

/* ── Selection, from the design system (SidebarLink) ───────────
   The rule there is that selection is brought FORWARD and everything else set
   back, rather than the selected row simply being tinted. So the resting state
   gains a slight scale-down and an inset shadow, and the active row gets a
   lift, a warmer text colour and a two-part wash.

   Converted from the kit's React: it tracked hover and press in component
   state and applied a class for ~340ms after selection landed. None of that
   needs JavaScript here -- :hover, :active and an animation on .active reach
   the same place. */
.sidebar-link {
  transform: scale(0.985);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: background 0.28s var(--ease-standard),
              color 0.28s var(--ease-standard),
              box-shadow 0.28s var(--ease-standard),
              transform 0.28s var(--ease-spring);
}

.sidebar-link:hover {
  background: rgba(148, 163, 184, 0.13);
  color: #cfe0f5;
  box-shadow: none;
  transform: translateX(3px) scale(1);
}

.sidebar-link.active {
  background:
    radial-gradient(circle at left center, rgba(34, 211, 238, 0.16) 0%, transparent 78%),
    linear-gradient(100deg, rgba(34, 211, 238, 0.10) 0%, rgba(99, 102, 241, 0.06) 45%, transparent 100%);
  color: #f2f9ff;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4),
              0 0 0 1px rgba(34, 211, 238, 0.22),
              inset 0 1px 0 rgba(255, 255, 255, 0.07);
  /* selectPop settles at translateX(4px), so the resting value matches its end
     and the row does not jump when the animation hands back. */
  transform: translateX(4px) scale(1);
  animation: selectPop 0.32s var(--ease-spring) both;
}

.sidebar-link.active svg,
.sidebar-link.active i { transform: scale(1.08); }

/* The rail: a gradient that travels along its own length while the glow
   breathes. background-size 100% 200% is what gives railFlow room to move. */
.sidebar-link.active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 14%;
  bottom: 14%;
  width: 3px;
  border-radius: 0 4px 4px 0;
  background: linear-gradient(180deg, #67e8f9 0%, #22d3ee 25%, #0891b2 50%, #22d3ee 75%, #67e8f9 100%);
  background-size: 100% 200%;
  animation: railFlow 2.4s linear infinite, railBreathe 2.4s ease-in-out infinite;
}

.sidebar-link.active .sidebar-label {
  animation: labelPop 0.36s var(--ease-expo-out) both;
}

@media (prefers-reduced-motion: reduce) {
  .sidebar-link.active,
  .sidebar-link.active::before,
  .sidebar-link.active .sidebar-label { animation: none !important; }
}

.sidebar-label {
  opacity: 0;
  transition: opacity 0.2s ease;
}

.app-sidebar.expanded .sidebar-label {
  opacity: 1;
}

/* ============================================================
   2. MESSENGER DUAL-PANE LAYOUT
   ============================================================ */
.messenger-layout {
  display: flex;
  flex-direction: row;
  flex: 1;
  overflow: hidden;
  background: transparent;
  padding: 0.5rem;
  /* Padding creates the outer gap around both windows */
  gap: 0.25rem;
  /* Gap creates the divider between Window 1 and Window 2 */
}

/* --- Resizable Divider --- */
.resizer-divider {
  width: 10px;
  background-color: transparent;
  cursor: col-resize;
  flex-shrink: 0;
  position: relative;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
}

/* In the practice layout the divider takes NO width — the sidebar, editor and
   results panel sit flush against each other, so the file-tab bar meets the
   sidebar with nothing between them. The 10px grab zone is an invisible
   pseudo-element straddling the seam, so the handle is still easy to hit but
   never carves a gap out of the layout. */
.practice-body > .resizer-divider {
  width: 0;
  flex: 0 0 0;
  background-color: transparent;
  overflow: visible;
}
.practice-body > .resizer-divider::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -5px;
  right: -5px;
  cursor: col-resize;
}

/* Grip handle: hidden by default (so the divider is a clean invisible seam),
   then flexes + fades in on hover/drag. */
.resizer-divider::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 4px;
  height: 56px;
  border-radius: 99px;
  background: var(--color-primary);
  opacity: 0;
  transform: translate(-50%, -50%) scaleY(0.2);
  transform-origin: center;
  transition: opacity 0.18s ease,
    transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1),
    box-shadow 0.25s ease;
  pointer-events: none;
}

.resizer-divider:hover::after,
.resizer-divider:active::after {
  opacity: 1;
  transform: translate(-50%, -50%) scaleY(1);
  box-shadow: 0 0 16px var(--color-primary-glow);
}

/* --- Collapse arrow, built into the seam ---------------------------------
   Hidden until you hover the divider, then it fades in on top of the grip. It
   used to be a pair of buttons in the editor toolbar, which put the control
   nowhere near the edge it moves. */
.rz-collapse {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.6);
  width: 18px;
  /* every transform below reuses this pair, so only scale ever varies */
  height: 34px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-primary);
  border-radius: 6px;
  background: #0d1117;
  color: var(--color-primary);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  z-index: 2;
  transition: opacity 0.16s ease,
    transform 0.24s cubic-bezier(0.34, 1.56, 0.64, 1),
    background-color 0.16s ease,
    color 0.16s ease;
}
.rz-collapse svg { width: 13px; height: 13px; }

.resizer-divider:hover .rz-collapse,
.resizer-divider:focus-within .rz-collapse {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, -50%) scale(1);
}

/* Touch has no hover, so on a touch device the arrow could never be revealed
   and the panes could not be collapsed at all. There, it is simply always
   visible — and big enough to hit. */
@media (hover: none) {
  .rz-collapse {
    opacity: 0.75;
    pointer-events: auto;
    transform: translate(-50%, -50%) scale(1);
    width: 24px;
    height: 44px;
  }
  .rz-collapse:active { opacity: 1; }
}
.rz-collapse:hover {
  background: var(--color-primary);
  color: #fff;
  box-shadow: 0 0 14px var(--color-primary-glow);
}
/* While the arrow is under the cursor, the grip behind it would just crowd it. */
.resizer-divider:has(.rz-collapse:hover)::after { opacity: 0; }

/* Pane folded away: nothing left to drag, so the seam becomes a narrow rail
   that holds only the arrow. It claims its own 18px rather than staying
   zero-width, so the button can't sit on top of the line-number gutter. The
   arrow stays faintly visible too — with the pane gone there is no edge left to
   hint at, and a control you can only find by guessing is not a control. */
.practice-body > .resizer-divider.collapsed {
  width: 18px;
  flex: 0 0 18px;
  cursor: default;
  background: #0d1117;
}
.practice-body > .resizer-divider.collapsed::before { display: none; }
.resizer-divider.collapsed::after { display: none; }
.resizer-divider.collapsed .rz-collapse {
  opacity: 0.5;
  pointer-events: auto;
  transform: translate(-50%, -50%) scale(0.85);
}
.resizer-divider.collapsed:hover .rz-collapse { opacity: 1; transform: translate(-50%, -50%) scale(1); }

@media (prefers-reduced-motion: reduce) {
  .rz-collapse { transition: opacity 0.16s ease; transform: translate(-50%, -50%); }
  .resizer-divider:hover .rz-collapse,
  .resizer-divider:focus-within .rz-collapse,
  .resizer-divider.collapsed .rz-collapse,
  .resizer-divider.collapsed:hover .rz-collapse { transform: translate(-50%, -50%); }
}

/* Window 1: Smaller Left Pane (40%) */
.messenger-pane-1 {
  width: 40%;
  /* Set to 40% of the available width */
  max-width: 480px;
  min-width: 320px;
  flex-shrink: 0;
  /* Crucial: Prevents this window from shrinking when sidebar expands */
  background: var(--bg-surface);
  border-radius: var(--radius-xl);
  /* Floating container look */
  border: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}

.pane-1-header {
  padding: 1.25rem 1rem;
  border-bottom: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  gap: 1rem;
  background: transparent;
  z-index: 10;
}

.pane-1-content {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 4px;
}


/* Window 2: Bigger Right Pane (60%) */
.messenger-pane-2 {
  flex: 1;
  /* Takes the remaining ~60% and absorbs all the shrinkage when sidebar expands */
  min-width: 0;
  /* Allows content inside to shrink properly */
  background: var(--bg-surface);
  border-radius: var(--radius-xl);
  /* Floating container look */
  border: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  position: relative;
  height: 100%;
  overflow-y: auto;
}

.messenger-pane-2>* {
  flex-shrink: 0;
}


/* ============================================================
   3. STANDARD PAGE LAYOUT (Browse, Analytics)
   ============================================================ */
.main-content {
  flex: 1;
  overflow-y: auto;
  position: relative;
  display: flex;
  flex-direction: column;
  /* Add gap/padding if standard pages also need the floating look */
  padding: 0.75rem;
}

.page-container {
  max-width: 80rem;
  width: 100%;
  margin: 0 auto;
  padding: var(--space-xl);
  flex: 1;
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border-radius: var(--radius-xl);
  border: 1px solid var(--border-color);
  overflow: hidden;
}

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-bottom: var(--space-xl);
  gap: var(--space-md);
}

.page-title {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 1.875rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.page-title svg {
  color: var(--color-primary);
  width: 28px;
  height: 28px;
}

/* --- Search Bar --- */
.search-container {
  position: relative;
  width: 320px;
}

.search-container svg {
  position: absolute;
  left: 1rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-tertiary);
  width: 18px;
  height: 18px;
  pointer-events: none;
}

.search-input {
  width: 100%;
  background: var(--bg-surface-hover);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-full);
  padding: 0.625rem 1rem 0.625rem 2.5rem;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  color: var(--text-primary);
  outline: none;
  transition: all var(--transition-fast);
}

.search-input:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-glow);
}

.search-input::placeholder {
  color: var(--text-tertiary);
}


/* ============================================================
   4. SPECIFIC MODULE LAYOUTS 
   ============================================================ */

/* --- Practice Layout (Full Dark) --- */
.practice-layout {
  display: flex;
  flex-direction: column;
  flex: 1;
  background: #0d1117;
  color: #b1bac4;
  height: 100%;
  border-radius: var(--radius-xl);
  overflow: hidden;
  border: 1px solid var(--border-color);
}

.practice-body {
  display: flex;
  flex: 1;
  overflow: hidden;
}

/* --- Solution/Diff Layout --- */
.solution-layout {
  display: flex;
  flex-direction: column;
  flex: 1;
  background: #0d1117;
  color: #b1bac4;
  height: 100%;
  border-radius: var(--radius-xl);
  overflow: hidden;
  border: 1px solid var(--border-color);
}

/* --- Legacy Study Area Layout --- */
.study-layout {
  display: flex;
  height: 100%;
  gap: var(--space-xl);
  overflow: hidden;
}

.study-sidebar {
  width: 320px;
  min-width: 320px;
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: var(--space-md);
  overflow-y: auto;
}

.study-main {
  flex: 1;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: var(--space-xl);
  overflow-y: auto;
}

.study-tabs {
  display: flex;
  gap: 1rem;
  margin-bottom: var(--space-lg);
  border-bottom: 1px solid var(--border-color);
}

.study-tab {
  background: none;
  border: none;
  padding: 0.75rem 1.5rem;
  color: var(--text-tertiary);
  font-weight: 600;
  font-size: 0.95rem;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  transition: all var(--transition-fast);
}

.study-tab:hover {
  color: var(--text-primary);
}

.study-tab.active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

.study-tab-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}


/* ============================================================
   5. COLLAPSIBLE FOLDERS & LIST ITEMS (Browse & Study)
   ============================================================ */
.snippet-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  overflow-y: auto;
  flex: 1;
}

.snippet-folder {
  margin-bottom: 0.75rem;
}

.snippet-folder-summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 800;
  color: var(--text-tertiary);
  padding: 0.5rem 0.25rem;
  cursor: pointer;
  user-select: none;
  transition: all var(--transition-fast);
}

.snippet-folder-summary::-webkit-details-marker {
  display: none;
}

.snippet-folder-summary:hover {
  color: var(--text-primary);
}

details[open]>.snippet-folder-summary {
  background: radial-gradient(circle at left, rgba(34, 211, 238, 0.15) 0%, transparent 80%), var(--bg-surface-hover) !important;
  color: var(--text-primary) !important;
  border-color: transparent !important;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-sm);
}

details[open]>.snippet-folder-summary::before {
  content: '';
  position: absolute;
  left: 0;
  top: 15%;
  bottom: 15%;
  width: 3px;
  background: #22d3ee;
  box-shadow: 2px 0 8px #22d3ee;
  border-radius: 0 4px 4px 0;
}

.folder-icon {
  width: 16px;
  height: 16px;
  transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

details[open]>.snippet-folder-summary>.folder-icon {
  transform: rotate(90deg);
}

.snippet-folder-content {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding-left: 0.75rem;
  margin-top: 0.25rem;
  border-left: 1px solid var(--border-color);
  margin-left: 0.5rem;
}

.snippet-list-item {
  padding: 0.75rem 1rem;
  background: transparent;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
  position: relative;
  overflow: hidden;
}

.snippet-list-item:hover {
  background: rgba(148, 163, 184, 0.15);
  border-color: transparent;
}

.snippet-list-item.active {
  background: radial-gradient(circle at left, rgba(34, 211, 238, 0.15) 0%, transparent 80%), var(--bg-nav);
  border-color: transparent;
}

.snippet-list-item.active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 15%;
  bottom: 15%;
  width: 3px;
  background: #22d3ee;
  box-shadow: 2px 0 8px #22d3ee;
  border-radius: 0 4px 4px 0;
}

.snippet-list-title {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-primary);
  transition: color var(--transition-fast);
}

.snippet-list-item.active .snippet-list-title {
  color: #ffffff;
  text-shadow: 0 0 6px rgba(34, 211, 238, 0.4);
}

/* ============================================================
   5b. DEEP NESTED TREE (Folder Library)
   ============================================================ */
.tree-container {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  flex: 1;
  /* No track down the edge of the tree. The global 8px scrollbar is fine on a
     document but reads as a seam beside a list of rows that already have their
     own right-hand accent strips -- two vertical lines a few pixels apart, one
     of them meaningless. The wheel, the trackpad and the keyboard all still
     scroll it; only the bar is gone. Applies to every library tree: browse,
     snippets, study and the two analytics sidebars all carry this class. */
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* legacy Edge */
}
.tree-container::-webkit-scrollbar { width: 0; height: 0; display: none; }

.tree-node {
  display: flex;
  flex-direction: column;
}

/* Depth Highlight Bars for Tree Nodes */
.tree-node[data-level="0"]>.tree-node-row::after {
  content: '';
  position: absolute;
  right: 0;
  top: 10%;
  bottom: 10%;
  width: 3px;
  background: var(--color-primary);
  box-shadow: -2px 0 8px rgba(34, 211, 238, 0.4);
  border-radius: 4px 0 0 4px;
}

.tree-node[data-level="1"]>.tree-node-row::after {
  content: '';
  position: absolute;
  right: 0;
  top: 15%;
  bottom: 15%;
  width: 3px;
  background: var(--color-accent);
  box-shadow: -2px 0 8px rgba(168, 85, 247, 0.4);
  border-radius: 4px 0 0 4px;
}

.tree-node[data-level="2"]>.tree-node-row::after {
  content: '';
  position: absolute;
  right: 0;
  top: 20%;
  bottom: 20%;
  width: 3px;
  background: var(--color-success);
  box-shadow: -2px 0 8px rgba(34, 197, 94, 0.4);
  border-radius: 4px 0 0 4px;
}

.tree-node[data-level="3"]>.tree-node-row::after {
  content: '';
  position: absolute;
  right: 0;
  top: 25%;
  bottom: 25%;
  width: 3px;
  background: var(--color-warning);
  box-shadow: -2px 0 8px rgba(245, 158, 11, 0.4);
  border-radius: 4px 0 0 4px;
}

/* The rail is quiet until you are near it: 0.55 at rest, 0.9 under the pointer,
   full on selection. It was a flat 0.8, which made every row's rail compete
   with the one that mattered. The growth rules are below the per-level blocks,
   which they have to outrank. */
.tree-node-row::after {
  opacity: 0.55;
  transition: opacity 0.25s ease,
              top 0.28s var(--ease-spring),
              bottom 0.28s var(--ease-spring),
              width 0.28s var(--ease-spring);
}

.tree-node[data-level="4"]>.tree-node-row::after,
.tree-node[data-level="5"]>.tree-node-row::after {
  content: '';
  position: absolute;
  right: 0;
  top: 25%;
  bottom: 25%;
  width: 3px;
  background: var(--color-danger);
  box-shadow: -2px 0 8px rgba(239, 68, 68, 0.4);
  border-radius: 4px 0 0 4px;
}

/* ── The rail grows when you reach it ──────────────────────────────────────
   Hover used to change opacity and nothing else, so a 3px bar that is only
   about 26px of a 44px row looked identical whether the pointer was on it or
   not, and read as unresponsive rather than restrained.

   IT GROWS INWARD, NOT OUTWARD, and that is forced rather than chosen. The rail
   is an ::after of .tree-node-row, and the row is `overflow: hidden` -- it has
   to be, that clip is what keeps a long name from spilling past the row while
   the label marquee scrolls it. So anything that moves the rail past the row's
   right edge is not merely invisible, it is cut off the bar: a previous attempt
   pushed it 2px right and lost 2 of its 3.9px, which is why hovering made the
   highlight look SMALLER. Width grows from a right-anchored edge and reaches
   left, which is inside the clip and survives.

   EXPLICIT PIXELS, NOT A SCALE FACTOR. Each depth insets the rail by a
   different percentage -- 10% at level 0 down to 25% at level 4 -- so rest
   heights run 35.1px to 22.0px. One multiplier applied to all of them keeps
   that spread and would overshoot the row's own height at level 0 while barely
   moving level 4. Fixed insets land every level on the same size.

   The chain is repeated to outrank `.tree-node[data-level="N"] > ...::after`,
   which sets the width and insets these override and beats a bare `:hover` on
   its own. */
.tree-node[data-level] > .tree-node-row:hover::after,
.tree-node-row:hover::after {
  opacity: 0.9;
  top: 4px;
  bottom: 4px;
  width: 5px;
}

/* Selection matches hover's SIZE and differs in colour. Making it the fullest
   the rail gets put it at 39.87px of a 43.87px row and 6px wide -- 91% of the
   row height, and half again the resting thickness -- which read as oversized
   rather than as emphasis. It does not need the size: .active swaps the flat
   per-level colour for the animated cyan gradient, takes opacity to 1, and runs
   railFlow/railBreathe, none of which hover does. That is already the loudest
   thing in the tree. */
.tree-node[data-level] > .tree-node-row.active::after,
.tree-node-row.active::after {
  opacity: 1;
  top: 4px;
  bottom: 4px;
  width: 5px;
}

/* ── Folders are the exception, and they have to be ────────────────────────
   The insets above are uniform, which is right for the levels that hold
   programs but wrong for level 0. A folder's rail is already 79.8% of its row
   at rest (35.03px of 43.87) against a program's 59.6% (26.15px), because the
   per-level blocks inset it 10% rather than 20%. So the same 4px hover inset
   that grows a program by 37% grows a folder by 0.84px -- no useful feedback --
   while still widening it 3px -> 5px, and a bar that is nearly row-height and
   suddenly two thirds thicker just reads as oversized.

   Level 0 keeps its resting length in every state and answers in width alone.
   That also preserves the depth hierarchy the per-level insets exist to draw:
   if hover flattened every level to one size, a hovered program would be
   exactly as long as the folder above it.

   Same (0,4,1) specificity as the rules it refines, so it has to sit after
   them rather than merely be more specific. */
.tree-node[data-level="0"] > .tree-node-row:hover::after,
.tree-node[data-level="0"] > .tree-node-row.active::after {
  top: 10%;
  bottom: 10%;
}
.tree-node[data-level="0"] > .tree-node-row:hover::after,
.tree-node[data-level="0"] > .tree-node-row.active::after { width: 4px; }

.tree-node-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* Every row is the same height, folder or program. Equal padding got them to
     within 2px, but a program's label is set a size smaller inline by each
     renderer, so its line box is shorter and no amount of padding closes that
     without touching five render sites. One floor does it, and 44px is also the
     target size a finger expects. */
  min-height: 2.75rem;
  padding: 0.625rem 0.75rem;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  user-select: none;
  position: relative;
  overflow: hidden;
}

.tree-node-row:hover {
  background: rgba(148, 163, 184, 0.12);
  transform: translateX(2px);
}

.tree-node-row:active {
  transform: translateX(1px) scale(0.99);
}

/* ── Selection, from the design system's TreeNode ──────────────
   Converted from the kit's React inline styles. The wash is a cyan radial
   anchored at the RIGHT edge, not a left-to-right ramp: the row's depth rail
   lives on that side, so the light comes from the rail rather than across the
   text. The row also moves LEFT on selection while hover moves right, which is
   what separates the two states without either needing more colour.

   The kit tracked hover and a 340ms just-landed flag in component state; here
   :hover and an animation on .active do the same with no JavaScript. */
.tree-node-row {
  /* Everything not selected is set back a touch, so selection reads as coming
     forward rather than merely being tinted. */
  transform: scale(0.99);
  transition: background 0.25s var(--ease-standard),
              color 0.25s var(--ease-standard),
              box-shadow 0.25s var(--ease-standard),
              transform 0.28s var(--ease-spring);
}

.tree-node-row.active {
  background:
    radial-gradient(circle at right center, rgba(34, 211, 238, 0.10) 0%, transparent 72%),
    var(--bg-surface-hover);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  transform: translateX(-3px);
  color: var(--color-primary);
  /* No keyframe animation here, and that IS the pop.
     smoothReveal animates transform from translateY(8px), so while it ran it
     owned the property outright and the spring transition above could not move
     the row at all -- selection dropped in from below instead of springing
     sideways. The export drives this purely from the transition, on
     --ease-spring, whose overshoot is the pop. */
}

/* Hover must not fight selection: a selected row stays where selection put it,
   or it jumps 5px sideways whenever the pointer crosses it. */
.tree-node-row.active:hover { transform: translateX(-3px); }

/* No labelPop here. It ran the label 0 -> +4px and settled at +2px while the
   row itself slides -3px the other way, so one click moved two things in
   opposite directions and left the text off its own baseline. The row's own
   movement is the feedback; the label just changes weight and colour. */
.tree-node-row.active .tree-node-label {
  font-weight: 700;
  color: #f2f9ff;
  transition: color 0.25s ease;
}

/* The depth rail is already drawn per level below. Selection turns it into the
   same travelling gradient the sidebar's rail uses, so the two selections in
   the app read as one idea. */
.tree-node[data-level] > .tree-node-row.active::after,
.tree-node-row.active::after {
  background: linear-gradient(180deg, #67e8f9 0%, #22d3ee 25%, #0891b2 50%, #22d3ee 75%, #67e8f9 100%);
  background-size: 100% 200%;
  opacity: 1;
  box-shadow: none;
  animation: railFlow 2.4s linear infinite, railBreathe 2.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .tree-node-row { transition: none !important; }
  .tree-node-row::after { transition: none !important; }
  .tree-node-row.active::after { animation: none !important; }
}

.tree-node-row.active .tree-node-icon {
  color: var(--color-primary);
}

.tree-node-row.active .tree-node-label {
  color: var(--text-primary);
}

/* Drag and Drop Reordering Hints */
.tree-node-row.dragging {
  opacity: 0.4;
  background: var(--bg-surface-hover);
}

.tree-node-row.drag-over-top {
  border-top: 2px solid var(--color-primary);
  background: rgba(34, 211, 238, 0.05);
}

.tree-node-row.drag-over-bottom {
  border-bottom: 2px solid var(--color-primary);
  background: rgba(34, 211, 238, 0.05);
}

.tree-node-chevron {
  width: 18px;
  height: 18px;
  min-width: 18px;
  color: var(--text-tertiary);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.15s ease;
  flex-shrink: 0;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.tree-node-chevron:hover {
  color: var(--text-primary);
}

.tree-node-chevron.expanded {
  transform: rotate(90deg);
}

.tree-node-chevron.invisible {
  visibility: hidden;
}

.tree-node-icon {
  width: 18px;
  height: 18px;
  min-width: 18px;
  flex-shrink: 0;
}

.tree-node-icon.folder-icon-color {
  color: var(--color-accent);
}

.tree-node-icon.item-icon-color {
  color: var(--text-tertiary);
}

.tree-node-label {
  font-size: 0.95rem;
  /* increased from 0.875rem */
  font-weight: 600;
  color: var(--text-primary);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--transition-fast);
}

/* A name too long for the pane was an ellipsis and nothing else -- no tooltip,
   no way to read the end of it short of dragging the pane wider. Hovering the
   row now walks the text left far enough to show the end, holds there, and
   walks back.

   The moving part is a span wrapped around the text on hover and unwrapped on
   the way out, so an idle label is untouched markup with its ellipsis intact
   and none of the five renderers that write these labels has to change.

   It animates transform rather than text-indent. text-indent looked like the
   tidier answer -- it needs no wrapper at all -- and it does move the layout:
   a Range over the text reported it shifting from x=161 to x=-4. It just does
   not paint. Verified against a screenshot with the frame proven fresh, the
   text sat unshifted at the start of the box. transform paints, and stays on
   the compositor besides.

   The two travelling legs are the same share of the cycle and the cycle length
   comes from the distance, so a long name and a barely-too-long one move at
   the same speed rather than one whipping past. Below ~55px of overrun the
   minimum duration takes over and it moves slower still, which is wanted: a
   few pixels of travel should not read as a twitch. The hold at 0-12% doubles
   as a start delay, so sweeping the pointer down the tree does not set off a
   wave of motion behind it. */
.tree-node-label.is-scrolling { text-overflow: clip; }
.tree-node-label.is-scrolling .tree-label-run {
  display: inline-block;
  white-space: nowrap;
  will-change: transform;
  animation: treeLabelScroll var(--label-scroll-time, 5s) ease-in-out infinite;
}
@keyframes treeLabelScroll {
  0%, 12%   { transform: translateX(0); }
  47%, 59%  { transform: translateX(var(--label-scroll-dist, 0px)); }
  94%, 100% { transform: translateX(0); }
}
/* Reduced motion gets the name as a tooltip instead -- see _treeLabelStart. */
@media (prefers-reduced-motion: reduce) {
  .tree-node-label.is-scrolling { text-overflow: ellipsis; }
  .tree-node-label.is-scrolling .tree-label-run { animation: none; }
}

.tree-node-badge {
  background: var(--bg-surface-hover);
  padding: 0.125rem 0.375rem;
  border-radius: 1rem;
  font-size: 0.625rem;
  font-weight: 700;
  color: var(--text-tertiary);
  margin-left: auto;
  flex-shrink: 0;
}

.tree-children {
  display: grid;
  grid-template-rows: 1fr;
  /* Use smooth ease-out (no bounce) so adjacent nodes push down gradually, not snap */
  transition: grid-template-rows 0.7s cubic-bezier(0.4, 0, 0.2, 1);
  overflow: hidden;
  border-left: 2px solid var(--border-color);
  margin-left: 1.125rem;
}

.tree-children.collapsed {
  grid-template-rows: 0fr;
}

.tree-children-inner {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  opacity: 1;
  /* Slight delay on fade-in so space opens before content appears */
  transition: opacity 0.7s ease 0.08s;
}

.tree-children.collapsed .tree-children-inner {
  opacity: 0;
  /* Fade out immediately before grid collapses */
  transition: opacity 0.7s ease;
}

/* Stagger slide-in removed to prevent flicker */

@media (prefers-reduced-motion: reduce) {
  .tree-children {
    transition: none !important;
  }

  .tree-children-inner {
    transition: none !important;
  }
}

/* Lock indicator on tree node */
.tree-node-lock {
  width: 14px;
  height: 14px;
  color: var(--color-warning);
  flex-shrink: 0;
  margin-left: 0.25rem;
}

/* Toggle: hide individual items (files/programs/notebooks) in tree, keep folders */
.hide-tree-items > .tree-item-node,
.hide-tree-items .tree-children-inner > .tree-item-node {
  display: none;
}

/* --- Breadcrumbs --- */
.breadcrumb-nav {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.5rem 0;
  margin-bottom: 1rem;
  font-size: 0.8125rem;
  color: var(--text-tertiary);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border-color);
}

.breadcrumb-item {
  cursor: pointer;
  color: var(--text-secondary);
  transition: color var(--transition-fast);
  font-weight: 600;
  background: none;
  border: none;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  padding: 0.125rem 0.375rem;
  border-radius: var(--radius-sm);
}

.breadcrumb-item:hover {
  color: var(--color-primary);
  background: var(--color-primary-subtle);
}

.breadcrumb-separator {
  color: var(--text-tertiary);
  font-size: 0.75rem;
  display: flex;
  align-items: center;
}

.breadcrumb-separator i,
.breadcrumb-separator svg {
  width: 12px;
  height: 12px;
}

.breadcrumb-current {
  color: var(--text-primary);
  font-weight: 700;
  font-size: 0.8125rem;
}

/* --- Right-Click Context Menu --- */
.tree-context-menu {
  position: fixed;
  z-index: 2000;
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xl), 0 0 20px rgba(0, 0, 0, 0.15);
  padding: 0.375rem;
  min-width: 200px;
  animation: popIn 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  transform-origin: top left;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.tree-context-menu.hidden {
  display: none;
}

.tree-context-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.8125rem;
  font-weight: 600;
  font-family: var(--font-sans);
  color: var(--text-secondary);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
  background: none;
  border: none;
  width: 100%;
  text-align: left;
}

.tree-context-item i,
.tree-context-item svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}

.tree-context-item:hover {
  background: var(--color-primary-subtle);
  color: var(--color-primary);
  transform: translateX(2px);
}

.tree-context-item:hover i,
.tree-context-item:hover svg {
  transform: scale(1.1);
}

.tree-context-item.danger {
  color: var(--text-secondary);
}

.tree-context-item.danger:hover {
  background: var(--color-danger-bg);
  color: var(--color-danger);
  transform: translateX(2px);
}

.tree-context-divider {
  height: 1px;
  background: var(--border-color);
  margin: 0.25rem 0.5rem;
}


/* --- Folder Card (right pane subfolder) --- */
.subfolder-card {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1rem;
  background: var(--bg-surface-hover);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.subfolder-card:hover {
  border-color: var(--color-primary);
  background: var(--color-primary-subtle);
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}

.subfolder-card i,
.subfolder-card svg {
  width: 20px;
  height: 20px;
  color: var(--color-accent);
  flex-shrink: 0;
}

.subfolder-card-label {
  font-weight: 700;
  font-size: 0.9375rem;
  color: var(--text-primary);
  flex: 1;
}

.subfolder-card-count {
  font-size: 0.75rem;
  color: var(--text-tertiary);
  font-weight: 600;
}


/* ============================================================
   6. RESPONSIVE DESIGN
   ============================================================ */
@media (max-width: 1024px) {
  .messenger-pane-1 {
    width: 35%;
    min-width: 280px;
  }
}

@media (max-width: 768px) {

  /* Hide sidebar labels completely on small screens, keep just icons */
  .app-sidebar.expanded {
    width: 72px;
  }

  .app-sidebar.expanded .sidebar-label {
    opacity: 0;
    pointer-events: none;
  }

  .messenger-layout {
    flex-direction: column;
  }

  .messenger-pane-1 {
    width: 100%;
    min-width: 100%;
    height: 50%;
    /* Stack panes on mobile */
    border-right: none;
    border-bottom: 1px solid var(--border-color);
  }

  .messenger-pane-2 {
    height: 50%;
  }

  .page-header {
    flex-direction: column;
    align-items: stretch;
  }

  .search-container {
    width: 100%;
  }

  .page-container {
    padding: var(--space-md);
  }

  .practice-body {
    flex-direction: column;
  }

  .practice-sidebar {
    width: 100%;
    min-width: 100%;
    max-height: 40vh;
  }

  .practice-topbar-left {
    flex-shrink: 0;
  }

  .practice-topbar-right {
    flex-shrink: 0;
    gap: 0.25rem;
  }

}

/* The practice topbar runs out of room well before the phone breakpoint — Back,
   two icon toggles, the timer and up to three action buttons already fill it.
   Wrap the title + boss bar onto their own full-width row from 1024px down so
   the SAO nameplate always has real estate instead of collapsing to a sliver. */
@media (max-width: 1024px) {
  .practice-topbar {
    flex-wrap: wrap;
    gap: 0.375rem;
    padding: 0.5rem;
    min-height: unset;
  }

  /* flex-basis, not width — `flex: 1` on the center would otherwise keep it on
     the first row and squeeze the boss bar down to a sliver. */
  .practice-topbar-center {
    order: 3;
    flex: 1 0 100%;
    width: 100%;
    margin: 0 !important;
  }

  /* Unless the bar is switched off, when the centre is one small nameplate and
     a full-width row is 900px of nothing with a label adrift in it. Sized to
     its content it goes back up beside Back on the first row. The rule above
     still owns the case it was written for — the bar, which does want the row. */
  .practice-topbar-center:not(.boss-on) {
    /* Back to source order — the base rule's `order: 3` is what sends the BAR
       to the bottom, and inheriting it put the nameplate after the buttons on
       the right instead of after Back on the left. */
    order: 0;
    flex: 0 1 auto;
    width: auto;
    margin-right: auto !important;
    justify-content: flex-start;
  }

  /* Own row = room again, so the boss nameplate gets its full readout back. */
  .sao-hp { display: inline-block; }
  .sao-boss {
    flex: 1 1 auto;
    max-width: none;
    padding: 11px 0 10px;
  }
  .practice-title-badge { max-width: 45%; }
}

/* ============================================================
   FILE TAB BAR — VS Code-style multi-file tabs
   ============================================================ */
.file-tab-bar {
  display: flex;
  align-items: stretch;
  background: #0d1117;
  border: none;
  border-bottom: 1px solid var(--border-color);
  border-radius: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  min-height: 38px;
  flex-shrink: 0;
}

.file-tab-bar::-webkit-scrollbar {
  display: none;
}

.file-tab {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0 0.875rem;
  font-size: 0.8125rem;
  font-family: var(--font-mono);
  color: var(--text-tertiary);
  white-space: nowrap;
  cursor: pointer;
  border-right: 1px solid var(--border-color);
  border-bottom: 2px solid transparent;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
  user-select: none;
  min-height: 38px;
}

.file-tab:hover {
  color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.04);
}

.file-tab.active {
  color: var(--color-accent, #06b6d4);
  border-bottom-color: var(--color-accent, #06b6d4);
  background: rgba(6, 182, 212, 0.08);
}

.file-tab-name {
  pointer-events: none;
}

.file-tab-x {
  font-size: 0.7rem;
  line-height: 1;
  padding: 1px 2px;
  border-radius: 3px;
  color: var(--text-tertiary);
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.15s, background 0.15s;
}

.file-tab:hover .file-tab-x {
  opacity: 1;
}

.file-tab-x:hover {
  background: rgba(239, 68, 68, 0.2);
  color: var(--color-danger);
}

.file-tab-add {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  min-width: 32px;
  background: none;
  border: none;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
  border-radius: 0;
}

.file-tab-add:hover {
  color: var(--color-primary);
  background: rgba(99, 102, 241, 0.08);
}

/* ============================================================
   MOBILE SIDEBAR OVERLAY (≤640px)
   ============================================================ */
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 99;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: fadeIn 0.2s ease-out;
}

.sidebar-backdrop:not(.hidden) {
  display: block;
}

@media (max-width: 640px) {
  .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    z-index: 200;
    transform: translateX(-100%);
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    min-width: 260px;
    width: 260px;
  }

  .app-sidebar.mobile-open {
    transform: translateX(0);
    box-shadow: var(--shadow-xl);
  }

  /* On mobile, sidebar always shows labels when open */
  .app-sidebar.mobile-open .sidebar-label {
    opacity: 1;
  }

  /* Prevent sidebar from taking layout space on mobile */
  #spa-content {
    margin-left: 0 !important;
  }
}

/* The notebook attempt's section strip is hidden when there is only one section
   to switch to (see renderSidebar). The block under it opens with a rule and a
   pad that separated it FROM that strip, so on its own it drew a line across
   the top of the panel with nothing above it. */
.practice-sidebar.np-single-section > div:nth-child(3) {
  border-top: none !important;
  padding-top: 0 !important;
}
