/* The feedback bubble and its panel. Loaded on every page; nothing here paints until
   public/feedback.js builds the markup, so a page with scripts off is untouched by this file.

   TOKENS, AND THE TWO DIALECTS THIS FILE HAS TO SPAN. The homepage loads home.css and every
   other page loads app.css, and the two name the same colours differently: --ink/--muted/--rule
   there, --txt/--dim/--line here. A widget that lives on all eighteen pages cannot pick one. So
   every colour below resolves through a var() chain — app.css's name first, home.css's name
   second, a literal last — which means the widget inherits whichever palette the page brought
   with it, including all three themes, with no per-page override anywhere.

   The literal at the end of each chain is day-theme paper and is unreachable in practice (one of
   the two stylesheets is always present). It is there so this file still renders sanely if it is
   ever lifted into another page, not as a fallback anyone should rely on.

   SIZES ARE calc(<px> * var(--ts)) without exception, which is the site's reader-controlled text
   size. A hard px anywhere in here is a control that stops growing when someone turns the type
   up, i.e. exactly the reader this widget most needs to hear from. */
.twfb{
  --fb-bg:      var(--bg,#FBFAF8);
  --fb-card:    var(--card,rgb(var(--surface,255,255,255)));
  --fb-text:    var(--txt,var(--ink,#191917));
  --fb-dim:     var(--dim,var(--muted,#4F4C46));
  --fb-faint:   var(--faint,#747066);
  --fb-line:    var(--line,var(--rule,#E5E2DA));
  --fb-line2:   var(--line2,var(--rule-strong,#D2CEC4));
  --fb-accent:  var(--mint,#0E7A62);
  --fb-radius:  14px;
}

/* ---- the bubble -----------------------------------------------------------------------------
   Bottom-right, above the fold of nothing: it is the last thing in the tab order on every page
   because feedback.js appends it to <body>, which is where a persistent utility belongs — a
   keyboard reader should not meet it before the page's own content.

   z-index sits BELOW the theme sweep (9999) and the booking overlay (10000) on purpose. Those
   two are full-screen and momentary; a bubble painting on top of a modal someone opened is the
   widget deciding it matters more than what the reader asked for. */
.twfb-bubble{
  position:fixed;right:18px;bottom:18px;z-index:9990;
  display:inline-flex;align-items:center;gap:calc(8px * var(--ts));
  padding:calc(10px * var(--ts)) calc(15px * var(--ts));
  border:1px solid var(--fb-line2);border-radius:999px;
  background:var(--fb-card);color:var(--fb-text);
  font:inherit;font-size:calc(14.5px * var(--ts));line-height:1;
  cursor:pointer;
  box-shadow:0 6px 20px rgba(0,0,0,.10),0 1px 2px rgba(0,0,0,.06);
  transition:transform .18s cubic-bezier(.22,.61,.36,1),box-shadow .18s,border-color .18s;
}
.twfb-bubble:hover{ transform:translateY(-1px);box-shadow:0 10px 26px rgba(0,0,0,.14);border-color:var(--fb-accent) }
.twfb-bubble:focus-visible{ outline:2px solid var(--fb-accent);outline-offset:3px }
.twfb-bubble svg{ width:calc(17px * var(--ts));height:calc(17px * var(--ts));flex:0 0 auto;color:var(--fb-accent) }
/* The panel opens over the bubble, so the bubble goes. visibility, not just opacity: an
   opacity:0 button is still in the tab order, and the first Tab inside a panel that declares
   aria-modal would land on a control the reader cannot see, behind the thing they just opened. */
.twfb-bubble[aria-expanded="true"]{ opacity:0;visibility:hidden;pointer-events:none }

/* Under 560px the label goes and the bubble becomes a circle. Not to save room for its own sake:
   a pill wide enough to read is wide enough to sit over a paragraph on a phone, and the icon
   alone still carries the accessible name from aria-label. */
@media (max-width:560px){
  .twfb-bubble{ right:14px;bottom:14px;padding:calc(12px * var(--ts));border-radius:50% }
  .twfb-bubble .twfb-bubble-label{ display:none }
}

/* ---- scrim ----------------------------------------------------------------------------------
   Transparent on a wide screen and dimmed on a narrow one, and it exists at both sizes for the
   same non-visual reason: it is what makes a click anywhere outside the panel close it. The
   panel declares aria-modal, so dismissal has to be available without hunting for the × . */
.twfb-scrim{
  position:fixed;inset:0;z-index:9994;display:none;background:transparent;
}
.twfb-scrim.open{ display:block }
@media (max-width:720px){
  .twfb-scrim{ background:rgba(10,12,10,.42) }
}

/* ---- the panel ------------------------------------------------------------------------------
   Anchored over the bubble on a wide screen; a bottom sheet on a narrow one. max-height is
   capped and the body scrolls inside, because the one thing this must never do is grow taller
   than the viewport and put its own submit button somewhere unreachable. */
.twfb-panel{
  position:fixed;right:18px;bottom:18px;z-index:9995;
  width:min(392px,calc(100vw - 36px));max-height:min(640px,calc(100vh - 36px));
  display:none;flex-direction:column;
  background:var(--fb-card);color:var(--fb-text);
  border:1px solid var(--fb-line2);border-radius:var(--fb-radius);
  box-shadow:0 22px 60px rgba(0,0,0,.24),0 2px 6px rgba(0,0,0,.08);
  overflow:hidden;
}
.twfb-panel.open{ display:flex }
@media (max-width:720px){
  .twfb-panel{
    right:0;left:0;bottom:0;width:auto;max-height:88vh;
    border-radius:var(--fb-radius) var(--fb-radius) 0 0;
    /* A sheet flush to both edges has no side to draw. Left as 1px borders they were also the
       reason the panel measured 437.5px inside a 437px viewport — half a pixel of horizontal
       overhang on every phone. */
    border-left:0;border-right:0;border-bottom:0;
  }
}

.twfb-head{
  display:flex;align-items:flex-start;gap:calc(10px * var(--ts));
  padding:calc(16px * var(--ts)) calc(16px * var(--ts)) calc(10px * var(--ts));
  border-bottom:1px solid var(--fb-line);
}
.twfb-title{ font-size:calc(15.5px * var(--ts));font-weight:650;margin:0;line-height:1.25 }
.twfb-sub{ font-size:calc(12.5px * var(--ts));color:var(--fb-dim);margin:calc(3px * var(--ts)) 0 0;line-height:1.4 }
/* ---- the invite line ------------------------------------------------------------------------
   "We aren't shy about hearing praise (or criticism)." The two words take turns being lit: each
   one warms to its own colour, lifts by half a pixel, and hands over. They are OFFSET BY HALF THE
   CYCLE rather than running together, because the sentence is about a pair and the animation
   should read as the pair trading places, not as two words flashing at once.

   Slow — one full exchange takes about six seconds. This sits three lines under a heading in a
   panel someone opened to write in; anything quicker competes with the textarea for attention,
   which is the one thing a line encouraging people to write must not do. */
.twfb-invite{
  font-size:calc(12.5px * var(--ts));color:var(--fb-dim);
  margin:calc(6px * var(--ts)) 0 0;line-height:1.45;
}
.twfb-em{
  font-style:normal;font-weight:650;
  display:inline-block;          /* transform needs a box; inline elements do not take one */
  will-change:transform;
}
.twfb-em-praise{ color:var(--fb-accent);animation:twfb-lift 6.2s ease-in-out infinite }
.twfb-em-crit{ color:var(--amber,#8A5A0F);animation:twfb-lift 6.2s ease-in-out infinite;animation-delay:-3.1s }
@keyframes twfb-lift{
  0%,42%,100%{ transform:translateY(0);opacity:.72 }
  16%{ transform:translateY(-.9px);opacity:1 }
  30%{ transform:translateY(0);opacity:1 }
}

/* Stopped, but NOT flattened. The resting state keeps both colours, because the colours are the
   half of this that carries meaning — "praise" in the accent and "criticism" in the amber says
   the two are equally welcome, which is the whole point of the sentence. What stops is the
   movement. */
:where(html[data-motion="reduced"],html[data-motion="off"]) .twfb-em{ animation:none;opacity:1 }
@media (prefers-reduced-motion:reduce){
  html:not([data-motion]) .twfb-em{ animation:none;opacity:1 }
}

.twfb-close{
  margin-left:auto;flex:0 0 auto;
  width:calc(28px * var(--ts));height:calc(28px * var(--ts));border-radius:50%;
  border:1px solid var(--fb-line);background:transparent;color:var(--fb-dim);
  font:inherit;font-size:calc(17px * var(--ts));line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
}
.twfb-close:hover{ color:var(--fb-text);border-color:var(--fb-line2) }
.twfb-close:focus-visible{ outline:2px solid var(--fb-accent);outline-offset:2px }

.twfb-body{ padding:calc(14px * var(--ts)) calc(16px * var(--ts));overflow-y:auto;flex:1 1 auto }

.twfb-field{ margin-bottom:calc(13px * var(--ts)) }
.twfb-label{
  display:block;font-size:calc(12.5px * var(--ts));font-weight:600;
  color:var(--fb-text);margin-bottom:calc(5px * var(--ts));
}
.twfb-label .twfb-opt{ font-weight:400;color:var(--fb-faint) }
.twfb-help{ font-size:calc(11.5px * var(--ts));color:var(--fb-faint);margin-top:calc(4px * var(--ts));line-height:1.45 }

.twfb-input,.twfb-select,.twfb-textarea{
  width:100%;box-sizing:border-box;
  padding:calc(9px * var(--ts)) calc(10px * var(--ts));
  border:1px solid var(--fb-line2);border-radius:9px;
  background:var(--fb-bg);color:var(--fb-text);
  font:inherit;font-size:calc(13.5px * var(--ts));line-height:1.45;
}
.twfb-textarea{
  min-height:calc(92px * var(--ts));resize:vertical;
  /* A comment box has nothing to say horizontally: everything in it wraps, so a sideways
     scrollbar can only ever mean something has escaped the box. Belt and braces, because the
     thing that escaped was the PLACEHOLDER, which does not wrap reliably across engines and is
     not reachable by the wrapping properties below in all of them. The placeholder is short now
     (see feedback.js) so this should never fire — it is here so that a longer one written later
     degrades to a clipped hint rather than to a scrollbar. */
  overflow-x:hidden;
  overflow-wrap:break-word;word-break:normal;white-space:pre-wrap;
}
.twfb-textarea::placeholder{ white-space:pre-wrap;overflow-wrap:break-word }
.twfb-input:focus,.twfb-select:focus,.twfb-textarea:focus{
  outline:2px solid var(--fb-accent);outline-offset:1px;border-color:var(--fb-accent);
}
.twfb-select{
  /* The arrow is drawn rather than left to the platform: a native select on a dark theme keeps
     the platform's own light chrome in several browsers, which is the one control on the panel
     that would flash white on night paper. */
  appearance:none;-webkit-appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:right calc(15px * var(--ts)) center,right calc(10px * var(--ts)) center;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
  padding-right:calc(28px * var(--ts));
}
.twfb-count{ float:right;font-weight:400;color:var(--fb-faint);font-variant-numeric:tabular-nums }
.twfb-count.over{ color:var(--rose,#A8383F);font-weight:600 }

/* ---- the tag chips --------------------------------------------------------------------------
   The row only exists once something matched, so an empty message shows no scaffolding for a
   feature that has not done anything yet. Each chip is a button that removes itself: the title
   names the cue that produced it, which is the whole transparency claim made good. */
.twfb-tags{ display:none;flex-wrap:wrap;gap:calc(6px * var(--ts));margin-top:calc(7px * var(--ts)) }
.twfb-tags.on{ display:flex }
.twfb-tagnote{ width:100%;font-size:calc(11.5px * var(--ts));color:var(--fb-faint);line-height:1.45 }
.twfb-chip{
  display:inline-flex;align-items:center;gap:calc(5px * var(--ts));
  padding:calc(3px * var(--ts)) calc(7px * var(--ts));
  border:1px solid var(--fb-line2);border-radius:999px;
  background:transparent;color:var(--fb-dim);
  font:inherit;font-size:calc(11.5px * var(--ts));line-height:1.6;cursor:pointer;
}
.twfb-chip:hover{ border-color:var(--fb-accent);color:var(--fb-text) }
.twfb-chip:focus-visible{ outline:2px solid var(--fb-accent);outline-offset:2px }
.twfb-chip .x{ font-size:calc(12.5px * var(--ts));line-height:1;opacity:.7 }
.twfb-chip .dot{
  width:calc(6px * var(--ts));height:calc(6px * var(--ts));border-radius:50%;
  background:var(--fb-accent);flex:0 0 auto;
}

.twfb-check{ display:flex;align-items:flex-start;gap:calc(8px * var(--ts));font-size:calc(12.5px * var(--ts));line-height:1.45 }
.twfb-check input{ margin:calc(2px * var(--ts)) 0 0;flex:0 0 auto;accent-color:var(--fb-accent) }

/* The honeypot. Never shown, never focusable, never announced — the three ways a real person
   could otherwise be handed a field that fails their submission silently. */
.twfb-hp{ position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden }

.twfb-foot{
  padding:calc(12px * var(--ts)) calc(16px * var(--ts)) calc(14px * var(--ts));
  border-top:1px solid var(--fb-line);background:var(--fb-bg);
}
.twfb-send{
  width:100%;padding:calc(10px * var(--ts)) calc(14px * var(--ts));
  border:1px solid var(--fb-accent);border-radius:10px;
  background:var(--fb-accent);color:#fff;
  font:inherit;font-size:calc(14px * var(--ts));font-weight:600;line-height:1.2;cursor:pointer;
}
.twfb-send:hover{ filter:brightness(1.06) }
.twfb-send:focus-visible{ outline:2px solid var(--fb-text);outline-offset:2px }
.twfb-send[disabled]{ opacity:.55;cursor:default;filter:none }

/* ---- the human line -------------------------------------------------------------------------
   "A person reads this" is a claim, and a claim on a website is worth roughly nothing unless it
   costs something to make. What it costs here is the address itself: hello@tileward.com is a
   real mailbox a human answers, so the line is checkable by anyone who doubts it. */
.twfb-human{
  display:flex;align-items:center;gap:calc(9px * var(--ts));
  margin-top:calc(11px * var(--ts));
  font-size:calc(11.5px * var(--ts));color:var(--fb-dim);line-height:1.45;
}
.twfb-human a{ color:var(--fb-accent);text-decoration:underline;text-underline-offset:2px }
.twfb-human svg{ width:calc(30px * var(--ts));height:calc(30px * var(--ts));flex:0 0 auto;overflow:visible }

/* The figure: a head, shoulders, and a raised hand that waves. Three separate motions, each slow
   and small, and all three are decoration on a drawing that is already complete when still. */
/* transform-origin here is in the SVG's OWN user units, not CSS pixels, and must NOT be scaled
   by --ts. The <svg> box grows with the reader's type size; its viewBox does not, so an origin
   multiplied by --ts walks off the drawing the moment someone turns the text up — the hand would
   pivot from a point outside the figure and swing across the panel. */
.twfb-hand{ transform-origin:21.5px 17px;animation:twfb-wave 3.4s ease-in-out infinite }
.twfb-head-g{ transform-origin:15px 11px;animation:twfb-nod 6.8s ease-in-out infinite }
.twfb-ring{ animation:twfb-breathe 4.2s ease-in-out infinite }
@keyframes twfb-wave{
  0%,58%,100%{ transform:rotate(0deg) }
  64%{ transform:rotate(-17deg) }
  72%{ transform:rotate(11deg) }
  80%{ transform:rotate(-11deg) }
  88%{ transform:rotate(4deg) }
}
@keyframes twfb-nod{ 0%,88%,100%{ transform:translateY(0) } 93%{ transform:translateY(.9px) } }
@keyframes twfb-breathe{ 0%,100%{ opacity:.30 } 50%{ opacity:.62 } }

/* The site's motion setting, honoured the way every other component here honours it: the
   attribute is written to <html> in each page's <head> before first paint. A waving hand is the
   most literally "moving decoration" thing on the site, so it is the first thing that should
   stop when a reader asks for less — and what is left is a finished drawing of a person, not a
   gap where one was. */
:where(html[data-motion="reduced"],html[data-motion="off"]) .twfb-hand,
:where(html[data-motion="reduced"],html[data-motion="off"]) .twfb-head-g,
:where(html[data-motion="reduced"],html[data-motion="off"]) .twfb-ring{ animation:none }
:where(html[data-motion="reduced"],html[data-motion="off"]) .twfb-bubble{ transition:none }
:where(html[data-motion="reduced"],html[data-motion="off"]) .twfb-bubble:hover{ transform:none }

/* THE NO-JS NET, matching app.css: with scripts off nothing writes data-motion, so the OS
   preference is honoured directly for the only case that can reach it. (Nothing in this widget
   exists without scripts, so this is belt-and-braces against a page that loads the stylesheet
   and drops the script — a cache mismatch, an extension, a blocked request.) */
@media (prefers-reduced-motion:reduce){
  html:not([data-motion]) .twfb-hand,
  html:not([data-motion]) .twfb-head-g,
  html:not([data-motion]) .twfb-ring{ animation:none }
}

/* ---- states ---------------------------------------------------------------------------------
   One live region for everything the panel needs to say. It is written to rather than replaced,
   because inserting a new role="status" is a thing several screen readers decline to announce. */
.twfb-status{ font-size:calc(12px * var(--ts));line-height:1.45;margin-top:calc(9px * var(--ts)) }
.twfb-status:empty{ margin-top:0 }
.twfb-status.bad{ color:var(--rose,#A8383F) }
.twfb-status.ok{ color:var(--fb-accent) }

.twfb-done{ padding:calc(20px * var(--ts)) calc(16px * var(--ts));text-align:left }
.twfb-done h3{ font-size:calc(15px * var(--ts));margin:0 0 calc(7px * var(--ts));line-height:1.3 }
.twfb-done p{ font-size:calc(12.5px * var(--ts));color:var(--fb-dim);margin:0 0 calc(9px * var(--ts));line-height:1.5 }
.twfb-ref{
  font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:calc(12px * var(--ts));color:var(--fb-text);
  background:var(--fb-bg);border:1px solid var(--fb-line);border-radius:6px;
  padding:calc(1px * var(--ts)) calc(6px * var(--ts));
}
