/* feedback.css - the screen that replaced the Google form. Phase PF3.
   ===================================================================

   WHY THIS IS A FILE OF ITS OWN, WHICH IS THE ONLY INTERESTING DECISION HERE.

   The same dialog opens from three places and two of them are on different
   pages. index.html loads variables, document and landing; build.html loads
   variables, components, utilities, style and document. There is no
   stylesheet in this project that both pages already load and that this
   belongs in - variables.css is tokens and document.css is the CV itself -
   so the choice was between one new file both pages load, and one copy of
   the dialog chrome in landing.css beside another in style.css.

   Two copies of a rule that agree until the first edit is the shape every
   drift defect in this repository has had, and the owner's brief for this
   phase is that the screen is OURS: one screen, not two that look alike.
   So it is one file, and the dialog it dresses carries none of the two
   pages' own classes - not .modal, which lives in style.css and does not
   exist on the landing page, and not .btn, which BOTH pages define and
   define differently (build's has a 44px floor and landing's does not).
   Everything the screen needs is here, once, for both pages.

   WHAT IT MAY USE. The tokens css/variables.css already declares, and
   nothing else. Phase PN5 closed ten counters on this interface and
   tests/interface.test.mjs holds every one of them at zero by equality, so
   this file is written to them rather than measured against them afterwards:

     no gradient anywhere, on an element or a pseudo-element
     every border is var(--hairline), which is 1px
     the only shadow is var(--shadow-dialog), which is one of the two named
     every font-size is a --type-* token, which is the six-step scale
     every weight is 400, 500 or 600
     every duration is --motion-fast or --motion-fade, 100ms and 150ms, and
       both are zero under prefers-reduced-motion because variables.css says
       so - which is why nothing here writes a duration of its own
     every control is at least 44px on both axes
     no colour literal: every colour is a token
*/

/* ─── the trigger in the landing page footer ───────────────────────── */

/* It was an <a> to a Google form and it is a button now, which is right -
   it opens something on this page rather than going anywhere - and a button
   is the one element that does NOT inherit the page's type. Left alone it
   took the browser's own default and read as Arial at 13.33px in a footer
   of Schibsted Grotesk at 16, which is a second typeface and a size off the
   six-step scale from one unstyled control.

   THAT IS NOT HYPOTHETICAL AND IT IS NOT MY OBSERVATION EITHER: the two
   counters in tests/interface.test.mjs went red on the first run after this
   button existed, naming the element and both numbers, and phase PN5 had
   closed the identical defect on .btn-delete - a button nobody had given a
   font-family, wearing Arial, that nothing in the repository could see until
   those counters existed.

   `font: inherit` rather than a size and a family of its own, because the
   requirement is not "16px Schibsted" but "whatever the links either side of
   it are". A shorthand that copies the footer cannot drift from the footer. */
.foot-feedback {
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    color: var(--ink-soft);
    cursor: pointer;
    transition: color var(--motion-fast);
}

.foot-feedback:hover {
    color: var(--ink);
}

/* ─── the overlay ──────────────────────────────────────────────────── */

.fb-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--spacing-md);
    background: var(--scrim);
    animation: fbFade var(--motion-fade);
}

/* AND THIS LINE IS NOT A TIDY-UP. `hidden` is a presentation hint - the user
   agent implements it as display:none and ANY author display declaration
   beats it - so the rule above, which needs display:flex to centre the sheet,
   silently turned the closed dialog into a transparent sheet of glass over
   the whole of both pages. Every control on the landing page and the builder
   stopped answering a pointer, and nothing looked wrong.

   It was found by pressing the button rather than by reading this file:
   Playwright refused to click the footer link and named the element that was
   in front of it. js/feedback.js toggles `hidden` and nothing else, so this
   is the declaration that makes the attribute mean what it says. */
.fb-modal[hidden] {
    display: none;
}

/* Its own name rather than fadeIn, which css/style.css already declares.
   Two @keyframes with one name in one document is the last one winning
   silently, and both pages would have to be read to find out which. */
@keyframes fbFade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* The sheet. One dialog shadow, whose 1px ring is its edge, so the box
   needs no border of its own - the same construction the app's own dialogs
   got in PN5. dvh rather than vh: on a phone vh is the height with the
   browser chrome retracted, so 90vh is taller than the glass whenever the
   address bar is showing, which is most of the time. */
.fb-sheet {
    width: 100%;
    max-width: 30rem;
    max-height: 90dvh;
    overflow-y: auto;
    background: var(--paper-raised);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-dialog);
}

/* ─── the head ─────────────────────────────────────────────────────── */

.fb-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--spacing-sm);
    padding: var(--spacing-md) var(--spacing-lg);
    border-bottom: var(--hairline);
}

.fb-title {
    margin: 0;
    font-size: var(--type-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--ink);
}

.fb-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border: none;
    border-radius: var(--radius-md);
    background: transparent;
    font-family: inherit;
    font-size: var(--type-lg);
    line-height: 1;
    color: var(--ink-soft);
    cursor: pointer;
    transition: background-color var(--motion-fast), color var(--motion-fast);
}

.fb-close:hover {
    background: var(--surface);
    color: var(--ink);
}

/* ─── the body ─────────────────────────────────────────────────────── */

.fb-body {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-md);
    padding: var(--spacing-lg);
}

/* The two languages, as two lines. The Arabic carries dir="rtl" on itself
   and nothing else on either page does: the dialog, the form and the
   document all stay left to right, and what flips is one span.

   THE CLASS NAMES ARE THE PROJECT'S OWN, WHICH IS THE POINT RATHER THAN A
   CONVENIENCE. bilingual() in js/utils.js emits .say-ar and .say-en, and the
   instruments know those two names: tests/interface.test.mjs reads a
   bilingual label by splitting it on them, and without them the Arabic and
   the English weld into one token - which reports the English half as Title
   Case in the middle of a sentence when it is the first word of its own, and
   reads none of it where the Arabic has no space. A new pair of names for
   the same idea would have bought that defect back on one screen.

   SCOPED, so this is a floor and never a second opinion. css/style.css
   declares the same two properties for these names on build.html; this says
   the same thing inside this dialog, which is the only place on the landing
   page they appear. Same values, and the scope is what makes it a fallback
   rather than a rule arguing with another one. */
.fb-modal .say-ar,
.fb-modal .say-en {
    display: block;
}

.fb-modal .say-en {
    margin-top: var(--spacing-2xs);
}

.fb-ask {
    margin: 0;
    font-size: var(--type-sm);
    font-weight: var(--font-weight-medium);
    color: var(--ink-2);
}

/* ─── the thumb ────────────────────────────────────────────────────── */

.fb-thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-sm);
}

/* Buttons rather than radios, and the reason is the third answer. A pair of
   radios can be set and never unset, so a person who taps one by accident
   is stuck having given an opinion. These toggle: press it once for yes,
   press it again for neither, which is what aria-pressed already means and
   what the row stores as null. */
.fb-thumb {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-xs);
    min-height: 44px;
    min-width: 44px;
    padding: 0 var(--spacing-md);
    border: var(--hairline);
    border-radius: var(--radius-md);
    background: var(--paper-raised);
    font-family: inherit;
    font-size: var(--type-sm);
    font-weight: var(--font-weight-medium);
    color: var(--ink-2);
    cursor: pointer;
    transition: background-color var(--motion-fast),
                border-color var(--motion-fast),
                color var(--motion-fast);
}

.fb-thumb:hover {
    background: var(--surface);
    color: var(--ink);
}

/* Chosen. A wash and the page's own ink rather than a second fill colour,
   so the pressed state reads without introducing a colour the palette does
   not have. The state is on aria-pressed rather than on a class, so the
   attribute a screen reader is told and the thing the eye sees cannot say
   different things. */
.fb-thumb[aria-pressed="true"] {
    background: var(--pen-wash);
    border-color: var(--pen);
    color: var(--pen-deep);
}

/* ─── the box ──────────────────────────────────────────────────────── */

.fb-label {
    display: block;
    font-size: var(--type-sm);
    font-weight: var(--font-weight-medium);
    color: var(--ink-2);
    margin-bottom: var(--spacing-2xs);
}

.fb-text {
    display: block;
    width: 100%;
    min-height: 8rem;
    padding: 0.7rem 0.9rem;
    border: var(--hairline);
    border-radius: var(--radius-md);
    background: var(--surface);
    font-family: inherit;
    font-size: var(--type-md);
    line-height: var(--line-height-normal);
    color: var(--ink);
    resize: vertical;
    transition: border-color var(--motion-fast), background-color var(--motion-fast);
}

.fb-text::placeholder {
    color: var(--ink-faint);
}

.fb-text:hover {
    border-color: var(--ink-faint);
}

.fb-text:focus {
    border-color: var(--pen);
    background-color: var(--paper-raised);
}

.fb-left {
    margin: var(--spacing-2xs) 0 0;
    font-size: var(--type-xs);
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

/* ─── the sentence ─────────────────────────────────────────────────── */

/* What this screen promises, in both languages, and it is the only thing on
   it that is a claim rather than a label. tests/sweep.py holds it in the
   claims table and tests/paywall.test.mjs holds it on both pages, for the
   reason D-127 exists: a sentence with nothing behind it does not ship. */
.fb-note {
    margin: 0;
    padding-top: var(--spacing-md);
    border-top: var(--hairline);
    font-size: var(--type-xs);
    color: var(--ink-soft);
}

/* ─── the two buttons ──────────────────────────────────────────────── */

.fb-actions {
    display: flex;
    gap: var(--spacing-sm);
    justify-content: flex-end;
    flex-wrap: wrap;
}

.fb-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-xs);
    min-height: 44px;
    min-width: 44px;
    padding: 0 var(--spacing-lg);
    border: var(--hairline);
    border-color: transparent;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: var(--type-sm);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: background-color var(--motion-fast),
                border-color var(--motion-fast),
                color var(--motion-fast);
}

.fb-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* One colour, no gradient and no lift: hover darkens the fill and press
   dips one pixel. The dip is on :active, which is held by the mouse button
   rather than by where the pointer is, so it cannot loop the way a :hover
   that moves its own element can (D-171). */
.fb-send {
    background: var(--pen);
    color: var(--on-pen);
}

.fb-send:hover:not(:disabled) {
    background: var(--pen-deep);
}

.fb-send:active:not(:disabled) {
    transform: translateY(1px);
}

.fb-cancel {
    background: var(--paper-raised);
    border-color: var(--rule);
    color: var(--ink);
}

.fb-cancel:hover {
    background: var(--surface);
}

.fb-cancel:active {
    transform: translateY(1px);
}

/* ─── what happened ────────────────────────────────────────────────── */

/* role="status" in the markup, so the answer is spoken as well as shown.
   Empty until there is something to say, and it takes no height then: a
   reserved empty line under two buttons reads as a layout mistake. */
.fb-said {
    margin: 0;
    font-size: var(--type-sm);
    color: var(--ink-2);
}

.fb-said:empty {
    display: none;
}

.fb-said.is-wrong {
    color: var(--stop);
}

/* ─── the microphone, PNIGHT ───────────────────────────────────────── */

/* A strip under the box that shares its edge, the way the write button's
   bar does on the builder (.ai-write-bar in css/style.css): the box keeps
   its top corners, the strip takes the bottom two. js/voice.js puts it
   straight after the textarea, so the adjacency selector is the join. */
.fb-text:has(+ .fb-voice) {
    border-end-start-radius: 0;
    border-end-end-radius: 0;
}

.fb-voice {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--spacing-2xs);
    padding: var(--spacing-2xs);
    background: var(--surface);
    border: var(--hairline);
    border-top: 0;
    border-end-start-radius: var(--radius-md);
    border-end-end-radius: var(--radius-md);
    transition: border-color var(--motion-fast);
}

.fb-text:hover + .fb-voice {
    border-color: var(--ink-faint);
}

.fb-text:focus + .fb-voice,
.fb-text.fb-voice-fresh + .fb-voice {
    border-color: var(--pen);
}

.fb-voice-btn,
.fb-voice-undo {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    min-height: 44px;
    min-width: 44px;
    padding: 0 var(--spacing-md);
    border: var(--hairline);
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: var(--type-sm);
    font-weight: var(--font-weight-medium);
    line-height: var(--line-height-tight);
    cursor: pointer;
    transition: background-color var(--motion-fast),
                border-color var(--motion-fast),
                color var(--motion-fast);
}

.fb-voice-btn {
    background: var(--paper-raised);
    border-color: var(--rule);
    color: var(--ink);
}

.fb-voice-btn:hover:not(:disabled) {
    background: var(--pen-wash);
}

/* Listening: it cannot be pressed, and it keeps its colour, because what
   changed is the spinner in the icon's slot rather than whether it works. */
.fb-voice-btn[aria-busy="true"] {
    cursor: progress;
}

/* Recording. The stop square and the time carry it; the frame takes the
   stop colour so the state reads at a glance without anything moving. */
.fb-voice-btn[aria-pressed="true"] {
    border-color: var(--stop);
}

.fb-voice-btn[aria-pressed="true"] .fb-voice-icon {
    color: var(--stop);
}

.fb-voice-undo {
    background: transparent;
    border-color: transparent;
    color: var(--ink-2);
}

.fb-voice-undo:hover {
    background: var(--surface-deep);
}

.fb-voice-undo[hidden] {
    display: none;
}

.fb-voice-icon {
    color: var(--pen);
}

.fb-voice-undo .fb-voice-icon {
    color: var(--ink-soft);
}

.fb-voice-label {
    display: flex;
    flex-direction: column;
    text-align: center;
}

.fb-modal .fb-voice-label .say-en {
    margin-top: 0;
}

.fb-voice-time {
    font-variant-numeric: tabular-nums;
    color: var(--stop);
}

.fb-voice-time[hidden] {
    display: none;
}

/* Its own spinner, because the landing page does not load css/style.css
   where .icon-spin lives. An infinite rotation is a state, not a
   transition. css/variables.css zeroes durations but not animations, so it
   is stopped here under reduced motion; the label still says Listening. */
.fb-spin {
    animation: fbSpin 900ms linear infinite;
}

@keyframes fbSpin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .fb-spin {
        animation: none;
    }
}

/* What a press said when it did not work. Empty after one that did. */
.fb-voice-said {
    margin: var(--spacing-2xs) 0 0;
    font-size: var(--type-xs);
    color: var(--stop);
}

.fb-voice-said:empty {
    display: none;
}

/* The words just landed: the pen's wash for about a second, on and off
   through the box's own --motion-fast transition. */
.fb-text.fb-voice-fresh {
    border-color: var(--pen);
    background-color: var(--pen-wash);
}

/* ─── the owner's questions, PNIGHT ────────────────────────────────── */

/* The rating is a fieldset of five real radios, so arrow keys move along it
   and a screen reader says "3 of 5" without anything written for it. The
   fieldset's own border and padding go: it is a group, not a box. */
.fb-rate {
    margin: 0;
    padding: 0;
    border: 0;
    min-width: 0;
}

.fb-rate legend {
    padding: 0;
    margin-bottom: var(--spacing-xs);
}

/* The one required answer says so beside the question, in the question's
   own size, rather than with a star a person has to decode. */
.fb-need {
    font-weight: var(--font-weight-medium);
    color: var(--pen);
}

.fb-scale {
    display: grid;
    grid-template-columns: repeat(5, minmax(44px, 1fr));
    gap: var(--spacing-xs);
}

.fb-point {
    position: relative;
    display: flex;
}

/* The radio stays in the page for the keyboard and the screen reader, and
   the number beside it is what the eye presses. */
.fb-point input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.fb-point span {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 44px;
    border: var(--hairline);
    border-radius: var(--radius-md);
    background: var(--paper-raised);
    font-size: var(--type-md);
    font-weight: var(--font-weight-medium);
    font-variant-numeric: tabular-nums;
    color: var(--ink-2);
    cursor: pointer;
    transition: background-color var(--motion-fast),
                border-color var(--motion-fast),
                color var(--motion-fast);
}

.fb-point:hover span {
    background: var(--surface);
    color: var(--ink);
}

.fb-point input:checked + span {
    background: var(--pen-wash);
    border-color: var(--pen);
    color: var(--pen-deep);
}

.fb-point input:focus-visible + span {
    outline: 2px solid var(--pen);
    outline-offset: 2px;
}

/* What the two ends mean, under them. */
.fb-ends {
    display: flex;
    justify-content: space-between;
    gap: var(--spacing-sm);
    margin: var(--spacing-2xs) 0 0;
    font-size: var(--type-xs);
    color: var(--ink-soft);
}

.fb-ends > span:last-child {
    text-align: end;
}

/* The email: one line, the box's own frame and type. An address is
   left-to-right whatever the page around it is. */
.fb-field {
    display: block;
    width: 100%;
    min-height: 44px;
    padding: 0 0.9rem;
    border: var(--hairline);
    border-radius: var(--radius-md);
    background: var(--surface);
    font-family: inherit;
    font-size: var(--type-md);
    color: var(--ink);
    transition: border-color var(--motion-fast), background-color var(--motion-fast);
}

.fb-field:hover {
    border-color: var(--ink-faint);
}

.fb-field:focus {
    border-color: var(--pen);
    background-color: var(--paper-raised);
}
