/* Preview access layout; the error motion below is from transitions.dev. */
.access-gate{min-height:100dvh;background:var(--forest);padding:48px 24px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:34px}
.access-logo{width:205px;height:auto}
.access-card{width:100%;max-width:500px;padding:42px;background:var(--cream);border-radius:24px;color:var(--forest)}
.access-label{font-size:12px;letter-spacing:.08em;color:var(--deep-red);margin-bottom:20px}
.access-card h1{font-size:52px;line-height:1.08;letter-spacing:-1.5px;margin-bottom:20px}
.access-card>p{font-size:16px}
.access-card label{display:block;font-size:14px;font-weight:500;margin:28px 0 10px}
.access-control{display:flex;background:white;border:1px solid #718781;border-radius:12px;min-height:54px}
.access-control input{width:100%;min-width:0;border:0;background:transparent;padding:14px;font-size:18px;color:var(--forest);border-radius:12px}
.access-control button{border:0;background:transparent;color:var(--forest);padding:12px 16px;font:inherit;font-size:13px;cursor:pointer;min-width:64px}
.access-control.is-error{border-color:var(--deep-red)}
.access-card .t-error-msg{min-height:1.5em;margin:10px 0 18px;color:var(--deep-red);font-size:13px}
.access-submit{width:100%}
.access-card .access-note{font-size:12px;color:var(--muted);margin:22px 0 0;text-align:center}
.lock-preview{border:0;background:transparent;color:#d3e4df;font:inherit;font-size:12px;padding:8px 0;text-decoration:underline;cursor:pointer}
@media(max-width:520px){.access-gate{padding:28px 18px;gap:26px}.access-card{padding:30px 24px}.access-card h1{font-size:43px}}
@media print{.access-gate,.lock-preview{display:none}}

:root {
  --shake-distance: 6px;
  --shake-overshoot: 4px;
  --shake-dur-a: 80ms;
  --shake-dur-b: 60ms;
  --shake-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --revert-hold: 3000ms;
  --revert-dur: 280ms;
}

/* Border-color tween. Define your input's default / focused
   / error border-color in your own component CSS — this rule
   only owns the interpolation. Use a constant border-width
   across states so the tween never shifts inner content. */
.t-input {
  transition: border-color 150ms ease-out;
  will-change: transform;
}
.t-input.is-error {
  /* Error border auto-reverts on the hold timer, so the
     fade-out uses the slower revert duration (matches the
     message fade). */
  transition: border-color var(--revert-dur, 280ms) ease-out;
}

/* Error message reveal. Visibility is delayed by --revert-dur
   on hide so the message stays painted for the full opacity
   fade-out. Entering .is-error drops the delay to 0 so the
   message becomes visible immediately. */
.t-error-msg {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity    var(--revert-dur, 280ms) ease-out,
    visibility 0s linear var(--revert-dur, 280ms);
}
.t-input-wrap.is-error .t-error-msg {
  opacity: 1;
  visibility: visible;
  transition:
    opacity    var(--revert-dur, 280ms) ease-out,
    visibility 0s linear 0s;
}

/* Multi-segment keyframe with per-stop easing so each leg
   of the shake follows its own cubic-bezier independently.
   %-stops are cumulative durations as a fraction of the
   total (80, 60, 80, 60 = 280ms): 28.57%, 57.14%, 78.57%,
   100%. Recompute if any segment duration changes. */
.t-input.is-shaking {
  animation: t-input-shake calc(
      var(--shake-dur-a) * 2 + var(--shake-dur-b) * 2
    ) linear;
}
@keyframes t-input-shake {
  0%      { transform: translateX(0);                                 animation-timing-function: var(--shake-ease); }
  28.57%  { transform: translateX(var(--shake-distance));             animation-timing-function: var(--shake-ease); }
  57.14%  { transform: translateX(calc(var(--shake-distance) * -1)); animation-timing-function: var(--shake-ease); }
  78.57%  { transform: translateX(var(--shake-overshoot));            animation-timing-function: var(--shake-ease); }
  100%    { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .t-input { animation: none !important; transform: none !important; }
}
