/*
 * CodeRange control plane — LiteChat brand.
 *
 * Propshaft serves this file directly (no require/import bundling), so the whole
 * brand lives here in ordered sections: tokens → base → components → pages.
 * url() references (fonts, logos) are rewritten to digested asset paths by Propshaft.
 * Brand tokens ported from litechat_rails/public/css/styles.css.
 */

/* ------------------------------------------------------------------ tokens */
:root {
  --primary-font: "Fredoka", sans-serif;
  /* Secondary/UI font: a neutral system stack (no bundled webfont). Uses a
     locally-installed Helvetica Neue if the OS has one, else the platform sans. */
  --secondary-font:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
    Helvetica, Arial, sans-serif;
  --body-font: "Inter", sans-serif;

  --blue: #0780b5;
  --dark: #061820;
  --dark-grey: #8c9fa7;
  --light-blue: #96d2ed;
  --light: #f4fbfe;
  --white: #fefefe;

  --bg-color: var(--white);
  --bg-color-light: var(--light);

  --primary-color: var(--blue);
  --primary-color-hover: #066994;
  --primary-color-light: rgba(7, 127, 180, 0.1);

  --text-color: var(--dark);
  --text-color-light: var(--dark-grey);
  --text-color-inverted: var(--light);
  --text-color-accent: var(--blue);

  --border-color: rgba(140, 159, 167, 0.2);
  --input-bg: var(--light);
  --input-border-focus: var(--primary-color);
  --error-color: #ef4444;
  --success-color: #0f9d58;
  /* amber for in-progress/transitional states (no LiteChat equivalent) */
  --warn-color: #d9822b;

  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md:
    0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
  --shadow-lg:
    0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.05);

  --subtle-bg: rgba(0, 0, 0, 0.05);

  --border-radius-sm: 4px;
  --border-radius-md: 8px;
  --border-radius-lg: 16px;
}

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

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

body {
  margin: 0;
  font-family: var(--body-font);
  font-size: 16px;
  line-height: 1.55;
  color: var(--text-color);
  background: var(--bg-color-light);
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4 {
  font-family: var(--primary-font);
  font-weight: 600;
  line-height: 1.15;
  color: var(--text-color);
  margin: 0 0 0.5em;
}

h1 { font-size: 2rem; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.1rem; }

p { margin: 0 0 1rem; }

a {
  color: var(--text-color-accent);
  text-decoration: none;
}
a:hover { text-decoration: underline; }

small { color: var(--text-color-light); }

code {
  font-family: "Courier New", Courier, monospace;
  font-size: 0.9em;
  background: var(--subtle-bg);
  padding: 0.1em 0.35em;
  border-radius: var(--border-radius-sm);
}

/* ------------------------------------------------------------------ layout */
main {
  max-width: 960px;
  margin: 0 auto;
  padding: 2rem 1.25rem 3rem;
}

/* the operator dashboard carries wide, data-dense tables */
main.admin {
  max-width: 1200px;
}

section {
  margin-top: 2rem;
}

/* Shared header (shared/_header) */
.site-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border-color);
  background: var(--white);
}
.site-header__brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--primary-font);
  font-size: 1.4rem;
  font-weight: 600;
  color: var(--text-color);
}
.site-header__brand:hover { text-decoration: none; }
.site-header__logo {
  width: 34px;
  height: 34px;
}
.site-header__nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 1rem;
  font-family: var(--secondary-font);
  font-size: 0.95rem;
}

/* ------------------------------------------------------------------ components: buttons */
/* Base button is NEUTRAL (white/bordered, a secondary action). Primary intent is
   opt-in via .btn--primary, so a bare button never reads as the page's main CTA
   by accident. */
/* :where() keeps the bare-element selectors at zero specificity so a .btn--*
   variant (a single class) always wins — otherwise input[type="submit"] would
   outrank .btn--primary and a primary form submit would render neutral. */
.btn,
:where(button, input[type="submit"]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--secondary-font);
  font-size: 0.95rem;
  font-weight: 500;
  line-height: 1;
  padding: 0.65rem 1.1rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background: var(--white);
  color: var(--text-color);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.btn:hover,
:where(button, input[type="submit"]):hover {
  background: var(--light);
  border-color: var(--dark-grey);
  text-decoration: none;
}
/* An unavailable action that still has to explain itself. Marked aria-disabled
   rather than disabled so it keeps focus and its title tooltip (see
   inert_button_controller.js); this only makes it *read* inert. Deliberately no
   pointer-events: none — that would suppress the tooltip the button exists to show.
   The :hover rules below re-state the resting look so the button doesn't lift. */
.btn[aria-disabled="true"],
:where(button, input[type="submit"])[aria-disabled="true"] {
  background: var(--light);
  color: var(--text-color-light);
  box-shadow: none;
  cursor: not-allowed;
}
.btn[aria-disabled="true"]:hover,
:where(button, input[type="submit"])[aria-disabled="true"]:hover {
  background: var(--light);
  border-color: var(--border-color);
}

.btn--google {
  background: var(--white);
  color: var(--text-color);
  border-color: var(--border-color);
  box-shadow: var(--shadow-sm);
}
.btn--google::before {
  content: "";
  width: 18px;
  height: 18px;
  background: url("/assets/google-logo-5ea439fe.png") no-repeat center / contain;
}
.btn--google:hover {
  background: var(--light);
  border-color: var(--dark-grey);
}

.btn--link {
  background: transparent;
  color: var(--text-color-accent);
  padding: 0.35rem 0.25rem;
  border: none;
  box-shadow: none;
}
.btn--link:hover {
  background: transparent;
  text-decoration: underline;
}

.btn--sm {
  font-size: 0.8rem;
  padding: 0.4rem 0.7rem;
}

/* .btn--primary is the blue call-to-action — opt in where an action is THE next
   step (submit, resume, create). Self-contained so it doesn't lean on the base. */
.btn--primary {
  background: var(--primary-color);
  border-color: transparent;
  color: var(--text-color-inverted);
  box-shadow: none;
}
.btn--primary:hover {
  background: var(--primary-color-hover);
  border-color: transparent;
}
/* destructive actions — red (ported from litechat_rails iam.css .btn-danger) */
.btn--danger {
  background: var(--error-color);
  border-color: var(--error-color);
  color: var(--white);
  box-shadow: none;
}
.btn--danger:hover {
  background: #dc2626;
  border-color: #dc2626;
}

/* neutral outline — a low-emphasis button for a destructive action that should NOT
   read as a call-to-action until the user commits (confirm_button arms it to red). */
.btn--outline {
  background: transparent;
  border-color: var(--border-color);
  color: var(--text-color-light);
  box-shadow: none;
}
.btn--outline:hover {
  background: var(--light);
  border-color: var(--dark-grey);
  color: var(--text-color);
}
/* nudge a destructive action away from the actions clicked often (roster cluster) */
.row-actions__danger { margin-left: 0.5rem; }

/* Host fleet panel (Phase 5): keep the per-host lifecycle buttons on one line. */
.host-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
  align-items: center;
}
.host-actions form {
  display: inline;
  margin: 0;
}
.muted {
  color: var(--dark-grey);
}

/* ------------------------------------------------------------------ components: flash */
/* The flash region floats instead of sitting in flow. Most flashes are set by a
   turbo_stream.update("flash", ...) fired from a button far down the page, which
   navigates nothing and scrolls nothing: as an in-flow block at the top of <body>,
   the message appeared off-screen and shoved the whole page down under the pointer.
   Fixed here, it needs no change to any of the three controller helpers, which all
   already render one partial into this one id. */
#flash {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(26rem, calc(100vw - 2rem));
  /* The region spans its full width even when empty; without this it would swallow
     clicks aimed at whatever sits underneath. Re-enabled per toast below. */
  pointer-events: none;
}
.flash {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.75rem 1.1rem;
  border-radius: var(--border-radius-md);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-lg);
  font-family: var(--secondary-font);
  font-size: 0.95rem;
  overflow-wrap: anywhere;
  animation: flash-in 0.15s ease-out;
}
.flash__message {
  flex: 1;
}
/* Overrides the bare-element button styling, which is deliberately zero-specificity
   (:where) so a single class wins. */
.flash__dismiss {
  flex: none;
  padding: 0;
  width: 1.25rem;
  height: 1.25rem;
  font-size: 1.1rem;
  line-height: 1;
  border: 0;
  background: none;
  box-shadow: none;
  color: inherit;
  opacity: 0.7;
}
.flash__dismiss:hover {
  background: none;
  border: 0;
  opacity: 1;
}
@keyframes flash-in {
  from {
    opacity: 0;
    transform: translateY(-0.35rem);
  }
}
@media (prefers-reduced-motion: reduce) {
  .flash {
    animation: none;
  }
}
/* Both tints are translucent, which was fine for a block on a white page but shows
   page content through a toast. Composite the same tint over an opaque base rather
   than restating the palette. */
.flash--notice {
  background:
    linear-gradient(var(--primary-color-light), var(--primary-color-light)),
    var(--white);
  border-color: rgba(7, 127, 180, 0.3);
  color: var(--primary-color-hover);
}
.flash--alert {
  background:
    linear-gradient(rgba(239, 68, 68, 0.08), rgba(239, 68, 68, 0.08)),
    var(--white);
  border-color: rgba(239, 68, 68, 0.35);
  color: #b91c1c;
}

/* 640px matches the roster's own breakpoint. */
@media (max-width: 640px) {
  /* Full-bleed at the top on narrow screens: a right-pinned card would sit over the
     header's controls. */
  #flash {
    top: 0.5rem;
    right: 0.5rem;
    left: 0.5rem;
    width: auto;
  }
}

/* Inline advisory callout (e.g. "the control plane is still schedulable"). */
.callout {
  max-width: 960px;
  margin: 1rem auto 0;
  padding: 0.75rem 1.1rem;
  border-radius: var(--border-radius-md);
  border: 1px solid var(--border-color);
  font-family: var(--secondary-font);
  font-size: 0.95rem;
}
.callout--warn {
  background: rgba(217, 130, 43, 0.08);
  border-color: rgba(217, 130, 43, 0.35);
  color: var(--warn-color);
}

/* ------------------------------------------------------------------ components: forms */
form div {
  margin-bottom: 1rem;
}
label {
  display: block;
  margin-bottom: 0.35rem;
  font-family: var(--secondary-font);
  font-size: 0.85rem;
  color: var(--text-color-light);
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="url"],
select,
textarea {
  width: 100%;
  max-width: 460px;
  font-family: var(--body-font);
  font-size: 1rem;
  padding: 0.6rem 0.75rem;
  background: var(--input-bg);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  color: var(--text-color);
}
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--input-border-focus);
  box-shadow: 0 0 0 3px var(--primary-color-light);
}

/* ------------------------------------------------------------------ components: cards & lists */
.card,
section > ul,
.my__workshop {
  background: var(--white);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-sm);
}

section > ul {
  list-style: none;
  margin: 0;
  padding: 0.5rem 0;
}
section > ul > li {
  padding: 0.75rem 1.1rem;
  border-bottom: 1px solid var(--border-color);
}
section > ul > li:last-child {
  border-bottom: none;
}

/* tables — wrap in .table-scroll so a wide table scrolls instead of clipping */
.table-scroll {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-lg);
  background: var(--white);
}
.table-scroll table {
  border: none;
  border-radius: 0;
}
table {
  width: 100%;
  border-collapse: collapse;
  background: var(--white);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-lg);
  overflow: hidden;
  font-size: 0.95rem;
}
th,
td {
  text-align: left;
  vertical-align: top;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border-color);
}
/* let long URLs / tokens wrap instead of forcing the column wide */
td code {
  white-space: normal;
  overflow-wrap: anywhere;
}
/* action buttons in a row cell: wrap neatly instead of overlapping */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.row-actions form {
  margin: 0;
}
thead th {
  font-family: var(--secondary-font);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-color-light);
  background: var(--light);
}
tbody tr:last-child td {
  border-bottom: none;
}
/* row hover highlight (ported from iam.css .data-table tbody tr:hover) — helps
   track a row across a wide roster */
tbody tr:hover {
  background: var(--bg-color-light);
}

/* inline budget editor (workshop roster) */
.budget {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.budget__spent {
  font-size: 0.8rem;
  color: var(--text-color-light);
}
.budget__form {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
}
.budget__cap {
  width: 6.5rem;
  padding: 0.35rem 0.5rem;
  font-size: 0.85rem;
}
.budget__unit {
  color: var(--text-color-light);
  font-size: 0.85rem;
}

/* empty state — one look for every "no rows yet" message (ApplicationHelper#empty_state) */
.empty-state {
  padding: 1rem 0;
}
.empty-state__message {
  color: var(--text-color-light);
  margin: 0;
}
.empty-state__hint {
  color: var(--text-color-light);
  font-size: 0.85rem;
  margin: 0.25rem 0 0;
}

/* status chip */
.chip {
  display: inline-block;
  font-family: var(--secondary-font);
  font-size: 0.75rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: var(--subtle-bg);
  color: var(--text-color-light);
}
/* semantic state variants (rendered by ApplicationHelper#status_chip): tinted
   background + matching border + text, in the LiteChat status-badge spirit */
.chip--ok {
  color: var(--success-color);
  background: rgba(15, 157, 88, 0.1);
  border: 1px solid rgba(15, 157, 88, 0.4);
}
.chip--warn {
  color: var(--warn-color);
  background: rgba(217, 130, 43, 0.1);
  border: 1px solid rgba(217, 130, 43, 0.4);
}
.chip--danger {
  color: var(--error-color);
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid rgba(239, 68, 68, 0.4);
}
.chip--muted {
  border: 1px solid var(--border-color);
}

/* ------------------------------------------------------------------ page: auth */
main.auth {
  max-width: 460px;
  margin-top: 8vh;
}
body:has(main.auth) {
  background:
    radial-gradient(1200px 600px at 5% 0%, #e8f5fc 0%, transparent 60%),
    radial-gradient(900px 500px at 100% 100%, #eef4f8 0%, transparent 62%),
    var(--bg-color-light);
  min-height: 100dvh;
}
main.auth h1 {
  font-size: 2.6rem;
  margin-bottom: 0.25rem;
}
main.auth > p {
  color: var(--text-color-light);
  margin-bottom: 1.5rem;
}
main.auth .btn--google {
  width: 100%;
}
/* elevated sign-in card (ported from litechat_rails login.css #login-form) */
.auth__card {
  padding: 1.75rem;
  border: 1px solid rgba(140, 159, 167, 0.25);
  border-radius: 14px;
  background: rgba(254, 254, 254, 0.9);
  box-shadow: 0 18px 42px rgba(6, 24, 32, 0.08);
}
/* "or" divider between Google and the operator/dev fallback */
.separator {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin: 1.25rem 0;
  color: var(--text-color-light);
}
.separator hr {
  flex-grow: 1;
  border: none;
  border-top: 1px solid var(--border-color);
}
.separator span {
  font-family: var(--secondary-font);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.auth__native {
  padding-top: 0.25rem;
}
.auth__native summary {
  cursor: pointer;
  font-family: var(--secondary-font);
  font-size: 0.85rem;
  color: var(--text-color-light);
}
.auth__native form {
  margin-top: 1rem;
}

/* ------------------------------------------------------------------ page: admin */
.admin__header {
  margin-bottom: 1.5rem;
}
.admin__header nav {
  margin-top: 0.5rem;
  font-family: var(--secondary-font);
  font-size: 0.95rem;
  color: var(--text-color-light);
}
.admin__header nav a { font-weight: 500; }

/* view header: title left, actions right (ported from iam.css .view-header) */
.view-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}
.view-header h1 { margin: 0; }
.action-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.action-bar form { margin: 0; }

/* label → value detail grid (ported from iam.css .detail-panel/.detail-row;
   --sidebar-bg mapped to --white) */
.detail-panel {
  background: var(--white);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-lg);
  padding: 0.5rem 1.25rem;
  box-shadow: var(--shadow-sm);
  margin-bottom: 1.5rem;
}
.detail-row {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border-color);
}
.detail-row:last-child { border-bottom: none; }
.detail-label {
  font-family: var(--secondary-font);
  font-size: 0.85rem;
  color: var(--text-color-light);
}
.detail-value { color: var(--text-color); }
@media (max-width: 560px) {
  .detail-row { grid-template-columns: 1fr; gap: 0.15rem; }
}

/* roster: one card per participant (replaces the old cramped 8-col table) —
   identity / workspace+budget / actions as separated zones with breathing room */
.roster {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.roster-item {
  background: var(--white);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.25rem;
}
.roster-item__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border-color);
}
.roster-item__email {
  font-family: var(--secondary-font);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.roster-item__body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem 2rem;
  padding: 0.9rem 0;
}
.roster-item__zone {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  align-items: flex-start;
}
.roster-item__zone-label {
  font-family: var(--secondary-font);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-color-light);
}
.roster-item__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.roster-item__field {
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}
.roster-item__field-label {
  color: var(--text-color-light);
  margin-right: 0.35rem;
}
.roster-item__muted { color: var(--text-color-light); }
/* The expiry countdown is reference info, not a warning: dimmer and smaller than
   the chips it sits under, so it doesn't compete with the workspace's state. */
.roster-item__expiry {
  font-size: 0.8rem;
  color: var(--text-color-light);
}
.roster-item__actions {
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-color);
}
/* push a destructive action to the far end of the action bar */
.row-actions__end { margin-left: auto; }

/* workspace links list (shared/_workspace_credentials + workspace_ports/_frame) —
   owned by the shared partial, so both the operator roster and the student card
   render it without borrowing either component's class names. Row 1 is the
   workspace URL; the rest are detected/manual app ports. */
.ws-links {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.9rem;
}
.ws-links__ports {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.ws-links__loading,
.ws-links__empty {
  color: var(--text-color-light);
  margin: 0.15rem 0;
}

/* one link row: badge + optional process on the left, URL + actions on the right,
   wrapping to a second line on narrow surfaces (the roster column). */
.ws-link {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  overflow-wrap: anywhere;
}
.ws-link__label {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
}
.ws-link__badge {
  font-family: var(--secondary-font);
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--subtle-bg);
  color: var(--text-color);
}
.ws-link__proc { color: var(--text-color-light); font-size: 0.8rem; }
.ws-link__url { flex: 1 1 12rem; min-width: 0; }
.ws-link__actions {
  display: inline-flex;
  gap: 0.3rem;
  margin-left: auto;
}
.ws-link__note { margin: 0; color: var(--text-color-light); font-size: 0.85rem; }

/* reachable row — a subtle ok tint on the badge so a working link reads as live. */
.ws-link--link .ws-link__badge {
  color: var(--success-color);
  background: rgba(15, 157, 88, 0.12);
}

/* the headline warning: an app bound to 127.0.0.1 the proxy can't reach. Loud
   enough that a beginner notices, with the fix inline instead of a dead link. */
.ws-link--warn {
  border-color: rgba(217, 130, 43, 0.4);
  background: rgba(217, 130, 43, 0.08);
}
.ws-link--warn .ws-link__badge {
  color: var(--warn-color);
  background: rgba(217, 130, 43, 0.15);
}
.ws-link--warn .ws-link__note { color: var(--warn-color); }

/* Refresh control, under the rows. */
.ws-links__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.15rem;
}
.ws-links__refresh { margin-left: auto; }

/* login password — copy-first (password_field_controller). Masked by default with a
   primary Copy and a secondary reveal; sits below the links, gating them all. */
.ws-password {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
  font-size: 0.9rem;
}
.ws-password__label { color: var(--text-color-light); }
.ws-password__value { letter-spacing: 0.05em; }
.ws-password__actions {
  display: inline-flex;
  gap: 0.3rem;
  margin-left: auto;
}
.btn--copy {
  background: var(--white);
  color: var(--text-color);
  border-color: var(--border-color);
}
.btn--copy:hover { background: var(--light); }
.btn--copy.is-copied {
  background: var(--success-color, var(--primary-color));
  border-color: transparent;
  color: var(--text-color-inverted);
}

@media (max-width: 640px) {
  .roster-item__body { grid-template-columns: 1fr; }
}

/* Live-adapter indicator (fake vs real) — study 1782983709 §6. */
.app-footer {
  margin-top: 2rem;
  padding: 0.75rem 1.25rem;
  border-top: 1px solid var(--border-color);
  color: var(--text-color-light);
  text-align: center;
}

/* State that was auto-healed to match the real runtime (ReconcileWorkspacesJob). */
.drift-note {
  display: inline-block;
  margin-top: 0.25rem;
  font-size: 0.8rem;
  color: var(--warn-color);
}

/* ------------------------------------------------------------------ rostered-user (/my) surface */
main.my {
  max-width: 720px;
  margin: 2rem auto;
  padding: 0 1.25rem;
}
.my__list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.my__workshop { padding: 1rem 1.25rem; }
.my__workshop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border-color);
}
.my__workshop-head h2 { margin: 0; font-size: 1.15rem; }
.ws-card {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding-top: 0.9rem;
}
.ws-card__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.ws-card__field { font-size: 0.9rem; overflow-wrap: anywhere; }
/* Twin of .roster-item__expiry — reference info, quieter than the state chips. */
.ws-card__expiry {
  font-size: 0.8rem;
  color: var(--text-color-light);
}
.ws-card__muted { color: var(--text-color-light); }
/* Explains Persistent to a student who has no control over it. Same size as
   .ws-card__field above it. The operator roster carries the same copy in the
   persistence toggle's tooltip instead — there it's about a button. */
.ws-card__help {
  font-size: 0.9rem;
  color: var(--text-color-light);
  margin: 0;
}
.ws-card__actions {
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-color);
}
