/* ALY Projects and the Project workspace.
   Kept separate from aly.css so each file stays under the ALYSSIUN 300-line
   source rule, and so the Projects surface can be reasoned about on its own.
   Same LUX tokens as everything else — nothing here invents a colour. */
.aly-projects {
  min-width: 0; min-height: 0; height: 100%; align-self: stretch;
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-gutter: stable;
  touch-action: pan-y;
  padding: 0 clamp(14px, 4vw, 28px) 40px;
}
.aly-projects[hidden] { display: none; }
.projects-head { width: min(860px, 100%); margin: 0 auto; padding: 34px 0 18px; }
/* The heading and the primary action share a row, so the way to start is level
   with the title rather than somewhere below the fold. It wraps before it
   crowds: on a narrow screen the button drops to its own full-width line. */
.projects-head-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.projects-head h1 { margin: 0; font-size: clamp(28px, 5vw, 40px); font-weight: 200; }
.projects-head p { margin: 6px 0 0; font-size: 15px; }
.projects-new { margin-left: auto; white-space: nowrap; }
@media (max-width: 520px) {
  .projects-new { margin-left: 0; width: 100%; }
}

/* The empty state IS the invitation. It gets the room a first impression needs,
   and offers the SAME action as the button above it — two ways to start is one
   too many, and the create form now lives in a dialog rather than sitting on
   this page competing with the Projects below it. */
.projects-empty-state { width: min(720px, 100%); margin: 0 auto; padding: 30px 0 8px; text-align: center; }
.projects-empty-state h2 { margin: 0 0 10px; font-size: clamp(20px, 3.6vw, 28px); font-weight: 250; }
.projects-empty-state p { margin: 0 auto 22px; max-width: 52ch; font-size: 15px; }
.projects-start { min-width: 220px; justify-content: center; }
@media (max-width: 520px) {
  .projects-start { width: 100%; }
}

/* 3 columns, hard-capped — `auto-fill` grew a new column for every 260px of
   width, which is why a wide monitor got a wall of small cards with no upper
   bound instead of a few larger ones. `repeat(3, minmax(0, 1fr))` fixes the
   column COUNT; the cards grow to fill whatever width `width` below allows,
   up to the max-width cap so they never get absurdly wide on a 4K display. */
.projects-grid {
  width: min(1600px, 96%); margin: 0 auto; padding: 0; list-style: none;
  display: grid; gap: 18px; grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 900px) {
  .projects-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
}
@media (max-width: 560px) {
  .projects-grid { grid-template-columns: minmax(0, 1fr); }
}
.project-card, .project-open, .project-body, .project-goal { min-width: 0; overflow-wrap: anywhere; }
/* No `overflow: hidden` here: the ⋯ menu below is an absolutely positioned
   child of this same card and can extend past its bottom edge — the
   thumbnail's own top corner radius (matching `--radius`) is what keeps it
   flush without needing to clip the card itself. */
.project-card { position: relative; border: 1px solid var(--hair); background: var(--glass); border-radius: var(--radius); }
.project-card:hover { background: var(--glass-2); }
/* The thumbnail sits flush against the card's own edges — its 16:9 image area
   comes first, so padding moves onto `.project-body` (below) instead of the
   link that wraps both. */
.project-open { display: block; }
.project-open:hover { text-decoration: none; }
.project-body { padding: 14px 18px 18px; }
.project-body h2 {
  margin: 0 0 8px; font-size: 18px; font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.project-goal { margin: 0; font-size: 13px; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
.project-meta { display: flex; align-items: center; gap: 10px; margin: 10px 0 0; font-size: 11px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.project-state { border: 1px solid var(--hair-2); border-radius: 5px; padding: 3px 7px; font-size: 10px; }
.project-state.is-failed { color: var(--danger); border-color: var(--danger); }
.project-state.is-building { color: var(--jade); border-color: var(--jade); }
/* What a Project is built on. Semantic tokens only — no brand colour is
   hardcoded here, per the ALYSSIUN design system contract. */
.project-kind { border: 1px solid var(--hair-2); border-radius: 5px; padding: 3px 7px; font-size: 10px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text); }
.project-kind.is-3d { border-color: var(--text); }
.project-when { text-transform: none; letter-spacing: 0; margin-left: auto; }

/* Card actions sit below the link rather than inside it: a button nested in an
   anchor is not reliably reachable by keyboard, and "Delete" must never be a
   thing somebody hits while trying to open a Project. */
.project-actions {
  display: flex; gap: 6px; padding: 0 12px 12px; flex-wrap: wrap;
}
.project-action { font-size: 12px; padding: 6px 10px; min-height: 34px; }
.project-action.is-danger { color: var(--danger); }

/* One compact menu keeps Project actions together and makes the card itself
   about the work. It is outside the project link, so it cannot accidentally
   open a Project when somebody is trying to manage it. */
/* Sits over the thumbnail, whose gradient hue is different per Project — a
   transparent trigger read as decoration, not a button, on some of them. The
   frosted circular chip behind the dots gives it a consistent, visible plate
   over ANY thumbnail colour, at rest, not only on hover. */
.project-menu-trigger {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 30px; height: 30px; min-width: 30px; min-height: 30px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .22);
  color: var(--text); cursor: pointer; font: inherit; font-size: 18px; line-height: 1;
  transition: background .14s ease, border-color .14s ease, transform .14s ease;
}
.project-menu-trigger:hover {
  background: var(--surface); border-color: color-mix(in srgb, var(--text) 30%, transparent);
  transform: scale(1.06);
}
.project-menu-trigger:focus-visible {
  outline: 2px solid var(--text); outline-offset: 2px;
}
.project-menu-trigger[aria-expanded="true"] {
  background: var(--surface); border-color: var(--text);
}
.project-menu {
  position: absolute; z-index: 30; top: 46px; right: 10px; min-width: 156px;
  padding: 5px; display: grid; gap: 2px; border: 1px solid var(--surface-line);
  border-radius: 10px; background: var(--surface); box-shadow: var(--card-shadow);
}
.project-menu-item {
  min-height: 36px; border: 0; border-radius: 6px; padding: 8px 10px; background: none;
  color: var(--text); cursor: pointer; font: inherit; font-size: 13px; text-align: left;
}
.project-menu-item:hover, .project-menu-item:focus-visible { background: var(--glass-2); }
.project-menu-item.is-danger { color: var(--danger); }
/* The same menu, hung off the gear on the ALY dock. It is placed against the
   VIEWPORT by aly-project-menu.js rather than inside the dock, because the dock
   clips its own overflow to keep its rounded corners — a menu appended into its
   head would be cut in half. `top` and `right` are cleared here because that
   placement sets whichever pair it needs. */
.dock-menu { position: fixed; top: auto; right: auto; }
.project-infra-back { margin-left: auto; }
.project-infra-move { white-space: nowrap; }
/* Once the head wraps, `margin-left: auto` strands Back on the right of its own
   line with Move on the left of the next. On a phone the two controls belong
   together, under the heading. */
@media (max-width: 640px) {
  .project-infra-back { margin-left: 0; }
}
.project-infra-card { width: min(860px, 100%); margin: 0 auto; }

/* AI usage, reused verbatim from the builder panel's Settings tab. Its own rule
   in aly-workspace-tabs.css indents it inside a 320px-wide panel and rules it off
   from the fields above it; on a full-width page it is a card of its own, so the
   panel's margins and hairline are the only things undone here. */
.project-settings-ai { width: min(860px, 100%); margin: 0 auto 12px; }
.project-settings-ai .workspace-ai-usage { margin: 0; padding-top: 0; border-top: 0; }

/* Project details — the name and the note, which used to be a panel of their
   own inside the builder. Their fields were written for a 320px-wide panel
   (aly-workspace-tabs.css indents them by 14px and pins the Save to a footer
   the full width of it); on a page they are a card like the others, so those
   panel margins are the only things undone here. */
.project-settings-details { width: min(860px, 100%); margin: 0 auto 12px; }
.project-settings-details .workspace-setting-label { margin-left: 0; margin-right: 0; }
.project-settings-details .workspace-setting-input { width: 100%; margin: 0; }
.project-settings-details .workspace-setting-counter { margin-right: 0; }
.project-settings-details .workspace-settings-footer {
  margin-top: 14px; padding: 12px 0 0; background: none;
  border-top: 1px solid var(--aly-line, rgba(128, 128, 128, 0.22));
}
.project-settings-details .workspace-settings-save { width: auto; min-width: 140px; padding: 0 18px; }

/* The Settings LINK that stands where the builder panel's Settings TAB did. It
   belongs in that row, so it looks like its neighbours — but it opens a page
   rather than switching a panel, and never shows the selected state a tab
   does, because nothing here is ever "the current tab". */
.workspace-panel-tab-link { color: var(--muted); }
.workspace-panel-tab-link:hover, .workspace-panel-tab-link:focus-visible { color: var(--text); }

/* The build offer, in the transcript. It reads as ALY asking, not as a banner. */
.build-offer {
  margin: 10px 0 4px; padding: 16px 18px; border: 1px solid var(--hair-2);
  border-radius: var(--radius); background: var(--input-2); box-shadow: var(--card-shadow);
}
.build-offer-question { margin: 0 0 6px; font-size: 15px; }
.build-offer-note { margin: 0 0 14px; font-size: 13px; }
.build-offer-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.projects-empty { width: min(860px, 100%); margin: 0 auto; padding: 40px 0; text-align: center; }
.aly-projects-link { width: 100%; justify-content: center; margin-top: -6px; }

/* --- Infrastructure -------------------------------------------------------
   The Space's own page. Deliberately the same visual language as the Projects
   list: this is not a console, it is the same shelf viewed from the side. */

.infra-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 28px;
  padding: 14px 0 18px;
  border-bottom: 1px solid var(--aly-line, rgba(128, 128, 128, 0.22));
  margin-bottom: 18px;
}

.infra-stat { display: flex; flex-direction: column; gap: 2px; }
.infra-stat strong { font-size: 1.35rem; font-weight: 600; line-height: 1.1; }
.infra-stat span { font-size: 0.82rem; opacity: 0.7; }
.infra-stat-attention strong { color: var(--aly-warn, #c2410c); }

.infra-cards { display: flex; flex-direction: column; gap: 12px; }

.infra-card {
  border: 1px solid var(--aly-line, rgba(128, 128, 128, 0.22));
  /* 4px, not a pill: the ALYSSIUN design system's own 2–4px law. */
  border-radius: 4px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Attention is a left edge, not a red card. A page that shouts at you about a
   full disk is a page you learn to ignore. */
.infra-card.is-attention { border-left: 3px solid var(--aly-warn, #c2410c); }

.infra-card-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.infra-card-name {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: inherit;
  cursor: pointer;
  text-align: left;
}
.infra-card-name:hover { text-decoration: underline; }

.infra-pill {
  font-size: 0.75rem;
  padding: 2px 8px;
  border-radius: 3px;
  border: 1px solid var(--aly-line, rgba(128, 128, 128, 0.22));
  opacity: 0.8;
}
.infra-pill.is-live { border-color: currentColor; opacity: 1; }

.infra-address { font-size: 0.82rem; opacity: 0.75; word-break: break-all; }

.infra-attention {
  margin: 0;
  padding-left: 18px;
  font-size: 0.86rem;
  color: var(--aly-warn, #c2410c);
}

.infra-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  font-size: 0.82rem;
  opacity: 0.75;
}

/* The Project's real files. A <details> because a person who only wants to know
   whether their app works should not have to scroll past a source listing. */
.infra-files { font-size: 0.82rem; margin-top: 10px; }
.infra-files summary { cursor: pointer; opacity: 0.65; }
.infra-file-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  max-height: 260px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-gutter: stable;
}
.infra-file-list li {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 3px 0;
  border-bottom: 1px solid var(--hair, rgba(20, 20, 26, 0.08));
}
.infra-file-list li:last-child { border-bottom: 0; }
.infra-file-list li.is-folder .infra-file-path { opacity: 0.7; font-weight: 600; }
.infra-file-path { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
.infra-file-size { opacity: 0.55; white-space: nowrap; }

/* The development environment. A <details> beside Files, for the same reason:
   somebody who only wants to know whether their app works should not have to
   scroll past a dependency list to find out. */
.infra-dev { font-size: 0.82rem; margin-top: 10px; }
.infra-dev summary { cursor: pointer; opacity: 0.65; }
.infra-dev .infra-facts { margin-top: 8px; }
.infra-dep-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  max-height: 200px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-gutter: stable;
}
.infra-dep-list li {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 3px 0;
  border-bottom: 1px solid var(--hair, rgba(20, 20, 26, 0.08));
}
.infra-dep-list li:last-child { border-bottom: 0; }

.infra-advanced { font-size: 0.82rem; }
.infra-advanced summary { cursor: pointer; opacity: 0.65; }
.infra-advanced-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 14px;
  margin: 8px 0 0;
}
.infra-advanced-list dt { opacity: 0.65; }
.infra-advanced-list dd { margin: 0; }

.infra-section { margin-top: 26px; }
.infra-section h2 { font-size: 1rem; margin: 0 0 6px; }

.infra-usage { list-style: none; margin: 8px 0 0; padding: 0; }
.infra-usage li {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 6px 0;
  border-bottom: 1px solid var(--aly-line, rgba(128, 128, 128, 0.14));
  font-size: 0.86rem;
}
.infra-usage-value { opacity: 0.7; white-space: nowrap; }

/* Narrow screens: the summary wraps rather than scrolling sideways, and the
   advanced grid becomes one column. Nothing here has a fixed width. */
@media (max-width: 520px) {
  .infra-summary { gap: 10px 20px; }
  .infra-advanced-list { grid-template-columns: 1fr; gap: 0 0; }
  .infra-advanced-list dd { margin-bottom: 6px; }
}

/* Connections: what this application may reach, and what it knows.
   A <details> beside Development and for the same reason — most visits are
   asking "does it work?", and the answer to "may it call Stripe?" is only
   interesting on the visit where it is the whole question. */
.infra-connections { font-size: 0.82rem; margin-top: 10px; }
.infra-connections summary { cursor: pointer; opacity: 0.65; }
.infra-connections .infra-facts { margin-top: 8px; }
.infra-connections .infra-facts .is-attention { color: var(--warn, #b4531a); font-weight: 600; }
.infra-connections p.muted { margin: 6px 0; }
.infra-connections .btn { margin-top: 10px; }
.infra-subhead {
  font-size: 0.82rem;
  font-weight: 600;
  margin: 16px 0 0;
  opacity: 0.75;
}
/* A row here carries a host, its usage and a Remove — three columns rather than
   the two `.infra-dep-list` assumes, so the middle one is what stretches. */
.infra-connections .infra-dep-list li { align-items: baseline; }
.infra-connections .infra-dep-list li .muted { flex: 1; text-align: right; }
.btn-quiet {
  background: none;
  border: 0;
  padding: 0 2px;
  font: inherit;
  opacity: 0.55;
  cursor: pointer;
  text-decoration: underline;
}
.btn-quiet:hover { opacity: 1; }
