/* Application shell: layout and components. */

/* ---------------------------------------------------------------- layout -- */

.app {
  display: grid;
  /* `auto` rather than a fixed header height: at narrow widths the actions
     wrap onto their own line, and a fixed row would clip them out of reach. */
  grid-template-rows: auto 1fr;
  height: 100vh;
  height: 100dvh; /* mobile browser chrome must not push the canvas off-screen */
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  min-height: var(--header-height);
  padding: var(--space-2) var(--space-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.header__brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.header__mark {
  flex: none;
  color: var(--accent);
}

.header__title {
  font-size: var(--text-base);
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.main {
  display: grid;
  grid-template-columns: 1fr var(--panel-width);
  min-height: 0;
}

.stage {
  position: relative;
  background: var(--surface-sunken);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none; /* pointer events drive pan and zoom, not the browser */
}

.stage__status {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-3);
  margin: 0;
  padding: var(--space-1) var(--space-3);
  background: color-mix(in srgb, var(--bg) 85%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  color: var(--text-dim);
  pointer-events: none;
  max-width: calc(100% - var(--space-6));
}

.panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  background: var(--surface);
  border-left: 1px solid var(--border);
  overflow-y: auto;
}

/* Below this width the sidebar stops being a sidebar. */
@media (max-width: 900px) {
  .main {
    grid-template-columns: 1fr;
  }

  .panel {
    border-left: none;
    border-top: 1px solid var(--border);
  }
}

/* ------------------------------------------------------------ components -- */

.card {
  padding: var(--space-4);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.card__title {
  font-size: var(--text-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: var(--space-3);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}

.badge__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.badge--connected {
  color: var(--accent);
  border-color: var(--accent-dim);
  background: var(--accent-fade);
}

.badge--standalone {
  color: var(--text-dim);
}

/* Empty-state block that fills the stage until the canvas arrives. */
.placeholder {
  max-width: 34rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: center;
}

.placeholder__mark {
  color: var(--accent);
  opacity: 0.55;
}

.placeholder__title {
  font-size: var(--text-xl);
  letter-spacing: 0.02em;
}

.placeholder__body {
  color: var(--text-dim);
  font-size: var(--text-sm);
}

.notice {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-left: 3px solid var(--text-faint);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: var(--text-sm);
  color: var(--text-dim);
  text-align: left;
}

.notice--info {
  border-left-color: var(--accent);
}

.notice--warn {
  border-left-color: var(--warn);
  background: var(--warn-fade);
}

.notice__title {
  color: var(--text);
  font-size: var(--text-sm);
}

/* Reference list of machine constants. Values are right-aligned so digits
   line up in the mono face. */
.spec {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
}

.spec__key {
  color: var(--text-faint);
  white-space: nowrap;
}

.spec__value {
  color: var(--text);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.spec__value--pending {
  color: var(--text-faint);
  font-style: italic;
}

.spec__value--good {
  color: var(--accent);
}

.phase {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

.phase__current {
  color: var(--accent);
}

/* At phone widths the mono face makes large headings run into the gutters. */
@media (max-width: 480px) {
  .placeholder__title {
    font-size: var(--text-lg);
  }
}


/* ----------------------------------------------------------------- forms -- */

.header__actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.card__header .card__title {
  margin-bottom: 0;
}

.button {
  min-height: var(--tap);
  padding: 0 var(--space-4);
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition);
  white-space: nowrap;
}

.button:hover {
  background: var(--surface);
  border-color: var(--accent-dim);
}

.button--primary {
  background: var(--accent-fade);
  border-color: var(--accent-dim);
  color: var(--accent);
}

.button--primary:hover {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

.button-row {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.button-row .button {
  flex: 1;
  padding: 0 var(--space-2);
}

.field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

.field-grid--four {
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* Four fields across a 320px panel leaves no room for a unit beside each
   number; the card's own width and height fields already establish millimetres. */
.field-grid--four .field__unit {
  display: none;
}

.field-grid--four .field__control {
  padding-right: 0;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.field__label {
  font-size: var(--text-xs);
  color: var(--text-faint);
  letter-spacing: 0.04em;
}

.field__control {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding-right: var(--space-2);
  min-width: 0;
}

.field__control:focus-within {
  border-color: var(--accent-dim);
}

.field__input {
  flex: 1;
  min-width: 0;
  width: 100%;
  min-height: 38px;
  padding: 0 var(--space-2);
  background: transparent;
  border: none;
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.field__input:focus {
  outline: none;
}

/* Spinner arrows cost ~15px of a field's width and are unusable at the sizes
   the four-up margin grid allows. The fields carry explicit step values and
   are typed into directly. */
.field__input::-webkit-outer-spin-button,
.field__input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.field__input[type='number'] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.field__unit {
  font-size: var(--text-xs);
  color: var(--text-faint);
  white-space: nowrap;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.checkbox {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 32px;
  font-size: var(--text-sm);
  color: var(--text-dim);
  cursor: pointer;
}

.checkbox__input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}

.hint {
  font-size: var(--text-xs);
  color: var(--text-faint);
  line-height: var(--leading);
}

.empty {
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* --------------------------------------------------------------- objects -- */

.object-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.object {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding: 0 var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition);
}

.object:hover {
  background: var(--surface);
}

.object--selected {
  background: var(--accent-fade);
  border-color: var(--accent-dim);
}

.object__visibility,
.object__remove {
  flex: none;
  width: 28px;
  height: 28px;
  background: none;
  border: none;
  color: var(--text-faint);
  cursor: pointer;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
}

.object__visibility:hover,
.object__remove:hover {
  color: var(--accent);
  background: var(--surface-raised);
}

.object__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-sm);
}

.object__kind {
  flex: none;
  font-size: var(--text-xs);
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* The spec list is reused inside cards that already have a heading. */
.card .spec {
  margin-top: var(--space-3);
}

.card .notice {
  margin-top: var(--space-3);
}

@media (max-width: 900px) {
  /* Below this width the page scrolls as a whole rather than the app being
     pinned to the viewport with a scrolling panel inside it.

     A soft keyboard shrinks the viewport. With the app locked to 100dvh and
     the panel as the scroll container, a focused field near the bottom ends up
     outside the visible area with nowhere to scroll to — the browser cannot
     bring it into view, so it dismisses the keyboard instead. Letting the page
     scroll restores the browser's normal scroll-into-view behaviour. */
  .app {
    height: auto;
    min-height: 100vh;
    min-height: 100dvh;
  }

  .main {
    grid-template-rows: auto auto;
  }

  .stage {
    /* svh rather than vh or dvh: the small-viewport unit is the height with
       browser chrome and keyboard showing, so the canvas keeps a stable size
       instead of resizing under the user's finger. */
    height: 55vh;
    height: 55svh;
    min-height: 300px;
  }

  .panel {
    /* The page is the scroll container now, not this. */
    overflow: visible;
  }

  /* The actions take their own line below the brand and badge. This only
     works because the header wraps and its grid row is `auto`; with a fixed
     row height the buttons end up outside the viewport and unclickable. */
  .header__actions {
    order: 3;
    width: 100%;
  }

  .header__actions .button {
    flex: 1;
    padding: 0 var(--space-2);
  }
}

/* ---------------------------------------------------------------- layers -- */

.layer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding: 0 var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition);
}

.layer:hover {
  background: var(--surface);
}

.layer--selected {
  background: var(--accent-fade);
  border-color: var(--accent-dim);
}

.layer__order {
  flex: none;
  width: 18px;
  font-size: var(--text-xs);
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.layer__colour {
  flex: none;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
}

.layer__colour::-webkit-color-swatch-wrapper {
  padding: 2px;
}

.layer__colour::-webkit-color-swatch {
  border: none;
  border-radius: 2px;
}

.layer__name {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-2);
}

.layer__name:hover {
  border-color: var(--border);
}

.layer__name:focus {
  background: var(--surface-sunken);
  border-color: var(--accent-dim);
  outline: none;
}

/* A select needs the same treatment as the number fields. */
select.field__input {
  min-height: 38px;
  cursor: pointer;
}

/* ------------------------------------------------------------------ tabs -- */

.tabs {
  display: flex;
  gap: var(--space-1);
  margin: calc(var(--space-2) * -1) calc(var(--space-2) * -1) var(--space-3);
  border-bottom: 1px solid var(--border);
}

.tab {
  flex: 1;
  min-height: var(--tap);
  padding: 0 var(--space-2);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-dim);
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--transition), border-color var(--transition);
}

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

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

/* The pane is the card's content, so it carries no chrome of its own. */
.tabs__body > * + * {
  margin-top: var(--space-3);
}

/* -------------------------------------------------------------- scrubber -- */

.field__range {
  width: 100%;
  min-height: var(--tap);
  accent-color: var(--accent);
  background: none;
  cursor: pointer;
}

/* The label carries a live readout on its right, so it has to be a row. */
.field__label--split {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
}

.field__readout {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- plotter -- */

.progress {
  height: 6px;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.progress__fill {
  height: 100%;
  background: var(--accent);
  transition: width var(--transition);
}

/* A cross, so the arrows sit where the directions are. */
.jog {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas: ". up ." "left . right" ". down .";
  gap: var(--space-2);
  margin-top: var(--space-3);
  justify-items: stretch;
}

.jog .button:nth-child(1) { grid-area: up; }
.jog .button:nth-child(2) { grid-area: left; }
.jog .button:nth-child(3) { grid-area: right; }
.jog .button:nth-child(4) { grid-area: down; }

/* A link that has to look like the buttons beside it. */
a.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
