/* Datum FLO — Factory Logistics & Operations. Forestdale's shop floor app.
 *
 * Designed for a phone or tablet held in one gloved hand in a fabrication shop, which
 * drives every choice here: large hit targets (44px is the accessibility floor and
 * everything tappable clears it), high contrast for a bright bay, dark ground so the
 * screen is not a torch, and no hover states because there is no pointer.
 *
 * Deliberately NOT sharing Datum's stylesheet. Datum is a dense desktop board tuned
 * for information per square inch; this is the opposite problem. One stylesheet
 * serving both would end up serving neither.
 */

:root {
  --ink:      #eef2f7;
  --ink-dim:  #9aa7b8;
  --ground:   #12161c;
  --panel:    #1b2129;
  --panel-2:  #232b35;
  --line:     #2f3945;
  --accent:   #4da3ff;
  --ok:       #35c07a;
  --warn:     #f0a63c;
  --bad:      #ff6b6b;
  --tap:      48px;          /* every interactive thing clears the 44px floor */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 16px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* 16px minimum on inputs, or iOS zooms the whole page when one is focused. */
  -webkit-text-size-adjust: 100%;
}

.wrap {
  max-width: 44rem;
  margin: 0 auto;
  padding: 1rem 1rem 4rem;
  /* Reach under the rounded corners, then put the padding back. */
  padding-left:  max(1rem, env(safe-area-inset-left));
  padding-right: max(1rem, env(safe-area-inset-right));
  padding-bottom: max(4rem, env(safe-area-inset-bottom));
}

h1 { font-size: 1.5rem; margin: .4rem 0 .2rem; }
h2 { font-size: 1.05rem; margin: 1.6rem 0 .5rem; color: var(--ink-dim);
     text-transform: uppercase; letter-spacing: .06em; }
.sub { color: var(--ink-dim); margin: 0 0 1rem; }
.hint { color: var(--ink-dim); font-size: .88rem; margin: .3rem 0 .9rem; }
.fineprint { color: var(--ink-dim); font-size: .82rem; border-left: 2px solid var(--line);
             padding-left: .6rem; margin: .8rem 0 1rem; }
.count { color: var(--ink-dim); margin: .2rem 0 .6rem; }
.empty { background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
         padding: 1.2rem; color: var(--ink-dim); }

/* --- top bar ------------------------------------------------------------- */
.bar {
  display: flex; align-items: center; gap: .75rem;
  background: var(--panel); border-bottom: 1px solid var(--line);
  padding: .55rem 1rem;
  padding-top: max(.55rem, env(safe-area-inset-top));
  position: sticky; top: 0; z-index: 5;
}
.bar-home { color: var(--ink); font-weight: 650; text-decoration: none;
            flex: 0 0 auto; }

/* Factory · Logistics · Operations. Scrolls horizontally rather than wrapping: a
   wrapped tab bar doubles the height of a sticky header on a phone, and the header is
   already competing with the notch. */
.tabs {
  display: flex; gap: .15rem; flex: 1 1 auto; min-width: 0;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  display: flex; align-items: center; white-space: nowrap;
  padding: .35rem .6rem; border-radius: 8px; text-decoration: none;
  color: var(--ink-dim); font-size: .9rem; min-height: 2.1rem;
}
.tab-on  { color: var(--ink); background: var(--panel-2);
           box-shadow: inset 0 -2px 0 var(--accent); }
/* Greyed, not hidden: "not built yet" and "not enabled for you" are different facts,
   and a missing tab states neither. Not a link, so it cannot be tapped hopefully. */
.tab-off { color: var(--faint, #66707d); opacity: .55; cursor: default; }

.bar-who  { color: var(--ink-dim); margin-left: auto; font-size: .9rem;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
            max-width: 28%; flex: 0 1 auto; }
.bar-out  { background: none; border: 1px solid var(--line); color: var(--ink-dim);
            border-radius: 8px; padding: .45rem .7rem; font-size: .85rem; }

/* --- flashes ------------------------------------------------------------- */
.flash { border-radius: 10px; padding: .8rem 1rem; margin: .6rem 0; font-weight: 550; }
.flash-ok    { background: #12301f; border: 1px solid var(--ok);   color: #b6f0cf; }
.flash-error { background: #331717; border: 1px solid var(--bad);  color: #ffc9c9; }

/* --- login --------------------------------------------------------------- */
.login { max-width: 22rem; margin: 3rem auto; }
.login h1 { font-size: 1.8rem; margin-bottom: 0; }

label, .field { display: block; margin: 1rem 0 .35rem; font-weight: 550; }
input[type=text], input[type=email], input[type=password], input[type=search],
input[type=number], input[type=file] {
  width: 100%; min-height: var(--tap);
  background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: 10px;
  padding: .7rem .8rem;
  font-size: 1rem;              /* never below 16px — see the iOS note above */
}
input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

button, .big {
  min-height: var(--tap);
  border-radius: 10px; border: 1px solid transparent;
  font-size: 1rem; font-weight: 600;
  background: var(--panel-2); color: var(--ink);
  padding: .7rem 1rem;
}
.big { display: block; width: 100%; margin-top: 1.4rem;
       background: var(--accent); color: #06121f; font-size: 1.1rem; }

/* --- home tiles ---------------------------------------------------------- */
.tiles { display: grid; gap: .8rem; margin-top: 1rem; }
.tile {
  display: block; text-decoration: none; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: 1rem; min-height: 5rem;
}
.tile-label { display: block; font-size: 1.15rem; font-weight: 650; }
.tile-blurb { display: block; color: var(--ink-dim); font-size: .9rem; margin-top: .2rem; }
.tile-off   { opacity: .5; }
.tile-tag   { display: inline-block; margin-top: .5rem; font-size: .75rem;
              text-transform: uppercase; letter-spacing: .05em; color: var(--warn); }

/* --- search + queue ------------------------------------------------------ */
.search { display: flex; gap: .5rem; align-items: stretch; margin: .6rem 0; }
.search input { flex: 1; }
.search button { flex: 0 0 auto; }
.clear { align-self: center; color: var(--ink-dim); font-size: .9rem; }

.lines, .recent, .capture { list-style: none; margin: 0; padding: 0; }
.lines li { margin-bottom: .6rem; }
.lines a {
  display: block; text-decoration: none; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: .85rem;
}
.line-top { display: flex; justify-content: space-between; gap: .6rem; }
.line-mid { display: block; color: var(--ink-dim); font-size: .92rem; margin: .25rem 0; }
.line-bot { display: flex; flex-wrap: wrap; gap: .5rem; font-size: .85rem; }
.vendor { color: var(--ink-dim); text-align: right; }
.qty    { color: var(--ink); }
.due    { color: var(--ink-dim); }
.overdue{ color: var(--warn); font-weight: 600; }

/* Floor confirmation is WEAKER evidence than a JobBoss receipt — nothing posted,
   nothing inspected, possibly uncounted. It gets its own colour so the two can never
   be read as the same fact. */
.seen { color: var(--accent); }

/* --- lookup: search, part detail, work-order detail ----------------------- */

.crumb { margin: 0 0 .4rem; }
.crumb a { color: var(--ink-dim); text-decoration: none; }
.desc { color: var(--ink-dim); margin: 0 0 1rem; }

/* Search hits. A whole card is the tap target, not the words in it — a gloved
   fingertip is nearer 15mm than the 44px floor a stylus assumes. */
.hits { list-style: none; margin: 0; padding: 0; }
.hits li { margin-bottom: .5rem; }
.hits a, .hit-flat {
  display: block; text-decoration: none; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: .8rem; min-height: var(--tap);
}
/* A result with no detail page is deliberately NOT styled as tappable: no border
   highlight, dimmer ground. Nothing here should look like a link that does nothing. */
.hit-flat { background: transparent; border-style: dashed; }
.hit-title { display: block; font-size: 1.05rem; }
.hit-sub   { display: block; color: var(--ink-dim); font-size: .9rem; margin-top: .15rem;
             white-space: pre-line; }
.hit-meta  { display: block; color: var(--ink-dim); font-size: .8rem; margin-top: .3rem; }

/* Drawings. The file name is the tap target and it wraps — customer drawing numbers
   run long and truncating one hides the revision suffix, which is the part that
   matters most on a shop floor. */
.docs { list-style: none; margin: 0; padding: 0; }
.docs li { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
           padding: .8rem; margin-bottom: .5rem; }
.docs a { color: var(--accent); text-decoration: none; word-break: break-word; }
.doc-meta { display: block; color: var(--ink-dim); font-size: .82rem; margin-top: .35rem; }

/* Provenance, never blended: a vetted JobBoss revision and an engineering-only file
   are different claims, and showing the second AS RELEASED would put a stale revision
   on the floor. */
.prov { display: inline-block; padding: 0 .4rem; border-radius: 4px; font-size: .75rem;
        border: 1px solid currentColor; }
.prov-ok   { color: var(--ok); }
.prov-warn { color: var(--warn); }
.prov-dim  { color: var(--ink-dim); }

.facts { display: grid; grid-template-columns: auto 1fr; gap: .3rem .9rem;
         margin: 0 0 1rem; }
.facts dt { color: var(--ink-dim); }
.facts dd { margin: 0; }

.warn { color: var(--warn); background: var(--panel); border: 1px solid var(--warn);
        border-radius: 10px; padding: .8rem; }
.warn-inline { color: var(--warn); }
.ok { color: var(--ok); }

/* 3D. A fixed height rather than an aspect ratio: three.js sizes its canvas from the
   container's clientHeight at construction, so a container that is 0px tall when the
   module runs renders nothing at all and looks like a failed load. */
.viewer3d { height: 60vh; min-height: 260px; background: #1b1f2a;
            border: 1px solid var(--line); border-radius: 12px; touch-action: none; }
.viewbar { display: flex; gap: .4rem; margin-top: .5rem; flex-wrap: wrap; }
.viewbar button { flex: 1 1 auto; min-height: var(--tap); }
.viewer-error { padding: 1rem; color: var(--ink-dim); }

/* Work-order material rows. Not `.lines` — that card lives on `.lines a`. */
.matlines { list-style: none; margin: 0; padding: 0; }
.matlines li { background: var(--panel); border: 1px solid var(--line);
               border-radius: 12px; padding: .8rem; margin-bottom: .5rem; }
.line-part  { color: var(--accent); text-decoration: none; font-size: 1.02rem; }
/* A sub-job is a COMPONENT of the line above it, not another thing the customer
   bought. Indented so the tree structure is visible without drawing one. */
.matlines li.subjob { margin-left: 1rem; border-left-width: 3px;
                      border-left-color: var(--panel-2); }

/* The lines inside a purchase order card. Nested, so a slip covering several lines of
   one PO reads as one delivery rather than several. */
/* A DXF outline, rendered server-side, standing in for the 3D viewer on a flat.
 *
 * White ground because ezdxf draws in #212830 — near-black — which on this app's dark
 * panel would be invisible: the same "renders but shows nothing" failure as the
 * kilometre-scale 3D model.
 *
 * ⚠️ NO object-fit AND NO max-height. The first version had width:100% with both, and
 * the SVG's intrinsic size is ~10mm (about 40px) with a 1,000,000-unit viewBox — the
 * combination scaled it unpredictably, which is what "rendering weirdly" was. Given a
 * viewBox the browser already knows the aspect ratio, so width plus height:auto is
 * enough and is the one thing that cannot fight it. Height is bounded by aspect, not
 * by a cap that would letterbox a long panel. */
.outline {
  display: block; width: 100%; height: auto;
  background: #fff; border: 1px solid var(--line); border-radius: 8px;
  margin: .3rem 0 .2rem; padding: .5rem;
}

/* The BOM tree. Nesting is shown by an indent and a rule rather than by connector
   lines: on a phone the indent is the only thing that survives a narrow column. */
.bomtree { list-style: none; margin: .6rem 0 0 .8rem; padding: 0 0 0 .7rem;
           border-left: 2px solid var(--line); }
.bomtree li { background: var(--panel-2); border: 1px solid var(--line);
              border-radius: 10px; padding: .7rem; margin-bottom: .45rem; }

/* Drawings behind a tap, not printed inline. A weldment with eight flats was eight
   prints deep before its quantities — the tree is for reading structure. */
/* The root of the tree is the part you asked for: named, not linked to itself. */
.line-root { color: var(--ink); }
.bomtree-root > li { background: var(--panel); }

/* Smaller than the tree's own text: a file name is a label you scan, not prose. */
.docs-sm { font-size: .82rem; }
.docs-sm li { padding: .3rem 0; }

/* The engineering-file preference. Quiet — it is a view setting, not a warning. */
.engtoggle { color: var(--ink-dim); font-size: .85rem; margin: .2rem 0 .6rem; }
.engtoggle a { color: var(--accent); }

.docdrop { margin-top: .45rem; }
.docdrop > summary {
  cursor: pointer; color: var(--accent); font-size: .88rem;
  min-height: 2rem; display: flex; align-items: center;
}
.docdrop[open] > summary { margin-bottom: .2rem; }

.polines { list-style: none; margin: .5rem 0 0; padding: 0; }
.polines li { padding: .4rem 0; border-top: 1px solid var(--line); font-size: .9rem; }
.polines a { color: var(--accent); text-decoration: none; }
.polines .qty { float: right; color: var(--ink); }
.polines .line-meta { display: block; clear: both; }

/* The action offered where the question is asked. Full width and past the tap floor:
   this is pressed by someone standing at a skid, not sitting at a desk. */
.pobtn {
  display: block; margin-top: .7rem; min-height: var(--tap);
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: #08121d; font-weight: 600;
  border-radius: 10px; text-decoration: none; padding: .6rem;
}
.line-stage { color: var(--ink-dim); font-size: .85rem; text-align: right; }
.line-desc  { color: var(--ink-dim); font-size: .9rem; margin-top: .2rem; }
.line-meta  { color: var(--ink-dim); font-size: .8rem; margin-top: .3rem; }
/* The floor's own claim, indented behind a rule so it reads as a SECOND source rather
   than more detail about the JobBoss stage above it. They can disagree, and that
   disagreement is the whole point of capturing on the floor. */
.line-floor { margin-top: .45rem; padding-left: .55rem;
              border-left: 2px solid var(--line); font-size: .85rem; }
.pill { display: inline-block; background: var(--panel-2); border: 1px solid var(--line);
        border-radius: 999px; padding: .05rem .5rem; margin-left: .3rem;
        font-size: .78rem; color: var(--ink-dim); }

.recent li { border-bottom: 1px solid var(--line); padding: .55rem 0;
             font-size: .9rem; color: var(--ink-dim); }
.recent strong { color: var(--ink); }
.when { display: block; font-size: .78rem; }
.tag  { display: inline-block; border: 1px solid var(--line); border-radius: 6px;
        padding: 0 .35rem; font-size: .72rem; text-transform: uppercase; }
.tag-counted { border-color: var(--ok); color: var(--ok); }

/* --- capture: the photo control ------------------------------------------ */
/* The native file input is a small grey "Choose File" — the wrong target for a
   gloved thumb, and it gives no clue a camera is involved. So the input is hidden
   and a <label for> is styled as the button; clicking a label activates its input,
   which is plain HTML and needs no JavaScript to work.
   ⚠️ `visually-hidden` rather than display:none — a display:none input is skipped by
   some browsers' form handling and unreachable to a screen reader, and this way the
   label still drives it. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.shotbtn {
  display: flex; align-items: center; justify-content: center; gap: .7rem;
  min-height: 5.5rem;                     /* far above the 48px floor: this is the
                                             one control everyone touches, outdoors,
                                             in gloves, often in a hurry */
  margin: .4rem 0 0;
  border: 2px dashed var(--accent); border-radius: 14px;
  background: var(--panel);
  color: var(--accent); font-size: 1.15rem; font-weight: 650;
  cursor: pointer;
}
.shotbtn:active { background: var(--panel-2); }
.shotbtn-icon { font-size: 1.7rem; line-height: 1; }

.shotalt {
  display: block; margin: .6rem 0 0; text-align: center;
  color: var(--ink-dim); font-size: .9rem; font-weight: 400;
  text-decoration: underline; cursor: pointer; min-height: var(--tap);
  line-height: var(--tap);
}

/* Nothing on screen changes when the camera closes, so a taken photo and a cancelled
   one look identical until submit. This is the difference. */
.chosen {
  margin: .5rem 0 0; padding: .55rem .7rem;
  background: var(--panel-2); border: 1px solid var(--ok); border-radius: 8px;
  color: var(--ok); font-size: .9rem; word-break: break-all;
}

/* The same control used as a link on the queue and scan screens. */
a.shotbtn { text-decoration: none; }
/* Once a scan has supplied the photo, retaking it is optional — so the button stops
   competing with the confirm button for attention. */
.shotbtn-quiet {
  min-height: var(--tap); border-style: solid; border-color: var(--line);
  color: var(--ink-dim); font-size: .95rem; font-weight: 500;
}
.shotbtn-quiet .shotbtn-icon { font-size: 1.1rem; }

/* --- what the scan read --------------------------------------------------- */
/* 🔑 Coloured BY CONFIDENCE, and confidence comes from corroboration (is the PO open,
   does the vendor match, are the parts outstanding) rather than from the model saying
   it is sure. A low-confidence match still gets shown — it is a lead worth checking,
   not an answer — but it must not look like a high one. */
.matched {
  border-radius: 12px; padding: .85rem 1rem; margin: .2rem 0 1rem;
  border: 1px solid var(--line); background: var(--panel);
  border-left-width: 5px;
}
.matched-high   { border-left-color: var(--ok); }
.matched-medium { border-left-color: var(--accent); }
.matched-low    { border-left-color: var(--warn); }
.matched-head { margin: 0 0 .3rem; font-size: .82rem; text-transform: uppercase;
                letter-spacing: .05em; color: var(--ink-dim); }
.matched-body { margin: 0 0 .4rem; }
.matched-why  { margin: .3rem 0 .5rem; padding-left: 1.1rem; font-size: .86rem;
                color: var(--ink-dim); }
.matched-why li { margin: .15rem 0; }
.matched-alt  { margin: 0; font-size: .88rem; color: var(--ink-dim); }

/* A quantity the slip actually stated, as opposed to the outstanding default. The
   two are different claims and the person is checking them against paper. */
.capture-qty-read { border-color: var(--accent); }

/* The slip stated a quantity and we refused to convert it. Left EMPTY on purpose —
   an outstanding-quantity default here is a claim the app has no evidence for. */
.capture-qty-blocked { border-color: var(--warn); background: var(--panel-2); }

/* 🔴 The line is not ordered in pieces. Steel is bought by weight and delivered by
   the sheet, so this is where a piece count would silently go into a pounds column. */
.unitwarn {
  display: block; margin-top: .3rem; font-size: .8rem; font-weight: 600;
  color: var(--warn);
}

/* Already received. The one no-match branch where the right action is to STOP, so it
   is coloured like a stop rather than like an apology. */
.warn-head { color: var(--warn); }

.readout { background: var(--panel); border: 1px solid var(--line);
           border-radius: 12px; padding: .9rem 1rem; margin-bottom: .8rem; }
.readout h2 { margin-top: 0; }
.readout dl { margin: 0; display: grid; grid-template-columns: auto 1fr;
              gap: .35rem .8rem; }
.readout dt { color: var(--ink-dim); font-size: .85rem; }
.readout dd { margin: 0; word-break: break-word; }
.why p { margin: .3rem 0; color: var(--ink-dim); font-size: .9rem; }

/* --- capture: the rest ---------------------------------------------------- */
.back { color: var(--ink-dim); text-decoration: none; font-size: .9rem; }
.toggle { display: flex; align-items: center; gap: .65rem; min-height: var(--tap);
          margin-top: 1.1rem; font-weight: 550; }
.toggle input { width: 1.4rem; height: 1.4rem; accent-color: var(--ok); }

.capture li {
  display: flex; gap: .8rem; align-items: center;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: .8rem; margin-bottom: .6rem;
}
.capture-part { flex: 1; min-width: 0; }
.capture-part span { display: block; color: var(--ink-dim); font-size: .88rem; }
.capture-part .meta { font-size: .8rem; margin-top: .2rem; }
.capture-qty { flex: 0 0 6.5rem; text-align: right; font-size: 1.15rem;
               font-variant-numeric: tabular-nums; }

.denied { text-align: center; margin-top: 2.5rem; }

/* A device left on a bright bench in daylight mode should still be readable. */
@media (prefers-color-scheme: light) {
  :root {
    --ink: #10151b; --ink-dim: #56637a; --ground: #f4f6fa;
    --panel: #ffffff; --panel-2: #ffffff; --line: #d3dae4;
    --accent: #0b62c4;
  }
  .big { color: #ffffff; }
  .flash-ok    { background: #e7f8ee; color: #146b3f; }
  .flash-error { background: #fdeaea; color: #8d1f1f; }
}
