/* Quill Stock
   Built for one thumb, in a warehouse, on a phone. Big targets, high
   contrast, nothing small enough to need aiming.

   Colours and type come from the Quill brand system in
   /root/Quill-Reference/dev/assets/css/site.css. This app is LIGHT, matching
   the main site. The only dark surface is the camera viewfinder - that one is
   a necessity, not a style choice, and it uses --panel-dark from the same
   system so it reads as the brand's dark panel rather than a different app. */

@font-face {
  font-family: "Helvetica Now";
  src: url("../fonts/HelveticaNowDisplay-Regular.otf") format("opentype");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Helvetica Now";
  src: url("../fonts/HelveticaNowDisplay-Medium.otf") format("opentype");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Helvetica Now";
  src: url("../fonts/HelveticaNowDisplay-Bold.otf") format("opentype");
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* straight from the brand system */
  --bg:          #f7f7f7;
  --bg-alt:      #f5f5f3;
  --ink:         #2e2e2e;
  --dim:         rgba(23,23,23,.55);
  --dimmer:      rgba(23,23,23,.35);
  --line:        rgba(23,23,23,.12);
  --line-strong: rgba(23,23,23,.2);

  --panel-dark:      #171717;
  --panel-dark-ink:  #f5f5f3;
  --panel-dark-line: rgba(255,255,255,.14);
  --panel-dark-dim:  rgba(245,245,243,.58);

  /* derived for this app's controls - the brand system has no keypad */
  --surface:   #ffffff;
  --surface-2: #ececea;
  --danger:    #b3261e;
  --ok:        #2e7d32;

  --ease: cubic-bezier(.23, 1, .32, 1);
  --tap: 64px;              /* nothing interactive is smaller than this */
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* No focus indication anywhere - native or custom.
   Outline only: blanket-killing box-shadow here would also wipe the inset
   shadow that marks a *selected* pill, which is a different thing. */
*:focus,
*:focus-visible,
*:focus-within { outline: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  /* Installed as a PWA there is no browser chrome to absorb overflow, so any
     layout taller than the viewport turns into a scrolling page. Screens size
     themselves to the viewport instead of relying on fixed pixel sums. */
  overflow: hidden;
  overscroll-behavior: none;
  background: var(--bg);
  color: var(--ink);
  /* Helvetica Now carries no Thai glyphs, so Noto Sans Thai sits behind it -
     Latin renders in the brand face, Thai falls through to a face that has
     the characters instead of showing tofu. */
  font-family: "Helvetica Now", "Noto Sans Thai", -apple-system,
               BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  font-size: 17px;
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------- login

   LAYOUT MODEL - read this before changing any number here.

   Every element on this screen is a FIXED size. Nothing scales with the
   viewport. An earlier version sized the wordmark, title, hint, gaps and
   message slot with vh-based clamp(), which meant any change to the container
   height resized all of them at once - so opening the keyboard, or showing an
   error, made the whole screen move.

   Exactly one element is flexible: the keypad. It absorbs whatever height is
   left over. Because every one of its siblings is fixed, "left over" is a
   constant for a given device, so the keys are the same size on every step and
   never change during use.

   Budget on the shortest phone we support (568px tall):
     padding        32
     brand + sub    57
     head           73
     dots           46
     message slot   40
     button         76
     language bar   52
     ------------------
     fixed total   376   ->  192 left for the keypad -> ~42px keys
   On any modern phone (>=667) the keys land between 60 and 96px.            */

.auth {
  height: 100dvh;               /* fallback if the script has not run yet */
  height: var(--vh, 100dvh);    /* measured - see the script in base.html */
  display: flex;
  flex-direction: column;
  max-width: 460px;
  margin: 0 auto;
  overflow: hidden;
  /* safe-area lives here, not on body - on body it adds to a full-height
     child and pushes the page taller than the screen */
  padding:
    calc(16px + env(safe-area-inset-top))
    calc(20px + env(safe-area-inset-right))
    calc(16px + env(safe-area-inset-bottom))
    calc(20px + env(safe-area-inset-left));
}

/* The Quill wordmark is always flat - never the gold bulb, never a gradient,
   on any background. Both SVGs in static/img are pre-flattened; this filter is
   the belt-and-braces so a future file swap cannot reintroduce colour. */
.auth__brand {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  margin-bottom: 6px;
}
.auth__brand img {
  height: 30px;
  width: auto;
  filter: brightness(0);      /* flat black on the light background */
}

.auth__sub {
  flex: 0 0 auto;
  font-size: 12px;
  line-height: 15px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--dim);
  text-align: center;
  margin-bottom: 6px;
}

.auth__head {
  flex: 0 0 auto;
  padding: 14px 0 0;
  text-align: center;
}

.auth__title { font-size: 24px; line-height: 29px; font-weight: 650; margin: 0 0 8px; }
.auth__hint  { font-size: 15px; line-height: 22px; color: var(--dim); margin: 0; }

/* min-height:0 is what lets a flex child actually shrink instead of forcing
   the column taller than its parent */
.auth__body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.auth__foot { flex: 0 0 auto; }

/* The action button belongs to the form, not to the bottom of the screen.
   Pinned to the bottom it reads as a separate block with a void between. */
.auth__body > .btn { flex: 0 0 auto; margin-top: 16px; }

/* is-centered is set ONLY while the username step is showing, and removed the
   moment the PIN step opens. It must never carry layout that the PIN step
   depends on. */
.auth__body.is-centered { justify-content: center; }

/* While the keyboard is up the usable area is roughly half the screen, so a
   vertically centred group sits behind it. Top-align for as long as the field
   is focused, then centre again. The PIN step never gets this - it has no
   text input and never raises a keyboard. */
.auth__body.is-centered.is-keyboard { justify-content: flex-start; }

/* steps swap in place */
.step { display: none; }
.step.is-active { display: flex; flex-direction: column; min-height: 0; }
.auth__body:not(.is-centered) .step.is-active { flex: 1 1 auto; }

/* ---------------------------------------------------------------- fields */

.field {
  width: 100%;
  height: var(--tap);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  color: var(--ink);
  font-size: 20px;
  font-family: inherit;
  padding: 0 18px;
  outline: none;
}
.field::placeholder { color: var(--dimmer); }

/* ---------------------------------------------------------------- buttons */

.btn {
  width: 100%;
  height: var(--tap);
  border: 0;
  border-radius: 14px;
  font-size: 19px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  background: var(--panel-dark);
  color: var(--panel-dark-ink);
  transition: opacity .12s var(--ease);
}
.btn:disabled { opacity: .25; cursor: default; }
.btn:active:not(:disabled) { opacity: .8; }

/* Not a fourth style - this is the chip at button size, for dialog action rows
   where a small chip next to a full-height primary button would look wrong.
   Same border, same fill, same ink. */
.btn--ghost {
  background: var(--bg);
  color: var(--ink);
  border: 1px solid var(--line-strong);
}

/* ---------------------------------------------------------------- pin */

.pin-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 14px;
  margin: 0;
  height: 46px;          /* fixed - sits above the keypad */
  flex: 0 0 auto;
}
.pin-dot {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1.5px solid var(--line-strong);
  transition: transform .1s var(--ease), background .1s;
}
.pin-dot.is-filled {
  background: var(--ink);
  border-color: var(--ink);
  transform: scale(1.08);
}

/* Takes the height left over rather than claiming a fixed amount, but every
   sibling above it is a fixed height, so "left over" is a constant for a given
   device. Keys are the same size on every screen of the flow and do not move
   when an error appears.
   The 420px cap stops keys stretching into slabs on a very tall phone. */
.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin-top: auto;
  flex: 1 1 auto;
  min-height: 0;
  max-height: 420px;
}
.key {
  height: 100%;
  min-height: 40px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--surface);
  color: var(--ink);
  font-size: 26px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background .1s;
}
.key:active { background: var(--surface-2); }
.key--blank { background: transparent; border-color: transparent; cursor: default; }
.key--wide  { font-size: 17px; font-weight: 600; }

/* ---------------------------------------------------------------- misc */

.msg {
  min-height: 40px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 15px;
  line-height: 1.4;
  padding: 4px;
  color: var(--danger);
}

/* The two login steps have DIFFERENT layout needs and are styled separately on
   purpose. Sign in is a centred group; the PIN screen is a fixed grid anchored
   to the bottom. Do not write shared rules across them - a tweak made for one
   previously leaked into the other and resized the keypad mid-entry. */

/* Both steps reserve the message slot permanently, at the same fixed height.
   On Sign in it stops the button jumping; on the PIN step it sits directly
   above the keypad, so any change here would resize every key. */
#step-user .msg,
#step-pin  .msg {
  height: 40px;
  min-height: 0;
  padding: 0;
}

.langbar {
  display: flex;
  justify-content: center;
  gap: 8px;
  padding-top: clamp(8px, 1.6vh, 14px);
}
.langbar a {
  color: var(--dim);
  text-decoration: none;
  font-size: 14px;
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: background .12s var(--ease), color .12s var(--ease);
}
/* Selected, not actionable. Solid black is reserved for the primary button -
   using it here made a language choice look like a second call to action.
   An outlined pill on the surface colour reads as "chosen" without competing. */
.langbar a.is-on {
  color: var(--ink);
  background: var(--surface);
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);   /* 2px weight, no layout shift */
  font-weight: 500;
}

.placeholder {
  height: 100dvh;
  height: var(--vh, 100dvh);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  text-align: center;
  padding: 30px;
}
.placeholder h1 { font-size: 24px; margin: 0; }
.placeholder p  { color: var(--dim); margin: 0; }
.placeholder form { width: 100%; max-width: 300px; }

/* ---------------------------------------------------------------- shell
   Pages with a list. body stays overflow:hidden so the PWA never rubber-bands
   the whole app; scrolling happens inside .shell__scroll instead. */

.shell {
  height: 100dvh;
  height: var(--vh, 100dvh);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(12px + env(safe-area-inset-top)) 20px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}
.topbar__logo { height: 22px; width: auto; filter: brightness(0); }

.shell__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.shell__inner {
  max-width: 640px;
  margin: 0 auto;
  padding: 20px 20px calc(28px + env(safe-area-inset-bottom));
}

.pagehead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 6px;
}
.pagehead__title { font-size: 24px; font-weight: 650; margin: 0; }

.btn--small {
  width: auto;
  height: 44px;
  padding: 0 18px;
  font-size: 15px;
  border-radius: 12px;
}

/* ---------------------------------------------------------------- users */

.userlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }

.user {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 16px;
}
.user__name { font-size: 18px; font-weight: 600; }
.user__you  { color: var(--dimmer); font-weight: 400; font-size: 15px; }

.user__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.user__devices { font-size: 13px; color: var(--dim); }
.user__hint {
  margin-top: 8px;
  font-size: 13px;
  color: var(--dim);
  line-height: 1.4;
}

.tag {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .02em;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  color: var(--dim);
}
/* A badge is a label, not a control - it should not look pressable either. */
.tag--admin         { background: var(--surface-2); color: var(--ink); border-color: var(--line-strong); font-weight: 600; }
.tag--active        { background: #e6f0e8; color: #24632c; border-color: #c5ddc9; }
.tag--enroll        { background: #fdf1d8; color: #8a5a00; border-color: #f0dcae; }
.tag--locked        { background: #fbe3e1; color: #93231c; border-color: #f0c7c3; }
.tag--claim_expired { background: #ededeb; color: #5c5c5c; border-color: var(--line-strong); }

.user__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
/* The one secondary-action style in this app. Anything that is a real action
   but not THE action on screen uses this - user card actions, sign out, etc.
   Three states total, do not add a fourth:
     solid black  = the primary action
     outlined     = a chosen option
     chip         = a secondary action */
.chip {
  font-family: inherit;
  font-size: 14px;
  padding: 10px 14px;
  min-height: 40px;
  border-radius: 10px;
  border: 1px solid var(--line-strong);
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
}
.chip:active { background: var(--surface-2); }
.chip:disabled { opacity: .4; }
.chip--danger { color: var(--danger); border-color: #e6c4c1; }

/* ---------------------------------------------------------------- sheet */

.sheet {
  border: 0;
  border-radius: 18px;
  padding: 0;
  width: min(440px, calc(100vw - 32px));
  background: var(--bg);
  color: var(--ink);
}
.sheet::backdrop { background: rgba(23,23,23,.45); }
.sheet--narrow { width: min(380px, calc(100vw - 40px)); }
.sheet__inner { padding: 22px 20px calc(20px + env(safe-area-inset-bottom)); }
.sheet__title { font-size: 20px; font-weight: 650; margin: 0 0 4px; }
.sheet__sub   { font-size: 14px; color: var(--dim); margin-bottom: 12px; }
.sheet__body  { font-size: 15px; line-height: 1.5; color: var(--dim); margin: 8px 0 0; }

/* full-width action rows inside the edit sheet - big enough to be deliberate,
   and every one of them opens a confirmation before anything happens */
.stack { display: grid; gap: 8px; }
.rowbtn {
  width: 100%;
  min-height: 52px;
  padding: 14px 16px;
  text-align: left;
  font-family: inherit;
  font-size: 16px;
  border-radius: 12px;
  border: 1px solid var(--line-strong);
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
}
.rowbtn:active { background: var(--surface-2); }
.rowbtn--danger { color: var(--danger); border-color: #e6c4c1; }

.btn--danger { background: var(--danger); color: #fff; }

.sheet__actions--single { grid-template-columns: 1fr; }

.lbl {
  display: block;
  font-size: 14px;
  color: var(--dim);
  margin: 14px 0 8px;
}

.segmented {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.segmented__opt {
  height: 48px;
  border-radius: 12px;
  border: 1px solid var(--line-strong);
  background: var(--bg);
  color: var(--ink);
  font-family: inherit;
  font-size: 15px;
  cursor: pointer;
}
.segmented__opt.is-on {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
  font-weight: 500;
}

.sheet__actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 18px;
}
.sheet__actions .btn { height: 52px; font-size: 17px; }

/* ---------------------------------------------------------------- camera
   The one dark surface. A viewfinder cannot sit on white, so this uses the
   brand's dark panel rather than inventing a second dark. Anything rendered
   over the camera goes inside .surface-dark so contrast stays predictable. */

.surface-dark {
  background: var(--panel-dark);
  color: var(--panel-dark-ink);
}
.surface-dark .auth__brand img { filter: brightness(0) invert(1); }
.surface-dark .btn { background: var(--panel-dark-ink); color: var(--panel-dark); }

/* The camera fills the screen. Same fixed-size discipline as the login screen:
   the viewfinder is the only thing that flexes, every control is a fixed
   height, so nothing moves between live and review. */

.cam {
  position: fixed;
  inset: 0;
  height: 100dvh;
  height: var(--vh, 100dvh);
  overflow: hidden;
}

.cam__video,
.cam__shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: none;
  background: #000;
}
.cam[data-state="live"]   .cam__video { display: block; }
.cam[data-state="review"] .cam__shot  { display: block; }

.cam__top {
  position: absolute;
  top: 0; left: 0; right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(12px + env(safe-area-inset-top)) 16px 12px;
  /* keeps white controls readable over a bright shot */
  background: linear-gradient(rgba(0,0,0,.45), rgba(0,0,0,0));
}
.cam__logo { height: 20px; width: auto; filter: brightness(0) invert(1); }

.chip--onDark {
  background: rgba(255,255,255,.14);
  border-color: rgba(255,255,255,.28);
  color: #fff;
}
.chip--onDark:active { background: rgba(255,255,255,.24); }

.btn--onDark {
  background: rgba(255,255,255,.16);
  border: 1px solid rgba(255,255,255,.3);
  color: #fff;
}

.cam__bar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: none;
  padding: 18px 20px calc(22px + env(safe-area-inset-bottom));
  background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,.5));
}
.cam[data-state="live"]   #bar-live   { display: flex; justify-content: center; }
.cam[data-state="review"] #bar-review { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* The one control on the live screen. Deliberately oversized - it is pressed
   hundreds of times a day, often one-handed, sometimes with gloves. */
.shutter {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: #fff;
  border: 5px solid rgba(255,255,255,.4);
  box-shadow: 0 0 0 2px rgba(0,0,0,.25);
  cursor: pointer;
  padding: 0;
}
.shutter:active { transform: scale(.94); }

.cam__fail {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  text-align: center;
  padding: 30px 26px calc(30px + env(safe-area-inset-bottom));
  background: var(--panel-dark);
}
.cam[data-state="fail"] .cam__fail { display: flex; }
.cam__fail-title { font-size: 22px; font-weight: 650; margin: 0; }
.cam__fail-body  { font-size: 16px; line-height: 1.5; color: var(--panel-dark-dim); margin: 0 0 8px; max-width: 340px; }
.cam__fail .btn  { max-width: 260px; }

/* -------------------------------------------------- code / quantity steps
   Full-screen light panels that slide over the camera. Same discipline as
   everywhere else: every element a fixed height, one flexible child. */

.sheetfull {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: none;
  flex-direction: column;
  background: var(--bg);
  height: 100dvh;
  height: var(--vh, 100dvh);
}
.sheetfull.is-open { display: flex; }

.sheetfull__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: calc(10px + env(safe-area-inset-top)) 16px 10px;
  border-bottom: 1px solid var(--line);
}
.sheetfull__title { flex: 1 1 auto; font-size: 19px; font-weight: 650; margin: 0; }
.sheetfull__thumb {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--surface-2);
}

.iconbtn {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  font-size: 30px;
  line-height: 1;
  border: 0;
  background: none;
  color: var(--ink);
  font-family: inherit;
  cursor: pointer;
  padding: 0;
}

.sheetfull__body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
}

/* the one flexible element on the code step */
.codelist {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}
.codelist__item {
  min-height: 58px;
  display: flex;
  align-items: center;
  padding: 10px 14px;
  font-size: 17px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  margin-bottom: 8px;
  word-break: break-all;
}
.codelist__item:active { background: var(--surface-2); }
.codelist__empty {
  padding: 22px 4px;
  color: var(--dim);
  font-size: 15px;
  text-align: center;
}

/* quantity */
.sheetfull__body--qty { align-items: stretch; }
.qty__code {
  font-size: 15px;
  color: var(--dim);
  text-align: center;
  word-break: break-all;
  min-height: 22px;
}
.qty__value {
  font-size: 56px;
  line-height: 66px;
  font-weight: 650;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.qty__hint {
  font-size: 14px;
  color: var(--dim);
  text-align: center;
  height: 20px;
}
.sheetfull__body--qty .msg { height: 34px; min-height: 0; padding: 0; }
.sheetfull__body--qty .btn { flex: 0 0 auto; margin-top: 14px; }

/* saved confirmation */
.done {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: var(--bg);
}
.done.is-open { display: flex; }
.done__tick {
  width: 84px; height: 84px;
  border-radius: 50%;
  background: var(--panel-dark);
  color: var(--panel-dark-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 42px;
}
.done__text { font-size: 17px; color: var(--dim); text-align: center; padding: 0 24px; word-break: break-all; }
