/* Park onboarding wizard — P3 of #14.
   Standalone maintainer tool, not under the app's CSP. Reuses the graph-editor's
   sidebar + map layout, toast, and panel conventions. */

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: #1a1a1a;
}

#app {
  display: flex;
  height: 100vh;
  width: 100vw;
  overflow: hidden;
}

/* ---- Sidebar ----
   ONE scrollbar, not two (#108). The column used to scroll as a whole AND carry a second scroller
   on the attraction list, so the wheel landed on whichever the pointer happened to be over.

   The inner cap was not gratuitous: `.nav` is the last child of the sidebar, so without it a
   160-attraction park pushed « Suivant → » below the fold. Removing it therefore takes a layout,
   not a deletion — the column becomes a flex column with one scrolling region and a pinned foot,
   and the nav is out of the scroll entirely instead of merely being kept in reach by a cap. */
#sidebar {
  width: 360px;
  min-width: 360px;
  height: 100%;
  display: flex;
  flex-direction: column;
  /* The scrolling belongs to .sidebar-scroll now; the column itself must not add a second one. */
  overflow: hidden;
  background: #f6f7f9;
  border-right: 1px solid #d7dbe0;
}

/* The padding sits here rather than on #sidebar so the scrollbar runs along the edge of the
   column instead of floating 16px inside it. `flex: 1` also keeps the foot at the bottom on the
   short steps, where the content does not fill the height. */
.sidebar-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 14px 16px;
}

/* Pinned below the scroll region. The border is what tells the reader the content passes UNDER it
   rather than ending there. */
.sidebar-foot {
  flex: 0 0 auto;
  padding: 10px 16px 12px;
  border-top: 1px solid #d7dbe0;
  background: #f6f7f9;
}

#sidebar h1 {
  font-size: 17px;
  margin: 0 0 2px;
}

#sidebar .subtitle {
  font-size: 12px;
  color: #6a7280;
  margin: 0 0 14px;
}

.panel {
  background: #fff;
  border: 1px solid #e2e6ea;
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
}

.panel h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #6a7280;
  margin: 0 0 8px;
}

/* ---- Stepper ---- */
.stepper {
  display: flex;
  gap: 4px;
  margin-bottom: 12px;
}

.step {
  flex: 1;
  font-size: 11px;
  text-align: center;
  padding: 6px 2px;
  border-radius: 6px;
  background: #fff;
  border: 1px solid #e2e6ea;
  color: #6a7280;
}

.step .num {
  display: inline-block;
  width: 16px;
  height: 16px;
  line-height: 16px;
  border-radius: 50%;
  background: #d7dbe0;
  color: #374151;
  font-weight: 700;
  margin-right: 2px;
}

.step.active {
  background: #2563eb;
  border-color: #2563eb;
  color: #fff;
}

.step.active .num {
  background: #fff;
  color: #2563eb;
}

.step.done {
  background: #dcfce7;
  border-color: #bbf7d0;
  color: #166534;
}

.step.done .num {
  background: #16a34a;
  color: #fff;
}

/* ---- Step panels ---- */
.step-panel {
  display: none;
}

.step-panel.active {
  display: block;
}

/* ---- Fields ---- */
label.field {
  display: block;
  font-size: 12px;
  margin-bottom: 8px;
}

label.field span {
  display: block;
  font-weight: 600;
  margin-bottom: 3px;
}

label.field input,
label.field select {
  width: 100%;
  padding: 5px 6px;
  font-size: 13px;
  border: 1px solid #c8ced5;
  border-radius: 6px;
}

/* ---- Buttons ---- */
button {
  padding: 7px 10px;
  font-size: 13px;
  border: 1px solid #c8ced5;
  border-radius: 6px;
  background: #fff;
  cursor: pointer;
}

button:hover:not(:disabled) {
  background: #eef1f4;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

button.primary {
  width: 100%;
  padding: 10px;
  font-size: 14px;
  font-weight: 700;
  color: #fff;
  background: #16a34a;
  border: none;
  border-radius: 8px;
  margin-top: 6px;
}

button.primary:hover:not(:disabled) {
  background: #15803d;
}

.nav {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.nav button {
  flex: 1;
}

/* ---- Status / help ---- */
.status {
  font-size: 12px;
  min-height: 1em;
  color: #166534;
}

.status.warn {
  color: #b45309;
}

.help {
  font-size: 11px;
  color: #4b5563;
  line-height: 1.5;
  margin: 6px 0 0;
}

.muted {
  font-size: 12px;
  color: #6a7280;
}

/* ---- Park-name autocomplete dropdown (#36, reworked in #72) ----
   Custom dropdown replacing the native <datalist>: it always shows the top-ranked matches (prefix >
   substring > fuzzy), capped in the DOM. The box shows ~8 rows and SCROLLS past that rather than
   hiding, so a broad query still gives immediate feedback. */
.park-name-field {
  position: relative;
}

.park-suggest {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 500;
  /* ~8 rows visible (~27px each), then the listbox scrolls through the remaining capped matches. */
  max-height: 216px;
  overflow-y: auto;
  margin-top: 2px;
  background: #fff;
  border: 1px solid #c8ced5;
  border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
}

.park-suggest[hidden] {
  display: none;
}

.park-suggest .suggest-item {
  padding: 6px 8px;
  font-size: 13px;
  cursor: pointer;
  border-bottom: 1px solid #eef1f4;
}

.park-suggest .suggest-item:last-child {
  border-bottom: none;
}

.park-suggest .suggest-item:hover,
.park-suggest .suggest-item.active {
  background: #eef1f4;
}

/* Truncation note pinned under the matches when the DOM cap is hit — not a selectable option. */
.park-suggest .suggest-more {
  padding: 6px 8px;
  font-size: 12px;
  font-style: italic;
  color: #6b7480;
  border-top: 1px solid #eef1f4;
  cursor: default;
}

/* Small muted note under a field: the language guessed from GPS (#31), or why a field is locked.
   Selector carries `label.field` so it outweighs the bold `label.field span` above — a hint must not
   read as heavily as the label it sits under. */
label.field .lang-guess-hint,
label.field .field-hint,
.lang-guess-hint,
.field-hint {
  display: block;
  margin: 3px 0 0;
  font-size: 11px;
  font-weight: 400;
  font-style: italic;
  color: #6a7280;
}

/* The ThemeParks UUID hint turned into a warning: the loaded UUID + attractions belong to a park
   other than the one now named (#43). Amber and upright so it reads as an alert, not a soft note. */
label.field .field-hint.warn,
.field-hint.warn {
  color: #b45309;
  font-style: normal;
  font-weight: 600;
}

/* A derived field the maintainer reads but does not type into (slug; guessed ThemeParks UUID). Greyed
   and non-focusable-looking so it is obviously an output, not an input to fill. */
label.field input[readonly] {
  background: #f1f3f5;
  color: #4b5563;
  cursor: default;
}

/* Standing warning in the sidebar header — read before the first step, not discovered at the last. */
.notice {
  margin: 0 0 12px;
  padding: 8px 10px;
  border: 1px solid #fcd34d;
  border-radius: 6px;
  background: #fffbeb;
  font-size: 11px;
  color: #78350f;
  line-height: 1.5;
}

/* ---- Onboarding help (#52) ---- */

/* Per-step intro: a framed callout at the top of a step, spaced from the controls below so the
   guidance reads as its own block, not as a caption glued to the first field. */
.step-intro {
  margin: 0 0 10px;
  padding: 7px 9px;
  border: 1px solid #e2e6ea;
  border-radius: 6px;
  background: #f8fafc;
  font-size: 11px;
  color: #4b5563;
  line-height: 1.5;
}

/* The numbered path shown on the step 0 "Comment ça marche" overview. */
.overview-steps {
  margin: 8px 0 0;
  padding-left: 18px;
  font-size: 11px;
  color: #4b5563;
  line-height: 1.5;
}

.overview-steps li {
  margin: 2px 0;
}

.tp-nomatch {
  margin: 8px 0 0;
  padding: 8px 10px;
  border: 1px solid #fcd34d;
  border-radius: 6px;
  background: #fffbeb;
  font-size: 11px;
  color: #78350f;
  line-height: 1.5;
}

.tp-nomatch p {
  margin: 0 0 4px;
}

.tp-nomatch ul {
  margin: 0;
  padding-left: 18px;
}

/* The OSM-source lint panel folds each code away so a handful of codes over sixty rides stay a
   handful of lines.
   The badge replaces both the list bullet and the native disclosure triangle: one mark per line,
   and it is the very mark the rows below carry. */
.lint-groups {
  list-style: none;
  padding-left: 0;
}

.tp-nomatch summary {
  cursor: pointer;
  list-style: none;
}

.tp-nomatch summary::-webkit-details-marker {
  display: none;
}

.tp-nomatch summary:hover {
  text-decoration: underline; /* the triangle is gone — keep something that says "this opens" */
}

.tp-nomatch details ul {
  margin: 2px 0 6px 26px; /* line the names up under the label, past the badge and its gap */
  padding-left: 16px;
  color: #92400e;
}

/* OSM-source badges on a row (#84). The slot keeps its width when empty so the columns of every
   row stay aligned instead of shifting under the ones that carry a warning; a row can hold two
   badges (a coaster whose only height sits under a key nobody reads). */
.row-lint {
  flex: 0 0 auto;
  min-width: 16px;
  display: inline-flex;
  gap: 2px;
}

/* One colour per code, legended by the panel above. The glyph carries the meaning too, so a
   colour-blind curator loses nothing: ? no type · ≈ too vague · + a tag to add · ↕ height missing ·
   ⇄ height under the wrong key. */
.lint-badge {
  /* inline-block, not the default inline: width/height are ignored on an inline box, which is why
     the same badge came out as a tight patch of colour in the legend and as a proper chip in the
     rows (there its flex parent blockified it). */
  display: inline-block;
  width: 16px;
  height: 16px;
  line-height: 16px;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  border-radius: 3px;
  cursor: help;
  flex: 0 0 auto;
}

/* Badge and label as flex items, centred: a 20px badge and a line of 11px text share no baseline,
   and nudging one with a hand-picked vertical-align only ever gets close. */
.lint-groups summary {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Bigger in the legend than on the rows: the panel sits at 11px, where a row-sized badge reads as
   a speck. On the rows it stays 16px, matched to the checkbox beside it. */
.lint-groups .lint-badge {
  width: 20px;
  height: 20px;
  line-height: 20px;
  font-size: 13px;
}

.lint-W_NO_TYPE {
  background: #e9d5ff;
  color: #6b21a8;
}

.lint-W_GENERIC_TYPE {
  background: #fde68a;
  color: #92400e;
}

.lint-W_COASTER_NO_HEIGHT {
  background: #fecaca;
  color: #991b1b;
}

.lint-W_NONCANONICAL_HEIGHT {
  background: #bfdbfe;
  color: #1e3a8a;
}

/* Not one of the ported rules: the attraction has no OSM object at all. Slate rather than another
   hue — it is the absence of the thing the others describe, and it leads the panel. */
.lint-NO_OSM_POINT {
  background: #e2e8f0;
  color: #334155;
}

/* "suggéré" (#85): the categories were derived from the OSM type and no human has confirmed them.
   Muted on purpose — it marks a state to resolve, not a problem to fix, so it must not compete
   with the lint badges beside it. */
.row-suggested {
  margin-left: 4px;
  padding: 1px 5px;
  border: 1px dashed #94a3b8;
  border-radius: 8px;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #475569;
  cursor: help;
}

/* Heights are derived from OSM and re-derived by the CI: shown, never typed. Greyed enough to read
   as a disabled field at a glance — a near-white tint looked exactly like the editable inputs above
   it, which is worse than no cue at all. */
.row-height[readonly] {
  background: #e8edf3;
  border-color: #cbd5e1;
  color: #334155;
  cursor: help;
}

/* ---- Attraction rows ---- */
/* No max-height and no scroller of its own (#108): the list flows into the sidebar's single
   scrolling region. What used to justify the cap — keeping the nav reachable — is now handled by
   .sidebar-foot, which sits outside the scroll altogether. */
.attraction-list {
  margin-top: 8px;
}

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  padding: 6px 4px;
  border-bottom: 1px solid #eef1f4;
  font-size: 11px;
}

.row.discarded {
  opacity: 0.45;
}

.row .row-name {
  flex: 1 1 90px;
  min-width: 80px;
  padding: 3px 4px;
  font-size: 12px;
  border: 1px solid #c8ced5;
  border-radius: 4px;
}

.row .row-api {
  flex: 1 1 110px;
  min-width: 90px;
  padding: 3px 4px;
  font-size: 11px;
  border: 1px solid #c8ced5;
  border-radius: 4px;
}

.row .row-cats {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  flex: 1 1 100%;
}

.row .row-cats label {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  color: #4b5563;
}

.row .row-heights {
  display: flex;
  gap: 3px;
  flex: 1 1 100%;
}

.row .row-height {
  width: 56px;
  padding: 2px 3px;
  font-size: 11px;
  border: 1px solid #c8ced5;
  border-radius: 4px;
}

/* ---- Review ---- */
.review {
  font-size: 12px;
  line-height: 1.6;
  margin-bottom: 10px;
}

.review code,
.help code,
.step-intro code {
  background: #eef1f4;
  border-radius: 3px;
  padding: 0 3px;
}

/* Resume note in the review summary (#66): flags that submitting updates an existing branch. */
.resume-note {
  margin-top: 6px;
  color: #8a5a00;
  font-weight: 600;
}

/* ---- OSM data freshness (#109) ----
   Sits under the step's status line at `.help` weight while the data is current, and takes on
   colour as it ages: this is a warning that has to be readable at a glance without ever competing
   with the step's own instructions. `:empty` collapses it so the steps that have not loaded
   anything (or a hand-drawn zone) carry no stray gap.

   It restates `.help`'s four declarations instead of the element carrying `class="help freshness"`,
   and that is forced rather than sloppy: `renderOsmFreshness` assigns `el.className` wholesale on
   every paint (it has to, to drop a previous level), so any second class written in the HTML would
   be silently erased the first time the line is painted. Standalone is the only shape that cannot
   quietly lose half of itself. */
.freshness {
  font-size: 11px;
  color: #4b5563;
  line-height: 1.5;
  margin: 6px 0 0;
}

.freshness:empty {
  display: none;
}

/* Same amber as `.status.warn`: in this sidebar, that colour already means "worth a look". */
.freshness.stale {
  color: #b45309;
  font-weight: 600;
}

.freshness.ancient {
  color: #b91c1c;
  font-weight: 600;
}

/* ---- Map ---- */
#map {
  flex: 1;
  height: 100%;
}

/* ---- Toast (from the editor) ---- */
#toast {
  position: fixed;
  bottom: 16px;
  left: 376px;
  z-index: 1000;
  max-width: 420px;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 13px;
  color: #fff;
  background: #1f2937;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  opacity: 0;
  transition: opacity 0.2s;
  pointer-events: none;
}

#toast.show {
  opacity: 1;
}

#toast.error {
  background: #b91c1c;
}

/* Proximity-matched apiName selects (#14): matched by location, not name — flag for review. */
.row-api.by-proximity {
  border-color: #d97706;
  background: #fff7ed;
}

/* Alias-matched apiName selects: matched on name:en / alt_name / int_name rather than the OSM
   primary name. A weaker signal than an exact name match, a stronger one than proximity — hence a
   colour of its own rather than a reuse of the amber above. */
.row-api.by-alias {
  border-color: #2563eb;
  background: #eff6ff;
}

/* Near-matched apiName selects (#54): the names only loosely agree — it is the short distance
   between the OSM object and the ThemeParks attraction that carried the match. The weakest of the
   three name-based signals, so the most saturated colour: these are the rows to read first. */
.row-api.by-near {
  border-color: #7c3aed;
  background: #f5f3ff;
}
