/* ==========================================================================
   Limonade — design system
   --------------------------------------------------------------------------
   Matches the sibling Opossum app's theme 1:1 (Material Design 3, indigo
   primary, Inter/JetBrains Mono) for visual consistency across the product
   suite. Variable names below are Limonade's own (kept so the rest of this
   file didn't need touching) but every value is Opossum's actual token.
   ========================================================================== */

:root {
  /* -- Palette (= Opossum's MD3 indigo tokens) -- */
  --ink-950: #f1f5f9;       /* md-background */
  --steel-800: #ffffff;     /* md-surface */
  --steel-700: #f1f5f9;     /* md-surface-2 */
  --steel-600: #e2e8f0;     /* md-outline */
  --blue-line: #4f46e5;     /* md-primary (indigo) */
  --blue-line-dim: #4338ca; /* md-primary-hover */
  --torch-500: #4f46e5;     /* single primary accent, no secondary hue */
  --torch-600: #3730a3;     /* md-primary-active */
  --paper-100: #f8fafc;     /* md-surface-variant */
  --paper-050: #ffffff;
  --ink-on-paper: #0f172a;  /* md-on-surface */
  --muted-on-paper: #475569;/* md-on-surface-variant */
  --muted-on-ink: #475569;
  --text-on-ink: #0f172a;

  --ok-500: #10b981;
  --ok-100: #d1fae5;
  --ko-500: #ef4444;
  --ko-100: #fee2e2;
  --warn-500: #f59e0b;  /* md-warning */
  --warn-100: #fef3c7;
  --secondary-500: #6366f1; /* md-secondary */
  --muted-lighter: #94a3b8; /* md-on-surface-muted — de-emphasized text (timestamps, section labels) */

  /* -- Type (= Opossum's font stack) -- */
  --font-display: "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", "Fira Code", Consolas, monospace;

  /* -- Geometry -- */
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-pill: 9999px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;

  --shell-topbar-height: 56px;
  --shell-bottomnav-height: 60px;

  /* -- Elevation (= Opossum's shadow scale) -- */
  --shadow-xs: 0 1px 2px rgba(15,23,42,.05);
  --shadow-sm: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.06);
  --shadow-md: 0 4px 6px rgba(15,23,42,.07), 0 2px 4px rgba(15,23,42,.06);
  --shadow-lg: 0 10px 15px rgba(15,23,42,.08), 0 4px 6px rgba(15,23,42,.05);

  /* -- Motion (= Opossum's easing/duration tokens) -- */
  --ease-default: cubic-bezier(0.4,0,0.2,1);
  --ease-decel: cubic-bezier(0,0,0.2,1);
  --duration-xs: 100ms;
  --duration-sm: 150ms;
  --duration-md: 250ms;

  color-scheme: light;
}

* { box-sizing: border-box; }

html, body {
  height: 100dvh;
  margin: 0;
  overflow: hidden;
  max-width: 100vw;
  background: var(--ink-950);
  color: var(--text-on-ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  touch-action: manipulation;
  -webkit-font-smoothing: antialiased;
}

.htmx-indicator { opacity: 0; transition: opacity 120ms ease; }
.htmx-request.htmx-indicator,
.htmx-request .htmx-indicator { opacity: 1; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: 8px;
  top: -48px;
  z-index: 100;
  background: var(--torch-500);
  color: #ffffff;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-weight: 700;
  text-decoration: none;
  transition: top 120ms ease;
}
.skip-link:focus { top: 8px; }

a { color: var(--blue-line); }
a:hover { color: var(--torch-500); }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.01em;
  margin: 0 0 var(--space-3);
}
h1 { font-size: 1.6rem; letter-spacing: -0.02em; }
h2 {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted-on-ink);
}
h3 {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--blue-line);
  margin-top: var(--space-4);
  margin-bottom: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--steel-600);
}
h3:first-child { border-top: none; padding-top: 0; margin-top: 0; }

:focus-visible {
  outline: 2px solid var(--torch-500);
  outline-offset: 2px;
}

/* ---- App shell ---- */
.app-shell {
  display: grid;
  grid-template-rows: var(--shell-topbar-height) 1fr;
  grid-template-columns: 232px 1fr;
  grid-template-areas:
    "topbar topbar"
    "sidebar main";
  height: 100dvh;
  min-width: 0;
}

.topbar {
  grid-area: topbar;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: env(safe-area-inset-top, 0px) var(--space-4) 0;
  border-bottom: 1px solid var(--steel-600);
  background: var(--steel-800);
  min-width: 0;
}

.topbar .brand {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-on-ink);
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.topbar .brand svg { flex-shrink: 0; border-radius: 7px; }
.topbar .brand:hover { color: var(--text-on-ink); }

.drawer-toggle {
  display: none;
  background: none;
  border: 1px solid var(--steel-600);
  color: var(--text-on-ink);
  border-radius: var(--radius-sm);
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
}

/* Inline SVG icons everywhere: kill baseline gap, never let them squash. */
svg { display: block; }
.btn svg, .sidebar nav a svg, .bottom-nav a svg, .drawer-toggle svg { flex-shrink: 0; }

.sidebar {
  grid-area: sidebar;
  border-right: 1px solid var(--steel-600);
  background: var(--steel-800);
  padding: var(--space-4) var(--space-3);
  overflow-y: auto;
  min-height: 0;
  min-width: 0;
}

.sidebar nav a {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px 12px 10px 13.5px;
  border-left: 2.5px solid transparent;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  color: var(--muted-on-ink);
  text-decoration: none;
  font-size: 0.88rem;
  margin: 1px 8px 1px 0;
  font-family: var(--font-body);
  transition: background var(--duration-sm) var(--ease-default),
              color var(--duration-sm) var(--ease-default);
}

.sidebar nav a:hover {
  background: var(--steel-700);
  color: var(--text-on-ink);
}
.sidebar nav a.active {
  background: #eef2ff;
  color: var(--blue-line);
  border-left-color: var(--blue-line);
  font-weight: 600;
}

.nav-group { margin-bottom: 2px; }
.nav-group-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px 12px 10px 13.5px;
  color: var(--text-on-ink);
  font-size: 0.88rem;
  font-weight: 600;
  font-family: var(--font-body);
}
.nav-sub {
  display: block;
  padding: 8px 12px 8px 44px;
  border-left: 2.5px solid transparent;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  color: var(--muted-on-ink);
  text-decoration: none;
  font-size: 0.85rem;
  margin: 1px 8px 1px 0;
  transition: background var(--duration-sm) var(--ease-default),
              color var(--duration-sm) var(--ease-default);
}
.nav-sub:hover { background: var(--steel-700); color: var(--text-on-ink); }
.nav-sub.active {
  background: #eef2ff;
  color: var(--blue-line);
  border-left-color: var(--blue-line);
  font-weight: 600;
}

.nav-section-label {
  font-family: var(--font-display);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-lighter);
  padding: var(--space-4) var(--space-3) 6px;
  border-top: 1px solid var(--steel-700);
  margin-top: var(--space-2);
}

main.content {
  grid-area: main;
  overflow-y: auto;
  min-height: 0;
  min-width: 0;
  padding: var(--space-6);
}

/* ---- Designer layout ----
   One shared surface for the whole page (form + results) instead of two
   independently-boxed cards side by side — the earlier per-column
   border+shadow treatment read as two separate embedded frames rather
   than a single page. A single outer card with an internal divider reads
   as one continuous view. */
/* Single flowing column, not two side-by-side panels: the drawing is
   live feedback on the data being entered, not a separate "result" to
   scroll to — it belongs directly under whichever step is showing, at
   any viewport width. */
.designer-surface {
  background: var(--steel-800);
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
  max-width: 820px;
  margin: 0 auto;
}

.designer-col {
  padding: var(--space-4);
  border-bottom: 1px solid var(--steel-600);
}

#results-panel { padding: var(--space-4); }
/* Nested inside .designer-surface, which already provides the border/
   shadow for the whole page — the cut-sheet here only needs its paper
   tint, not its own competing card frame (it keeps the full treatment
   on the tutorial page, where it's the only element on screen). */
#results-panel .cut-sheet { border: none; box-shadow: none; border-radius: var(--radius-md); }

.panel {
  background: var(--steel-800);
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  box-shadow: var(--shadow-xs);
}

/* Stacked sub-sections within the results column: a divider, not a box,
   so they read as parts of one panel rather than separate cards. */
.result-section {
  padding-top: var(--space-4);
  margin-top: var(--space-4);
  border-top: 1px solid var(--steel-700);
}
.result-section:first-child { padding-top: 0; margin-top: 0; border-top: none; }

/* Elevation + vue de dessus: stacked (mobile) by default, side by side once
   there's enough width for both cut-sheets to stay legible -- the divider
   border-top from .result-section only makes sense stacked, so it's
   suppressed once the grid goes two columns. */
.results-diagrams { display: grid; grid-template-columns: 1fr; gap: var(--space-4); }
@media (min-width: 769px) {
  /* align-items default (stretch) so both cut-sheets share the same
     height, even though elevation carries an extra stamp-row the plan
     view doesn't -- same-size gray boxes rather than one taller. */
  .results-diagrams { grid-template-columns: 1fr 1fr; }
  .results-diagrams .cut-sheet { height: 100%; display: flex; flex-direction: column; }
  .results-diagrams .result-section { padding-top: 0; margin-top: 0; border-top: none; height: 100%; }
  .results-diagrams .result-section .cut-sheet { height: 100%; }
}

[x-cloak] { display: none !important; }

/* Designer form wizard: same single form/field-set as before (so the
   existing live-recalc-on-change HTMX wiring is untouched and the
   results column keeps updating continuously regardless of which step
   is showing) — just gated into one visible group at a time instead of
   one long scroll of ~19 fields. */
.wizard-progress { margin-bottom: var(--space-4); }
.wizard-progress__label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.8rem;
  color: var(--muted-on-ink);
  margin-bottom: var(--space-2);
}
.wizard-progress__label span:last-child { font-weight: 600; color: var(--text-on-ink); }
.wizard-progress__track { display: flex; gap: 6px; }
.wizard-progress__dot {
  flex: 1 1 0;
  height: 5px;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--steel-600);
  cursor: pointer;
  transition: background var(--duration-sm) var(--ease-default);
}
.wizard-progress__dot.is-done { background: var(--blue-line); opacity: .5; }
.wizard-progress__dot.is-active { background: var(--blue-line); }

.wizard-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--steel-700);
}

.field-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--steel-700);
}
.field-row:last-child { border-bottom: none; }

.field-row label {
  flex: 1 1 auto;
  font-size: 0.85rem;
  color: var(--muted-on-ink);
}

.field-row__label-group { display: flex; align-items: center; gap: 6px; flex: 1 1 auto; }
.field-row__label-group label { flex: 1 1 auto; }

.field-help { position: relative; display: inline-flex; flex: 0 0 auto; }
.field-help__btn {
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid var(--steel-600);
  background: var(--steel-800);
  color: var(--muted-on-ink);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--duration-sm) var(--ease-default),
              color var(--duration-sm) var(--ease-default),
              border-color var(--duration-sm) var(--ease-default);
}
.field-help__btn:hover, .field-help__btn[aria-expanded="true"] {
  background: var(--blue-line);
  border-color: var(--blue-line);
  color: #ffffff;
}
/* Fixed + centered in the viewport, not anchored to the button: the
   button can sit anywhere in the form (including near an edge), and an
   absolutely-positioned popover anchored to it could render partly or
   fully off-screen depending on scroll position. Centering guarantees
   it's always fully visible regardless of which "?" was clicked. */
.field-help__backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, .45);
  z-index: 40;
}
.field-help__popover {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 41;
  width: 220px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - 32px);
  overflow: auto;
  padding: var(--space-3);
  background: #ffffff;
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.field-help__popover img { display: block; width: 100%; height: auto; }
.field-help__close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: none;
  background: var(--steel-700);
  color: var(--muted-on-ink);
  cursor: pointer;
  font-size: .7rem;
  line-height: 1;
}
.field-help__close:hover { background: var(--steel-600); }

.field-row input,
.field-row select {
  width: 132px;
  padding: 8px var(--space-2);
  border: 1.5px solid var(--steel-600);
  border-radius: var(--radius-md);
  background: var(--steel-800);
  color: var(--text-on-ink);
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
  outline: none;
  transition: border-color var(--duration-sm) var(--ease-default),
              box-shadow var(--duration-sm) var(--ease-default);
}
/* 132px fits the short numeric/"Droite"/"Gauche" values fine, but clips
   "Marche sous-palliere" with no ellipsis (browsers just cut a <select>'s
   displayed value silently) -- this one field's options are real sentences,
   not short tokens, so it gets its own wider rule instead of widening
   every select in the form. */
.field-row select.select--wide { width: 220px; }
.field-row input:hover:not(:focus), .field-row select:hover:not(:focus) {
  border-color: var(--muted-lighter);
}
.field-row input:focus, .field-row select:focus {
  border-color: var(--blue-line);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, .15);
}

/* ---- Cut-sheet: the signature panel ---- */
.cut-sheet {
  background: var(--paper-100);
  color: var(--ink-on-paper);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-4) var(--space-2);
  border: 1px solid var(--steel-600);
  box-shadow: 0 10px 15px rgba(15, 23, 42, .08), 0 4px 6px rgba(15, 23, 42, .05);
  min-width: 0;
}
.cut-sheet-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  border-bottom: 2px solid var(--ink-on-paper);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-2);
}
.cut-sheet-header h2 {
  color: var(--ink-on-paper);
  font-family: var(--font-display);
  font-size: 1rem;
}
.cut-sheet-header .plate-id {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--muted-on-paper);
  white-space: nowrap;
}
.tutorial-subtitle {
  color: var(--muted-on-paper);
  font-size: 0.82rem;
  font-style: italic;
  margin: 6px 0 0;
}

@media (max-width: 640px) {
  /* The uppercase title and the mono step/plate badge no longer fit side
     by side at phone widths — stacking avoids the two fighting for space
     and wrapping into each other. */
  .cut-sheet-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
}

.elevation-svg { width: 100%; height: auto; display: block; }
.elevation-svg .profile-line { fill: none; stroke: var(--ink-on-paper); stroke-width: 2.5; }
.elevation-svg .stringer-line { stroke-width: 5; stroke-linecap: round; }
.elevation-svg .stringer-line.ok { stroke: var(--torch-600); }
.elevation-svg .stringer-line.ko { stroke: var(--ko-500); }
.elevation-svg .baseline { stroke: var(--muted-on-paper); stroke-width: 1; stroke-dasharray: 2 3; }
.elevation-svg .dim-line { stroke: var(--blue-line-dim); stroke-width: 1; fill: none; }
.dim-arrow-fill { fill: var(--blue-line-dim); }
.elevation-svg .dim-label {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--blue-line-dim);
  font-weight: 600;
  /* Halo punches a visual gap in the dimension line right where the value
     sits, instead of floating the label off to the side of it — same
     empty-zone-of-the-line convention as the tutorial/PDF chips. */
  paint-order: stroke fill;
  stroke: var(--paper-100);
  stroke-width: 5px;
  stroke-linejoin: round;
}
.elevation-svg .angle-label {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--ink-on-paper);
  font-weight: 600;
  /* Halo so the label stays legible wherever it lands near the stringer
     line or baseline, instead of relying on position alone to clear them. */
  paint-order: stroke fill;
  stroke: var(--paper-100);
  stroke-width: 4px;
  stroke-linejoin: round;
}
.plan-svg { width: 100%; height: auto; display: block; }
.plan-outline {
  fill: rgba(79, 70, 229, .08);
  stroke: var(--blue-line);
  stroke-width: 2;
  stroke-linejoin: round;
}
.stairway-3d-wrap { position: relative; }
.stairway-3d-view {
  width: 100%;
  height: 360px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #f4f1ea;
  cursor: grab;
}
.stairway-3d-view:active { cursor: grabbing; }
@media (min-width: 769px) {
  .results-diagrams .result-section:has(.stairway-3d-view) { grid-column: 1 / -1; }
}

.stairway-3d-alert {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  right: 52px;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--ko-100);
  color: var(--ko-500);
  border: 1px solid var(--ko-500);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.stairway-3d-alert svg { flex-shrink: 0; }
.stairway-3d-alert:not([data-jump-targets]) { cursor: default; }

.stairway-3d-controls {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.stairway-3d-controls .icon-btn {
  width: 32px;
  height: 32px;
  background: rgba(255, 255, 255, .85);
  border-color: rgba(15, 23, 42, .15);
  color: var(--ink-on-paper);
  backdrop-filter: blur(2px);
}
.stairway-3d-controls .icon-btn:hover:not(:disabled) {
  background: #ffffff;
  border-color: var(--blue-line);
  color: var(--blue-line);
}
.stairway-3d-controls [data-view-action="zoom-in"] { margin-top: 10px; }
.stairway-3d-controls [data-view-action="reset"] { margin-top: 10px; }
.stairway-3d-hint {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--muted-on-paper);
  text-align: center;
  margin-top: var(--space-2);
}
.stairway-3d-stats {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--muted-on-ink);
  text-align: center;
  margin: var(--space-2) 0 var(--space-4);
  min-height: 1.2em;
}
.stairway-3d-stats .ok { color: var(--ok-500); font-weight: 600; }
.stairway-3d-stats .ko { color: var(--ko-500); font-weight: 600; }

.plan-marker--start { fill: var(--ok-500); }
.plan-marker--end { fill: var(--blue-line); }
.plan-label {
  font-family: var(--font-mono);
  font-size: 10px;
  fill: var(--muted-on-paper);
}

.elevation-empty {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--muted-on-paper);
  padding: var(--space-8) 0;
  text-align: center;
}

/* Pro-gated tutorial steps: a small gallery of what's behind the paywall
   (dimmed thumbnail + what it's for) instead of a plain text wall. */
.pro-teaser {
  padding: 24px 16px;
  text-align: center;
  overflow-y: auto;
  /* Without flex:1+min-height:0, this flex item (inside
     .tutorial-page__sheet's column) defaults to min-height:auto and
     refuses to shrink below its content height — the 2x2 thumbnail grid
     is taller than small mobile screens, so it overflowed the fixed-height
     tutorial page instead of scrolling internally, cutting off the "Voir
     les plans" button below the fold. */
  flex: 1 1 auto;
  min-height: 0;
}
.pro-teaser__heading {
  color: var(--ink-on-paper);
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 8px;
}
.pro-teaser__intro { color: var(--muted-on-paper); font-size: 0.9rem; max-width: 480px; margin: 0 auto 20px; }
.pro-teaser__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(160px, 220px));
  gap: 16px;
  justify-content: center;
  margin-bottom: 20px;
}
.pro-teaser__card {
  background: var(--paper-100);
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  text-align: left;
}
.pro-teaser__thumb {
  position: relative;
  height: 100px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #fff;
  margin-bottom: 8px;
}
.pro-teaser__thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: grayscale(0.6) blur(1px) brightness(1.05);
  opacity: 0.75;
}
.pro-teaser__lock {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--warn-500);
  color: #fff;
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
}
.pro-teaser__title { font-family: var(--font-display); font-weight: 600; font-size: 0.82rem; color: var(--ink-on-paper); margin-bottom: 2px; }
.pro-teaser__desc { font-size: 0.74rem; color: var(--muted-on-paper); line-height: 1.35; }

@media (max-width: 480px) {
  .pro-teaser__grid { grid-template-columns: 1fr; }
}

/* Tutorial page: main.content gets the exact leftover height of the app
   shell (topbar/bottom-nav subtracted by the grid); a legible image takes
   priority over fitting that without scrolling, so main.content's own
   overflow-y:auto is the intended fallback once content runs taller than
   the viewport (see .tutorial-image-frame's flex-shrink:0 below). */
.tutorial-page {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.tutorial-page__sheet {
  flex: 1 1 auto;
  /* No min-height:0 here (unlike most flex-column patterns in this file):
     that override lets a flex item shrink below its own content's natural
     size, which is exactly what caused the card's background to stop
     short while the (deliberately non-shrinking) image inside kept
     rendering past it. Leaving min-height at its default (auto) makes the
     card grow to actually contain the image instead of visually spilling
     out of it. .pro-teaser (the other possible child here) still scrolls
     fine internally -- it sets flex:1 1 auto + min-height:0 on itself. */
  display: flex;
  flex-direction: column;
}
.tutorial-page__nav { flex: 0 0 auto; }

/* Tutorial images: dimensions overlaid directly on the photo, at the
   corner nearest what they describe — the original desktop app placed
   QLabel widgets at absolute coordinates on top of the same tutorial
   photos (view/Tutorial/*.py SetGeometry), rather than listing values
   in a separate panel below. */
.tutorial-image-frame {
  position: relative;
  border-radius: 6px;
  /* visible, not hidden: a dimension chip near a photo edge can be wider
     than the frame itself once it's narrow (tall/narrow photos, or a short
     viewport) -- hidden silently clipped the chip's own text instead of
     just letting it spill a few px past the rounded corner. */
  overflow: visible;
  border: 1px solid var(--steel-600);
  background: #fff;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* When the server knows the image's real aspect ratio (see
   image_aspect_ratio in tutorial.py), the frame is forced to match it
   exactly instead of being sized by flex layout. Without this,
   object-fit:contain letterboxes the <img> inside a frame whose own
   proportions don't match the picture, so the dimension chips below
   (positioned absolutely against the FRAME's corners) drift away from
   the actual photo — every tutorial image has a different ratio (0.4 to
   1.8), so the drift was never negligible. flex:0 (no grow) so the box
   sizes purely from aspect-ratio + the max-width/max-height cap, rather
   than being stretched to fill the column's remaining height. */
.tutorial-image-frame[style*="aspect-ratio"] {
  /* flex-shrink:0 (not 1): with shrink allowed, flexbox actively
     compresses this item to fit whatever's left of .tutorial-page's fixed
     height:100% -- so max-height alone never mattered, the frame was
     always squeezed back down to fit the viewport regardless of the cap.
     Refusing to shrink forces the flex column to overflow its nominal
     height instead, which is exactly what should happen now that a
     legible image matters more than avoiding page scroll. */
  flex: 0 0 auto;
  /* Both axes capped (not just height) so every step renders inside the
     same envelope regardless of its own photo's real ratio (0.42 tall to
     2.47 wide across the 9 steps) -- picking whichever axis binds first,
     same idea as object-fit:contain but on the frame itself. Values
     chosen to land close to step 1's own on-screen size (its 0.443 ratio
     photo ends up height-bound at ~310x700): landscape steps that used to
     stretch to the full column width (up to ~850px, dwarfing step 1) now
     cap at 520px wide instead. The photo's own aspect-ratio (set inline,
     see image_aspect_ratio in tutorial.py) is never touched, only this
     envelope -- so the dimension chips (positioned as % of the frame,
     which still exactly equals the real photo since there's no
     letterboxing) stay pixel-aligned regardless of which axis binds. */
  max-width: 520px;
  max-height: 700px;
  margin: 0 auto;
}
.tutorial-image-frame img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; display: block; }

/* .tutorial-marker holds one dimension's dot + its data chip. Position
   comes from an inline style (percentages measured off the original
   app's reference screenshots — see _pos() in tutorial.py), anchored
   from whichever edge(s) are nearest so it never needs a centering
   transform; .anchor-left/right/top/bottom (set alongside the inline
   style, from the same position dict) drive which direction the chip
   itself grows so it doesn't run off the photo.
   Desktop: the chip is always visible, dot hidden — matches how the
   original desktop app showed every QLabel at once. Mobile: only a small
   pulsing dot shows by default (screen's too small for every chip at
   once); tapping it reveals that one chip, keeping the drawing legible.
   Both use the exact same anchor point — only the visual treatment
   (dot vs. always-open chip) differs between them. */
.tutorial-marker { position: absolute; z-index: 1; }

.tutorial-dot {
  display: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid #fff;
  background: var(--torch-500);
  box-shadow: 0 0 0 rgba(79, 70, 229, .6);
  padding: 0;
  cursor: pointer;
}
.tutorial-dot.is-open { background: var(--torch-600); }

@keyframes tutorial-dot-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(79, 70, 229, .55); }
  70%  { box-shadow: 0 0 0 9px rgba(79, 70, 229, 0); }
  100% { box-shadow: 0 0 0 0 rgba(79, 70, 229, 0); }
}

.tutorial-dim {
  background: rgba(255, 255, 255, 0.95);
  border: 1px solid var(--blue-line);
  border-radius: var(--radius-sm);
  padding: 3px 6px;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  line-height: 1.25;
  color: var(--ink-on-paper);
  /* Was nowrap: on a narrow frame (tall/narrow photo, or a short viewport)
     a long label had nowhere to go but past the frame edge, where
     overflow:hidden on the frame clipped it outright. Wrapping to 2 lines
     beats either losing text or spilling past the card uncontrolled. */
  white-space: normal;
  /* Shrunk from 190/0.72rem: the busiest steps pack 9-11 chips over one
     photo (see tutorial.py), and the original QLabels were much smaller
     than our first pass at this chip's size -- a bigger chip only needed
     more collision-avoidance nudging without adding real legibility. */
  max-width: 150px;
  box-shadow: var(--shadow-sm);
}
.tutorial-dim__label { display: block; color: var(--muted-on-paper); font-size: 0.58rem; }
.tutorial-dim__value { display: block; color: var(--blue-line); font-weight: 600; }

.tutorial-dim.align-right { text-align: right; }

/* .tutorial-label is a static on-image caption with no bound value (e.g.
   "Interieur"/"Exterieur"/"Cotes de controle" section headers) -- always
   visible at every viewport, unlike .tutorial-dim which collapses to a
   tap-to-reveal dot on mobile since it carries an actual measurement. */
.tutorial-label {
  position: absolute;
  z-index: 1;
  background: rgba(255, 255, 255, 0.95);
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-sm);
  padding: 3px 8px;
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted-on-paper);
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
}

@media (max-width: 480px) {
  .tutorial-label { font-size: 0.6rem; padding: 2px 6px; }
}

@media (min-width: 769px) {
  /* Desktop: unchanged from before — every chip visible at once. */
  .tutorial-dim { position: static; }
}

@media (max-width: 768px) {
  .tutorial-dot {
    display: block;
    animation: tutorial-dot-pulse 2s infinite;
  }
  .tutorial-dim {
    display: none;
    position: absolute;
    max-width: 46vw;
  }
  /* Chip opens away from whichever edge the dot itself is anchored to,
     so it grows toward the middle of the photo instead of off it. */
  .anchor-left .tutorial-dim { left: 0; }
  .anchor-right .tutorial-dim { right: 0; }
  .anchor-top .tutorial-dim { top: 22px; }
  .anchor-bottom .tutorial-dim { bottom: 22px; }
  .tutorial-dim.is-open { display: block; }
}

@media (max-width: 480px) {
  .tutorial-dim { font-size: 0.62rem; padding: 3px 6px; }
}

.spec-line {
  display: flex;
  justify-content: space-between;
  padding: 5px 0;
  border-bottom: 1px dotted var(--steel-600);
  font-size: 0.88rem;
  border-radius: var(--radius-sm);
  transition: background var(--duration-md) var(--ease-default),
              box-shadow var(--duration-md) var(--ease-default);
}
.spec-line .label { color: var(--muted-on-ink); }
.spec-line .value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
/* Briefly framed when jumped to from a red stamp badge — see the
   data-jump-targets click handler in app.js. */
.spec-line--flag {
  background: var(--ko-100);
  box-shadow: 0 0 0 2px var(--ko-500);
  padding-left: 8px;
  padding-right: 8px;
}
/* Persistent (not just on-jump) marker for rows whose value comes from an
   out-of-range input -- replaces the removed green/red legend by putting
   the warning directly on the affected line instead of requiring the
   reader to decode a color key first. */
.spec-line--warn .label { color: var(--ko-500); }
.spec-warn-icon { color: var(--ko-500); vertical-align: -2px; margin-right: 4px; display: inline-flex; }

.stamp-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.stamp {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
}
.stamp.ok { background: var(--ok-100); color: var(--ok-500); border-color: var(--ok-500); }
.stamp.ko { background: var(--ko-100); color: var(--ko-500); border-color: var(--ko-500); }
.tutorial-warning {
  background: var(--ko-100);
  color: var(--ko-500);
  border: 1px solid var(--ko-500);
  border-radius: var(--radius-md, 8px);
  padding: 10px 14px;
  font-size: 0.85rem;
  margin: 0 0 12px;
}
.tutorial-warning a { color: inherit; font-weight: 600; text-decoration: underline; }

/* Steps with more than one photo (see extra_images in tutorial.py) stack
   the extra frames below the primary one -- spacing only kicks in between
   two frames, or after a caption/note, so the very first frame still sits
   flush under the subtitle like before. */
.tutorial-image-frame + .tutorial-image-frame,
.tutorial-image-frame + .tutorial-image-caption,
.tutorial-note + .tutorial-image-frame {
  margin-top: var(--space-4);
}
.tutorial-image-caption {
  margin: 0 0 6px;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted-on-paper);
  text-align: center;
}

/* FlangesLatBendView's "Interieur"/"Exterieur" + "Cotes de controle" rows
   -- these sit in the original's page header, never over a photo (see
   tutorial.py docstring), so this is a plain two-column text block
   instead of an absolutely-positioned overlay. */
.tutorial-header-block {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin: 10px 0 16px;
}
.tutorial-header-block__col {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 160px;
}
.tutorial-header-pair {
  display: flex;
  align-items: center;
  gap: 10px;
}
.tutorial-header-pair__label {
  font-weight: 600;
  color: var(--ink-on-paper);
  font-size: 0.88rem;
}
/* Always-visible on every viewport (unlike .tutorial-dim, which collapses
   to a tap-to-reveal dot on mobile) -- these rows have no photo behind
   them to declutter, so there's nothing to hide. */
.tutorial-header-pair__dim {
  display: inline-flex;
  flex-direction: column;
  background: rgba(255, 255, 255, 0.95);
  border: 1px solid var(--blue-line);
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.3;
}

/* LimonsToolsView's instruction sentence with an embedded measurement
   (Lb_Marge) -- not a positioned chip in source, a plain paragraph. */
.tutorial-note {
  background: var(--paper-200, #f4f4f6);
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-md, 8px);
  padding: 10px 14px;
  font-size: 0.85rem;
  color: var(--ink-on-paper);
  margin: 12px 0 0;
}
.stamp::before { content: "\25CF"; font-size: 0.6rem; }
/* Red stamps rendered as <button> (they jump to the value to correct) —
   reset default button chrome so they look identical to the plain <span>
   ones, just with a pointer cursor and a focus ring. */
button.stamp {
  appearance: none;
  -webkit-appearance: none;
  /* Not "font: inherit" -- that shorthand resets font-family/size/weight
     too, and button.stamp's higher specificity (class+tag vs .stamp's
     class-only) then wins over .stamp's own mono/0.72rem/500, silently
     falling back to the browser's default button font. Repeating the same
     three properties here keeps every stamp identical regardless of
     whether it renders as <span> or <button>. */
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  cursor: pointer;
}
button.stamp:hover { box-shadow: 0 0 0 2px var(--ko-500); }

.stamp-legend {
  margin: var(--space-2) 0 0;
  font-size: 0.76rem;
  color: var(--muted-on-paper);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.stamp-legend .stamp { padding: 2px 8px; font-size: 0.68rem; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 9px 20px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  background: var(--torch-500);
  color: #ffffff;
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 0.88rem;
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  transition: background var(--duration-sm) var(--ease-default),
              box-shadow var(--duration-sm) var(--ease-default),
              border-color var(--duration-sm) var(--ease-default);
  position: relative;
  overflow: hidden;
}
.btn:hover:not(:disabled) { background: var(--torch-600); color: #ffffff; box-shadow: var(--shadow-sm); }
.btn:disabled { background: var(--steel-700); color: var(--muted-on-ink); cursor: not-allowed; box-shadow: none; }
.btn.secondary {
  background: #ffffff;
  color: var(--blue-line);
  border-color: var(--steel-600);
  box-shadow: none;
}
.btn.secondary:hover:not(:disabled) { background: #e0e7ff; border-color: var(--blue-line); color: var(--blue-line); }

.btn-row { display: flex; gap: var(--space-3); margin-top: var(--space-4); flex-wrap: wrap; }

.btn-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--blue-line);
  font-size: inherit;
  font-family: inherit;
  cursor: pointer;
  text-decoration: underline;
  min-height: 44px;
}
.btn-link:hover { color: var(--torch-500); }
.btn-link:disabled { color: var(--muted-on-paper); cursor: not-allowed; text-decoration: none; }

/* "Mes projets" table -- .responsive-table previously had no styling at
   all outside the @media(max-width:768px) card-layout override below, so
   it fell back to bare browser table defaults on desktop (no header
   shading, no row dividers, cramped cells). */
.table-scroll { overflow-x: auto; border: 1px solid var(--steel-600); border-radius: var(--radius-lg); }
table.responsive-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--steel-800);
}
table.responsive-table thead th {
  text-align: left;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted-on-ink);
  background: var(--steel-700);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--steel-600);
}
table.responsive-table tbody td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--steel-600);
  vertical-align: middle;
}
table.responsive-table tbody tr:last-child td { border-bottom: none; }
table.responsive-table tbody tr:hover { background: var(--steel-700); }

.icon-btn-row { display: flex; gap: var(--space-2); align-items: center; justify-content: flex-end; }
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-md);
  background: var(--steel-800);
  color: var(--muted-on-ink);
  cursor: pointer;
  text-decoration: none;
}
.icon-btn:hover:not(:disabled) { border-color: var(--blue-line); color: var(--blue-line); background: #eef2ff; }
.icon-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.icon-btn--danger:hover:not(:disabled) { border-color: #c2483a; color: #c2483a; background: #fdecea; }

form.auth-form {
  max-width: 380px;
  margin: 8vh auto;
  background: var(--steel-800);
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  border: 1px solid var(--steel-600);
  box-shadow: var(--shadow-sm);
}
form.auth-form h2 { color: var(--text-on-ink); font-size: 1.3rem; text-transform: none; }
.auth-form-logo { display: flex; justify-content: center; margin-bottom: var(--space-4); }
.auth-form-logo svg { border-radius: 10px; }
form.auth-form input {
  width: 100%;
  padding: 10px var(--space-3);
  margin-bottom: var(--space-3);
  border: 1.5px solid var(--steel-600);
  border-radius: var(--radius-md);
  background: #ffffff;
  color: var(--text-on-ink);
  font-family: var(--font-body);
  outline: none;
  transition: border-color var(--duration-sm) var(--ease-default),
              box-shadow var(--duration-sm) var(--ease-default);
}
form.auth-form input:focus {
  border-color: var(--blue-line);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, .15);
}

.feature-list { list-style: none; margin: 0 0 var(--space-4); padding: 0; }
.feature-list li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 0;
  font-size: 0.9rem;
  color: var(--text-on-ink);
}
.feature-list li svg { color: var(--torch-500); }

.account-plan-page { max-width: 560px; }
.account-plan-hero {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6);
  margin: var(--space-4) 0 var(--space-6);
  background: var(--paper-050);
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}
.account-plan-hero__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-md);
  background: var(--steel-700);
  color: var(--muted-on-ink);
  flex-shrink: 0;
}
.account-plan-hero--pro {
  border: 2px solid var(--blue-line);
  background: linear-gradient(180deg, rgba(79, 70, 229, .07), transparent 60%), var(--paper-050);
}
.account-plan-hero--pro .account-plan-hero__icon { background: var(--blue-line); color: #ffffff; }
.account-plan-hero__label {
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--muted-lighter);
  font-family: var(--font-display);
}
.account-plan-hero__name { font-family: var(--font-display); font-size: 1.4rem; font-weight: 800; letter-spacing: -0.01em; color: var(--text-on-ink); margin: 2px 0; }
.account-plan-hero__quota { font-size: .82rem; color: var(--muted-on-ink); }

/* Plan cards -- shared by /billing/pricing and the plan-gate modal (both
   render billing/_compare_table.html). Pro states its features as a delta
   ("tout Free, plus...") rather than repeating every row of a spec-sheet
   table -- clearer for a 2-tier plan than a full comparison grid. */
.pricing-page__intro { max-width: 480px; margin: 0 auto var(--space-6); text-align: center; }
.pricing-page__intro p { color: var(--muted-on-ink); font-size: .92rem; }

.plan-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--space-4);
  align-items: stretch;
  max-width: 640px;
  margin: 0 auto;
}
.plan-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--paper-050);
  border: 1px solid var(--steel-600);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-4) var(--space-4);
  box-shadow: var(--shadow-xs);
}
.plan-card--pro {
  border: 2px solid var(--blue-line);
  box-shadow: var(--shadow-md);
  /* Faint drafting-grid wash -- a nod to the technical/elevation drawings
     this app actually produces, instead of a generic marketing gradient. */
  background:
    repeating-linear-gradient(135deg, rgba(79,70,229,.05) 0 1px, transparent 1px 22px),
    repeating-linear-gradient(45deg, rgba(79,70,229,.05) 0 1px, transparent 1px 22px),
    linear-gradient(180deg, rgba(79,70,229,.07), transparent 55%),
    var(--paper-050);
}
.plan-card__badge {
  position: absolute;
  top: -11px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 12px;
  border-radius: var(--radius-pill);
  background: var(--blue-line);
  color: #fff;
  font-size: 0.64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  box-shadow: var(--shadow-sm);
  white-space: nowrap;
}
.plan-card__badge svg { color: #fde68a; }
.plan-card__name {
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-on-ink);
}
.plan-card--pro .plan-card__name { color: var(--blue-line); }
.plan-card__price { display: flex; align-items: baseline; gap: 6px; margin: 6px 0 var(--space-2); }
.plan-card__amount { font-family: var(--font-display); font-size: 2.1rem; font-weight: 800; letter-spacing: -0.02em; color: var(--text-on-ink); line-height: 1; }
.plan-card__tagline { font-size: 0.8rem; color: var(--muted-on-ink); margin: 0 0 var(--space-3); font-weight: 600; }

.plan-card__features { list-style: none; margin: 0 0 var(--space-4); padding: 0; flex: 1; }
.plan-card__features li { display: flex; align-items: flex-start; gap: var(--space-2); padding: 5px 0; font-size: 0.86rem; }
.plan-card__features li.is-yes svg { color: var(--ok-500); flex-shrink: 0; margin-top: 2px; }
.plan-card__features li.is-no { color: var(--muted-on-ink); opacity: .6; }
.plan-card__dash { display: inline-block; width: 15px; flex-shrink: 0; text-align: center; color: var(--muted-lighter); }

.plan-card__current {
  display: block;
  padding: 9px 20px;
  border-radius: var(--radius-pill);
  background: var(--steel-700);
  color: var(--muted-on-ink);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
}
.plan-card form { margin: 0; }
.plan-card .btn { width: 100%; }

/* FAB, used on mobile for the primary action */
.fab {
  position: fixed;
  right: max(var(--space-4), env(safe-area-inset-right));
  bottom: calc(var(--shell-bottomnav-height) + var(--space-4) + env(safe-area-inset-bottom, 0px));
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--torch-500);
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
  border: none;
  z-index: 20;
}

/* Results page mobile FAB: a single export-icon FAB pops a 3-item speed
   dial (Tutoriel, Export PDF, Export DXF) above it. Desktop keeps the
   plain Actions button row (see .btn-row, hidden via .actions-section) --
   the two are mutually exclusive via the media queries at the bottom of
   this file, not a duplicate UI shown twice. */
.results-fab { display: none; }
.fab-speed-dial {
  position: fixed;
  right: max(var(--space-4), env(safe-area-inset-right));
  bottom: calc(var(--shell-bottomnav-height) + var(--space-4) + env(safe-area-inset-bottom, 0px) + 68px);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
  z-index: 20;
}
.fab-speed-dial .btn {
  box-shadow: var(--shadow-md);
  white-space: nowrap;
  text-decoration: none;
}

.bottom-sheet-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 30; }
.bottom-sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  background: var(--steel-800);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: var(--space-6);
  padding-bottom: max(env(safe-area-inset-bottom, 0px), var(--space-6));
  z-index: 31;
  max-height: 80dvh;
  overflow-y: auto;
  border-top: 1px solid var(--steel-600);
}

.plan-gate-sheet {
  max-width: 680px;
  margin: 0 auto;
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
@media (min-width: 640px) {
  .plan-gate-sheet {
    bottom: var(--space-6);
    border-radius: var(--radius-lg);
    border: 1px solid var(--steel-600);
    box-shadow: var(--shadow-lg);
  }
}

.plan-gate-sheet__header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  background: var(--ink-on-paper);
  color: #ffffff;
  padding: var(--space-6);
  flex-shrink: 0;
}
.plan-gate-sheet__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, .12);
  color: #fde68a;
}
.plan-gate-sheet__text { flex: 1; min-width: 0; }
.plan-gate-sheet__text h2 { margin: 0 0 4px; font-size: 1.05rem; color: #ffffff; }
.plan-gate-sheet__text p { margin: 0; font-size: .85rem; color: rgba(255, 255, 255, .72); }
.plan-gate-sheet__close {
  background: rgba(255, 255, 255, .1);
  border: none;
  color: #ffffff;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  cursor: pointer;
  font-size: .85rem;
  line-height: 1;
  flex-shrink: 0;
  transition: background var(--duration-sm) var(--ease-default);
}
.plan-gate-sheet__close:hover { background: rgba(255, 255, 255, .22); }
.plan-gate-sheet__body { padding: var(--space-6); overflow-y: auto; }

.lock-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--warn-100);
  color: var(--warn-500);
  cursor: pointer;
  flex-shrink: 0;
  vertical-align: middle;
  transition: background var(--duration-sm) var(--ease-default);
}
.lock-badge:hover { background: #fde68a; }

/* ---- Responsive: single breakpoint at 768px ---- */
@media (max-width: 768px) {
  .app-shell {
    grid-template-columns: 1fr;
    grid-template-rows: var(--shell-topbar-height) 1fr var(--shell-bottomnav-height);
    grid-template-areas:
      "topbar"
      "main"
      "bottomnav";
  }
  .drawer-toggle { display: inline-flex; align-items: center; justify-content: center; }

  /* Icon+text auth buttons are the right call on desktop but too wide for
     a 360-400px topbar alongside the brand and hamburger — collapse to
     icon-only here, same treatment as the bottom-nav. */
  .topbar .btn span, .topbar form .btn span { display: none; }
  .topbar .btn { padding: 9px 12px; }

  .sidebar { display: none; }
  .sidebar.drawer-open {
    display: block;
    position: fixed;
    inset: var(--shell-topbar-height) 0 0 0;
    z-index: 25;
  }
  /* Stack label above input instead of cramming a fixed-width input
     next to the label — the fixed 132px input doesn't shrink below its
     intrinsic min-width and was pushing the whole row past the viewport. */
  .field-row { flex-wrap: wrap; row-gap: 6px; }
  .field-row label { flex-basis: 100%; }
  .field-row__label-group { flex-basis: 100%; }
  .field-row input, .field-row select { width: 100%; }

  table.responsive-table,
  table.responsive-table thead,
  table.responsive-table tbody,
  table.responsive-table tr,
  table.responsive-table td {
    display: block;
    width: 100%;
  }
  table.responsive-table thead { display: none; }
  table.responsive-table tr {
    border: 1px solid var(--steel-600);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-3);
    padding: var(--space-2);
  }
  table.responsive-table .icon-btn-row { display: flex; justify-content: flex-start; }

  /* Resultats page: the plain Actions button row gives way to the FAB
     cluster below it (same actions, reachable without scrolling). */
  .actions-section { display: none; }
  .results-fab { display: block; }

  .bottom-nav {
    grid-area: bottomnav;
    display: flex;
    justify-content: space-around;
    align-items: center;
    border-top: 1px solid var(--steel-600);
    background: var(--steel-800);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .bottom-nav a {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    color: var(--muted-on-ink);
    text-decoration: none;
    font-family: var(--font-display);
    font-size: 0.72rem;
    transition: color var(--duration-sm) var(--ease-default);
  }
  .bottom-nav a.active { color: var(--blue-line); }
  .bottom-nav .disabled {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    color: var(--muted-lighter);
    opacity: .5;
    cursor: not-allowed;
  }
}

@media (min-width: 769px) {
  .bottom-nav { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}
