/*
 * Front-end styles for the shortcode output.
 *
 * Scope: only what a shortcode emits and cannot be reached from the Elementor
 * editor. The approved page design is built in Elementor; everything here is a
 * plain selector and easy to override from there.
 *
 * SIZING: rem, on the 62.5% root convention this theme uses — 1rem = 10px.
 * If `html { font-size: 62.5% }` is ever removed from the theme, every size
 * here inflates by 1.6x. That is a theme-level dependency, not a local one.
 */

/* ------------------------------------------------------------------
 * YUVEZZI brand palette
 * ------------------------------------------------------------------ */

:root {
	/*
	 * Brand fuchsia, for text and links.
	 */
	--tp-fuchsia: #a50776;

	/*
	 * Button fuchsia — now the SAME value as the brand fuchsia above.
	 *
	 * It used to be #AB0B7B, sampled from the already-approved JetFormBuilder
	 * submit button on the site. The Figma styles sheet (node 1:4) settles it:
	 * both the filled and the outlined button are #A50776 there, and #AB0B7B
	 * appears nowhere in the design. The split was a guess made without access
	 * to the file, and the file now says there is only one fuchsia.
	 *
	 * The alias is kept rather than collapsed into --tp-fuchsia so the two roles
	 * stay separable if the client ever does want a distinct button colour.
	 */
	--tp-fuchsia-button: #a50776;
	--tp-fuchsia-button-hover: #8f0966;
	--tp-green: #58b947;
	--tp-green-ada: #52a540;
	--tp-charcoal: #2f415b;
	--tp-charcoal-tint: #eaecee;

	/* Tints derived from the brand colours, for badge backgrounds only. */
	--tp-fuchsia-tint: #f7e6f1;
	--tp-green-tint: #eaf5e7;
	--tp-amber-tint: #fdf3d9;
	--tp-amber-text: #7a5200;

	/* Matches the approved submit button. */
	--tp-font: "Figtree", sans-serif;
}

/*
 * ACCESSIBILITY NOTE — read before recolouring anything below.
 *
 * Neither brand green may be used for the small text in these components:
 *
 *   YUVEZZI Green #58B947  ~2.3:1 on white. Fails at every size.
 *   ADA Green     #52A540  ~3.1:1 on white. Large text ONLY — 18.66px bold,
 *                          or 24px regular. Status badges are neither.
 *
 * The Complete badge therefore uses NEITHER of them. It is #2E6A4F on #E7F0EA,
 * a darker green picked for the badge, which measures 5.49:1 and passes. The
 * rule above is about the two brand greens and still stands: do not swap this
 * for #58B947 or #52A540 because they are "the" green.
 *
 * The In progress badge is #6E7A88 on #F0F1EC — 3.85:1, which does NOT clear
 * 4.5:1 for text this size. It is the approved palette, so it ships; raised with
 * the client rather than silently corrected. #5C6775 reaches 4.6:1 on the same
 * background and is near-indistinguishable, if a fix is wanted.
 *
 * Fuchsia #A50776 (~7.4:1 on white) and Charcoal #2F415B (~9.9:1) are both
 * safe for body-size text.
 */

/* ------------------------------------------------------------------
 * One family, everywhere
 *
 * Figtree on every block this plugin emits, and on everything inside them.
 *
 * TWO REASONS THIS IS NOT AS SIMPLE AS IT LOOKS, both learned the hard way:
 *
 *   1. INHERITANCE LOSES TO ANY DIRECT RULE. Setting the family on the wrapper
 *      does nothing for an <h2> inside it, because a theme rule targeting
 *      headings beats an inherited value no matter how specific the wrapper
 *      selector is. Hence `, .x *` on every root rather than the root alone.
 *   2. `!important`, because the components sit in Elementor containers whose
 *      typography rules carry a higher specificity than a plugin stylesheet can
 *      reach. Without it, /no-access/ and the buttons rendered in the theme's
 *      font while this rule sat there looking correct.
 *
 * This is the ONE property enforced this way. Size, weight and colour all stay
 * overridable from the editor; the typeface is a brand constant, not a page
 * author's choice.
 *
 * An explicit list rather than `[class^="tp-"]`: these components are dropped
 * into pages next to widgets this stylesheet has no business restyling, and a
 * wildcard would eventually catch one.
 *
 * ADDING A COMPONENT? Add its root class here, with its `*` twin.
 * ------------------------------------------------------------------ */

.tp-bypass-banner, .tp-bypass-banner *,
.tp-doctor-header, .tp-doctor-header *,
.tp-patient-header, .tp-patient-header *,
.tp-start-over-form, .tp-start-over-form *,
.tp-notice, .tp-notice *,
.tp-status,
.tp-survey-action,
.tp-patient-count,
.tp-capped,
.tp-empty-state, .tp-empty-state *,
.tp-stats, .tp-stats *,
.tp-no-access, .tp-no-access *,
.tp-submit-row, .tp-submit-row *,
.tp-draft-notice,
.tp-filter-autocomplete,
.tp-filter-autocomplete::placeholder,
.tp-doctor-overview, .tp-doctor-overview *,
.tp-overview-actions, .tp-overview-actions *,
.tp-view, .tp-view *,
.tp-logout,
.tp-button,
.jet-form-builder__submit,
.dynamic-required-star {
	font-family: var(--tp-font) !important;
}

/*
 * Administrator bypass banner. Loud on purpose — it exists to stop "I can open
 * the dashboard directly" being mistaken for a broken guard.
 */
.tp-bypass-banner {
	position: fixed;
	inset: auto 0 0 0;
	z-index: 99999;
	padding: 1rem 1.6rem;
	background: #1d2327;
	color: #fff;
	font-size: 1.3rem;
	line-height: 1.5;
	text-align: center;
}

/*
 * One line: "Dr. Alicia Vance · Cascade Eye Associates".
 *
 * Wraps rather than truncates — on a narrow screen the practice must stay
 * readable. A shared front desk may hold several doctors' links, and this line
 * is the only thing saying whose list is open.
 */
.tp-doctor-header {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.8rem;
}

.tp-doctor-header .tp-doctor-name {
	color: var(--tp-charcoal);
	font-weight: 700;
}

.tp-doctor-header .tp-doctor-sep {
	color: var(--tp-charcoal);
	opacity: 0.5;
}

.tp-doctor-header .tp-practice-name {
	color: var(--tp-charcoal);
	opacity: 0.75;
}

/* ------------------------------------------------------------------
 * Survey page header
 *
 * Which patient this is, and the way out of a bad start. Sits above the form.
 * ------------------------------------------------------------------ */

.tp-patient-header {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 1.6rem;
	padding: 2rem 0;
	border-top: 1px solid var(--tp-charcoal-tint);
	border-bottom: 1px solid var(--tp-charcoal-tint);
}

.tp-patient-header__id {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
}

.tp-patient-header .tp-patient-number {
	color: var(--tp-charcoal);
	font-family: var(--tp-font);
	font-size: 2.4rem;
	font-weight: 700;
	line-height: 1.2;
}

/*
 * Charcoal at 75% rather than a lighter grey: this line carries the doctor's
 * name and which questionnaire is open, which is how staff catch that they are
 * in the wrong record. It is secondary to the patient number, not decorative.
 */
.tp-patient-header .tp-patient-meta {
	color: var(--tp-charcoal);
	font-family: var(--tp-font);
	font-size: 1.6rem;
	line-height: 1.3;
	opacity: 0.75;
}

.tp-patient-header .tp-patient-sep {
	opacity: 0.6;
}

/*
 * The button is in its own form, which is a block element and would otherwise
 * take the full row width and push the layout apart.
 */
.tp-start-over-form {
	margin: 0;
	flex: 0 0 auto;
}

.tp-notice {
	padding: 1.2rem 1.6rem;
	margin: 0 0 1.6rem;
	border-left: 0.4rem solid currentColor;
	border-radius: 2px;
	font-size: 1.6rem;
}

.tp-notice-success {
	color: var(--tp-charcoal);
	background: var(--tp-green-tint);
}

.tp-notice-error {
	color: var(--tp-fuchsia);
	background: var(--tp-fuchsia-tint);
}

/* ------------------------------------------------------------------
 * Buttons
 *
 * Metrics come from the Figma styles sheet (node 1:4, "Button styles"), which
 * draws the pair as ONE size: 153 x 48, 7px radius, Figtree Bold 20px, centred.
 * Filled is #A50776 on white text; outlined is white with a 2px #A50776 border
 * and #A50776 text.
 *
 * This replaces the earlier metrics, which were sampled off the approved
 * JetFormBuilder submit button and made the secondary deliberately smaller
 * (2.2rem vs 1.8rem, unequal optical padding). The design file does not draw
 * them that way, so the sampled values are gone.
 *
 * `min-width` rather than `width`: 153px fits "Add patient" and "Sign out" but
 * not "Save as draft", and a fixed width would clip it. The height is fixed, so
 * the two still line up on a row whatever the label.
 * ------------------------------------------------------------------ */

/*
 * Shared geometry. EVERY button in the program is this box — only the fill and
 * the text colour tell them apart.
 *
 * The list is deliberately exhaustive rather than a single utility class: the
 * buttons are emitted by five different shortcodes and one by JetFormBuilder,
 * so there is no one class they all carry. Adding a new button means adding it
 * here.
 *
 *   .tp-button                  Add patient
 *   .tp-logout                  Sign out, Save draft, Start over, Export CSV
 *   .jet-form-builder__submit   Save — JetFormBuilder's own
 *
 * `box-sizing: border-box` is what makes the fixed height and the padding agree:
 * 4.8rem total, 1.2rem of padding top and bottom, leaving 2.4rem for a 2rem line
 * of Figtree. Change one of the three and the text stops sitting centred.
 *
 * `min-width`, not `width`. 15.3rem fits "Add patient" and "Sign out"; "Save as
 * draft" and "Export CSV" need more, and a fixed width would clip them.
 */
:root {
	/*
	 * The button spec, as tokens.
	 *
	 * They exist because the metrics have to be written twice — once as a plain
	 * selector for the plugin's own buttons, and once at higher specificity to
	 * reach JetFormBuilder's Save past Elementor. Two literal copies of nine
	 * values is two copies to keep in step, and they would not stay in step.
	 * Change a number here and both rules move together.
	 */
	--tp-button-height: 4.8rem;
	--tp-button-min-width: 15.3rem;
	--tp-button-padding: 1.2rem 2rem;
	--tp-button-font-size: 2rem;
	--tp-button-radius: 7px;
	--tp-button-border-width: 0.2rem;
}

.tp-logout,
.tp-button,
.jet-form-builder__submit {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	min-width: var(--tp-button-min-width);
	height: var(--tp-button-height);
	padding: var(--tp-button-padding);
	border: var(--tp-button-border-width) solid var(--tp-fuchsia-button);
	border-radius: var(--tp-button-radius);
	font-family: var(--tp-font);
	font-size: var(--tp-button-font-size);
	font-style: normal;
	font-weight: 700;
	line-height: normal;
	text-align: center;
	text-decoration: none;
	transition: all 0.3s;
}

/*
 * Save — JetFormBuilder's own button, given the primary treatment so it matches
 * Add patient rather than whatever the form editor last set.
 */
.jet-form-builder__submit {
	background: var(--tp-fuchsia-button);
	color: #fff;
	cursor: pointer;
}

/*
 * The same geometry again, at three classes, ONLY to reach Save.
 *
 * Elementor styles that button through `.elementor-element-<id> .jet-form-builder__submit`
 * — two classes, and its stylesheet loads after this one, so the plain rule
 * above loses the tie and Save keeps whatever size the page author last set. It
 * rendered taller than Save as draft and sat lower on the row.
 *
 * Three classes beats two. This is the reason to reach for specificity rather
 * than `!important`: the page author can still override it from Elementor by
 * being more specific in turn, which `!important` would have taken away.
 *
 * Everything here is a token — see :root above. Do not paste literal values in.
 */
.jet-form-builder__submit-wrap.tp-submit-row .jet-form-builder__submit,
.jet-form-builder__action-button-wrapper.tp-submit-row .jet-form-builder__submit {
	box-sizing: border-box;
	min-width: var(--tp-button-min-width);
	width: auto;
	height: var(--tp-button-height);
	min-height: 0;
	max-height: none;
	padding: var(--tp-button-padding);
	border-width: var(--tp-button-border-width);
	border-radius: var(--tp-button-radius);
	font-family: var(--tp-font);
	font-size: var(--tp-button-font-size);
	font-weight: 700;
	line-height: normal;
	/*
	 * The alignment complaint itself. Elementor's button rules can set a top
	 * margin, and a flex row centres what it is given — a button with 12px above
	 * it is centred 6px low. Zeroed, and pinned to the row's centre line rather
	 * than left to whatever align-self it inherited.
	 */
	margin: 0;
	align-self: center;
}

.jet-form-builder__submit:hover,
.jet-form-builder__submit:focus {
	background: #fff;
	border-color: var(--tp-fuchsia-button);
	color: var(--tp-fuchsia-button);
}

/*
 * Sign out — outlined.
 *
 * Outlined rather than filled so it stays visibly secondary to Add Patient on
 * the same screen. It still has to be easy to find: on a shared front desk,
 * signing out is what stops the next person inheriting the open session.
 */
/*
 * !important on the colour, for the same reason .tp-button carries one:
 * Elementor's link-colour rules are more specific than a plugin stylesheet, and
 * Sign out and Export CSV are anchors. Without it they render in whatever the
 * theme paints links, not #A50776.
 *
 * Both states need it. Marking only the resting colour would let Elementor win
 * on hover, where the button fills fuchsia — theme-coloured text on a fuchsia
 * fill, which at worst is invisible.
 */
.tp-logout {
	background: #fff;
	color: var(--tp-fuchsia-button) !important;
}

/* The other half of the swap: outlined hovers into the filled primary. */
.tp-logout:hover,
.tp-logout:focus {
	background: var(--tp-fuchsia-button);
	border-color: var(--tp-fuchsia-button);
	color: #fff !important;
	text-decoration: none;
}

/*
 * Start over — identical to Sign out, by request.
 *
 * Inherits everything from .tp-logout: fuchsia outline, fuchsia text, and the
 * hover that fills fuchsia with white text. Only `cursor` is set here, because
 * this one is a real <button> where Sign out is an <a>.
 *
 * WHAT THIS GIVES UP: it used to be charcoal. Fuchsia is the colour of the
 * actions we want taken — Add patient, Save — and this one wipes every answer on
 * the record. Colour was the last thing distinguishing it, the size having been
 * equalised by the button spec. The typed confirmation is now the only guard
 * against a mis-aimed click, on a button that sits at the top of the page.
 *
 * Requested deliberately. Noted so nobody restores charcoal thinking it was an
 * oversight.
 */
.tp-start-over {
	cursor: pointer;
}

/* The hover state fills the button, so focus needs its own mark or it vanishes. */
.tp-logout:focus-visible {
	outline: 0.2rem solid var(--tp-charcoal);
	outline-offset: 0.2rem;
}

/*
 * Add Patient — the primary action. Geometry is shared with .tp-logout above.
 */
.tp-button {
	background: var(--tp-fuchsia-button);
	/*
	 * !important because Elementor's link-colour rules outrank a plugin
	 * stylesheet, and the failure mode is fuchsia text on a fuchsia background
	 * — an invisible button, not a merely off-brand one.
	 */
	color: #fff !important;
}

/*
 * The two buttons trade places on hover.
 *
 * Filled becomes outlined, outlined becomes filled — each one hovers into the
 * other's resting state. The pair reads as one control with two settings rather
 * than as two unrelated buttons, and the change is unmissable at arm's length,
 * which a darkening fill is not on a front-desk monitor.
 *
 * COLOUR ONLY. The two now share a box, so there is nothing else to swap — but
 * the rule still holds if that changes: never animate geometry on hover, or the
 * button resizes under the cursor and shifts everything beside it.
 *
 * Figma draws only the resting states. The swap is a behaviour the design file
 * does not contradict, so it is kept.
 */
.tp-button:hover,
.tp-button:focus {
	background: #fff;
	border-color: var(--tp-fuchsia-button);
	color: var(--tp-fuchsia-button) !important;
	text-decoration: none;
}

.tp-button:focus-visible {
	outline: 0.2rem solid var(--tp-charcoal);
	outline-offset: 0.2rem;
}

.tp-capped {
	font-family: var(--tp-font);
	font-size: 1.8rem;
	font-weight: 600;
	opacity: 0.8;
}

/*
 * Badges are a soft-cornered rectangle, not a pill. 2.659px is the design file's
 * own value — it looks like a scaling artefact and is reproduced as given rather
 * than rounded, so the next person comparing against Figma finds a match.
 *
 * On the BASE rule, so all three badges share it. The spec named Complete and In
 * progress; Delayed takes the same shape because a third badge with different
 * corners in the same column reads as a rendering fault, not as a distinction.
 */
.tp-status {
	display: inline-block;
	padding: 0.4rem 1.2rem;
	border-radius: 2.659px;
	font-size: 1.3rem;
	font-weight: 600;
	line-height: 1.5;
	white-space: nowrap;
}

/*
 * Design file colours, replacing the charcoal-on-tint these used to be.
 *
 * CONTRAST, MEASURED: 3.85:1. The badge is 1.3rem at 600, which is small text,
 * and AA wants 4.5:1 for that — it clears the 3:1 large-text bar only. Raised
 * with the client rather than silently corrected, because it is the approved
 * palette. #5C6775 on the same background reaches 4.6:1 and is barely
 * distinguishable from #6E7A88 side by side, if a fix is wanted.
 *
 * The greens below are fine: Complete measures 5.49:1.
 */
.tp-status--in_progress {
	color: #6e7a88;
	background: #f0f1ec;
}

/* Delayed is the list sales chases, so it has to be the one that catches the eye. */
.tp-status--delayed {
	color: var(--tp-amber-text);
	background: var(--tp-amber-tint);
}

/*
 * Complete. 5.49:1 — clears AA for small text.
 *
 * Note this IS green text on green, which the design-system note warns against.
 * That warning is about the two BRAND greens (#58B947 at 2.3:1, #52A540 at
 * 3.1:1), neither of which is this colour. #2E6A4F is a darker green chosen for
 * the badge and it passes; the rule stands for the brand pair.
 */
.tp-status--complete {
	color: #2e6a4f;
	background: #e7f0ea;
}

/*
 * Dynamic Table cells. The table itself is styled in Elementor; these only
 * cover what the plugin's own callbacks emit.
 */
/*
 * Two elements wear this class: the <a> that opens a survey to fill in, and the
 * <button> in the review table that opens the read-only modal.
 *
 * The button resets are what make the two look identical. A <button> otherwise
 * arrives with the browser's own chrome — grey fill, border, its own font — and
 * the review table would show a chrome button where every other row shows a
 * link. It is a button rather than a link because it performs an action on this
 * page instead of navigating, and screen readers should say so.
 */
.tp-survey-action {
	padding: 0;
	border: 0;
	background: none;
	color: var(--tp-fuchsia);
	font-family: var(--tp-font);
	font-size: 1.6rem;
	font-weight: 600;
	line-height: inherit;
	text-decoration: none;
	cursor: pointer;
}

.tp-survey-action:hover {
	text-decoration: underline;
}

/*
 * Edit and Resume are the same weight.
 *
 * This modifier used to drop a completed record's action to 400 while an
 * unfinished one stayed at 600, on the reasoning that a finished survey needs
 * less pull. Side by side in one column it read as an inconsistency rather than
 * as emphasis — same colour, same size, different weight.
 *
 * The class is kept as a hook: the two actions still differ in what they do, and
 * something may want to tell them apart later without re-deriving which is
 * which.
 */
.tp-survey-action--complete {
	font-weight: 600;
}

/*
 * First-run empty state. Centred because it is the only thing on the screen
 * when it shows.
 */
.tp-empty-state {
	padding: 4rem 2rem;
	text-align: center;
}

.tp-empty-state__heading {
	margin: 0 0 0.8rem;
	color: var(--tp-charcoal);
	font-family: var(--tp-font);
	font-size: 2.2rem;
	font-weight: 700;
}

.tp-empty-state__text {
	margin: 0 0 2.4rem;
	color: var(--tp-charcoal);
	font-size: 1.6rem;
	opacity: 0.8;
}

/*
 * Save draft. Shares the outlined treatment with Sign out — it is a secondary
 * action next to Submit survey, and must not compete with it. `button` resets
 * are included because this one is a real <button>, not a link.
 */
.tp-save-draft {
	cursor: pointer;
	font-family: var(--tp-font);
}

/* ------------------------------------------------------------------
 * /no-access/
 *
 * The one screen a doctor sees when something has gone wrong, and the one they
 * see after signing out on purpose. Centred, per the approved design.
 *
 * The logo above it is an Elementor image widget, not part of this shortcode —
 * centre that in the editor. This block owns the text only.
 * ------------------------------------------------------------------ */

.tp-no-access {
	max-width: 62rem;
	/*
	 * Centres the block itself as well as its text. Without the auto margins the
	 * copy is centred inside a full-width box, which reads as centred until the
	 * lines get short enough to sit visibly off-axis.
	 */
	margin: 0 auto;
	text-align: center;
}

/*
 * `--tp-charcoal-control` is #394A60 — the Figma variable "YUV Charcoal", not
 * the brand charcoal #2F415B. The token name is narrower than its use has turned
 * out to be: it was introduced for form control borders and now carries this
 * heading too. Same value either way; using the token rather than a fourth
 * literal keeps them from drifting apart.
 *
 * `line-height` is the design file's own 35.103px, reproduced rather than
 * rounded to 1.25 so a later comparison against Figma finds a match.
 */
.tp-no-access__title {
	margin: 0 0 1.6rem;
	color: var(--tp-charcoal-control);
	font-family: var(--tp-font);
	font-size: 2.8rem;
	font-style: normal;
	font-weight: 700;
	line-height: 35.103px;
}

.tp-no-access__text {
	margin: 0;
	color: #000;
	font-family: var(--tp-font);
	font-size: 2.2rem;
	font-style: normal;
	font-weight: 400;
	line-height: 1.2;
}

/*
 * Consecutive paragraphs, if the copy is ever split into more than one. The gap
 * is on the sibling rather than a bottom margin on every paragraph, so the block
 * never ends with trailing space under the last line.
 */
.tp-no-access__text + .tp-no-access__text {
	margin-top: 1.2rem;
}

/* ------------------------------------------------------------------
 * Patient tables — horizontal scroll with a pinned first column
 *
 * Both program tables are JetEngine Dynamic Tables and share their markup:
 *
 *   .jet-dynamic-table-wrapper > table.jet-dynamic-table
 *     thead .jet-dynamic-table__col--<slug>
 *     tbody .jet-dynamic-table__col--<slug>
 *
 * Seven columns on the review dashboard, five on the doctor's own list. Neither
 * fits a phone, and a squashed table is not a readable one — "Cascade Eye
 * Associates" wrapping over four lines makes every row a different height and
 * the whole thing unscannable.
 *
 * So the table keeps its full width and the wrapper scrolls. The first column is
 * pinned, because a row scrolled sideways with no identifier is a row you cannot
 * act on: on the review table that is the doctor, on the doctor's own table it
 * is the patient number. `:first-child` rather than the two column classes —
 * the right answer on both tables, and it survives a column being renamed in
 * JetEngine.
 *
 * NOTE ON `--header`: JetEngine puts `.jet-dynamic-table__row--header` on the
 * BODY rows as well as the header row, so it cannot be used to tell them apart.
 * Everything below keys on `thead` / `tbody`.
 * ------------------------------------------------------------------ */

.jet-dynamic-table-wrapper {
	overflow-x: auto;
	/* Momentum scrolling on iOS, where these are most likely to be read. */
	-webkit-overflow-scrolling: touch;
}

.jet-dynamic-table {
	/*
	 * separate, not collapse. A sticky cell under `border-collapse: collapse`
	 * leaves its borders behind when it moves — the border belongs to the table,
	 * not the cell. Borders are drawn per cell below instead.
	 */
	border-collapse: separate;
	border-spacing: 0;
	width: 100%;
}

/*
 * Cells keep their content on one line. This is what gives the table a width to
 * scroll — without it the columns compress to fit and the scroll never happens,
 * which is the failure this rule exists to prevent.
 */
.jet-dynamic-table__col {
	white-space: nowrap;
	border-bottom: 1px solid var(--tp-charcoal-tint);
}

/*
 * Every table heading on the programme is brand charcoal — the patient tables
 * and the doctor overview alike. Body cells keep the theme's near-black; this is
 * the heading row only.
 *
 * Header cells are also opaque so nothing shows through them, and this rule comes
 * FIRST on purpose: the sticky rule below sets `position: sticky` on the header's
 * first cell, and a later `position: relative` at lower specificity would look
 * like it wins to anyone reading top to bottom. It does not, but nobody should
 * have to work that out.
 *
 * SPECIFICITY: this is 0-1-2 and the patient tables are the theme's to style. If
 * a heading somewhere still renders in the old colour, the theme is winning the
 * cascade and that rule has to change rather than this one growing an
 * `!important`.
 */
.jet-dynamic-table thead th {
	background: #fff;
	color: var(--tp-charcoal);
}

/*
 * The pinned column.
 *
 * No divider on its right edge — removed by request. If the pinned column ever
 * needs to read as separate from the scrolling ones again, use `box-shadow`
 * rather than `border-right`: a shadow sits outside the cell box, where a border
 * would widen the column and shift everything beside it.
 */
.jet-dynamic-table thead th:first-child,
.jet-dynamic-table tbody td:first-child {
	position: sticky;
	left: 0;
	/*
	 * Opaque, and it has to be. A sticky cell with a transparent background
	 * shows the scrolling columns straight through it.
	 */
	background: #fff;
}

/*
 * The header's own pinned cell overlaps both the scrolling header cells and the
 * pinned body column, so it outranks both.
 */
.jet-dynamic-table thead th:first-child {
	z-index: 2;
}

.jet-dynamic-table tbody td:first-child {
	z-index: 1;
}

/* ------------------------------------------------------------------
 * Survey viewer modal
 *
 * Opened by the Open button in the review table. Read-only: questions and
 * answers, no controls that change anything.
 * ------------------------------------------------------------------ */

.tp-view {
	position: fixed;
	inset: 0;
	/*
	 * Above Elementor's sticky headers and JetSmartFilters' own overlays. A
	 * dialog that opens behind the page furniture reads as a broken button.
	 */
	z-index: 100000;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 2.4rem;
}

/* `hidden` on a flex element does nothing without this — display wins. */
.tp-view[hidden] {
	display: none;
}

.tp-view__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(47, 65, 91, 0.55);
}

.tp-view__panel {
	position: relative;
	box-sizing: border-box;
	width: 100%;
	max-width: 72rem;
	/*
	 * The panel scrolls, not the page behind it. A thirty-question survey is
	 * taller than any laptop screen.
	 */
	max-height: 85vh;
	overflow-y: auto;
	padding: 3.2rem;
	border-radius: 7px;
	background: #fff;
	font-family: var(--tp-font);
}

.tp-view__close {
	position: absolute;
	top: 1.2rem;
	right: 1.6rem;
	padding: 0.4rem 1rem;
	border: 0;
	background: none;
	color: var(--tp-charcoal);
	font-family: var(--tp-font);
	font-size: 2.8rem;
	line-height: 1;
	cursor: pointer;
}

.tp-view__title {
	margin: 0 0 0.8rem;
	padding-right: 4rem; /* Clear of the close button. */
	color: var(--tp-charcoal);
	font-size: 2.4rem;
	font-weight: 700;
	line-height: 1.2;
}

.tp-view__meta {
	margin: 0 0 2.4rem;
	color: var(--tp-charcoal);
	font-size: 1.6rem;
	line-height: 1.3;
	opacity: 0.8;
}

.tp-view__section {
	margin: 0 0 2.4rem;
}

.tp-view__section-title {
	margin: 0 0 1.2rem;
	padding-bottom: 0.8rem;
	border-bottom: 1px solid var(--tp-charcoal-tint);
	color: var(--tp-charcoal);
	font-size: 1.8rem;
	font-weight: 700;
	line-height: 1.2;
}

.tp-view__list {
	margin: 0;
}

.tp-view__q {
	margin: 0 0 0.4rem;
	color: var(--tp-charcoal);
	font-size: 1.6rem;
	font-weight: 700;
	line-height: 1.3;
}

.tp-view__a {
	margin: 0 0 1.6rem;
	color: #000;
	font-size: 1.8rem;
	line-height: 1.3;
}

.tp-view__multi {
	margin: 0;
	padding-left: 2rem;
}

/* An unanswered question, so it reads as blank rather than as missing. */
.tp-view__none {
	opacity: 0.5;
}

.tp-view__empty {
	color: var(--tp-charcoal);
	font-size: 1.8rem;
}

/*
 * Applied to <body> while the dialog is open. Without it the page scrolls
 * underneath, and closing the dialog leaves the reader somewhere else in the
 * table than where they opened it.
 */
.tp-view-open-body {
	overflow: hidden;
}

/* ------------------------------------------------------------------
 * Doctor overview table — [tp_doctor_overview]
 *
 * Matched to the JetEngine Dynamic Tables it sits beside on the review
 * dashboard, so the two read as one screen rather than two components that
 * happen to be stacked.
 *
 * Values taken from those tables as rendered, with ONE deliberate difference:
 * cells are padded 10px rather than 20px. This table is five narrow columns of
 * counts, and the roomier padding pushes it past the fold for no gain.
 *
 * Scoped to .tp-doctor-overview throughout. The selectors below would otherwise
 * reach every Dynamic Table on the page — including the patient tables, which
 * are the theme's to style, not this plugin's.
 * ------------------------------------------------------------------ */

/*
 * Export CSV above the doctor overview. Right-aligned so it sits over the
 * numeric columns rather than crowding the Doctor heading.
 */
.tp-overview-actions {
	display: flex;
	justify-content: flex-end;
	margin-bottom: 1.6rem;
}

.tp-doctor-overview .jet-dynamic-table__col {
	box-sizing: border-box;
	padding: 10px;
	background: #fff;
	font-size: 1.8rem;
	line-height: 2.4rem;
	text-align: center;
	vertical-align: middle;
	white-space: nowrap;
}

/*
 * Heading colour is set on the shared thead rule below, for every table.
 *
 * `text-transform` rather than typing the labels in capitals: the shortcode
 * keeps readable headings ("Last login", not "LAST LOGIN"), which is what the
 * CSV export and the screen reader get, and the capitals stay a presentation
 * choice that can be dropped in one line.
 */
.tp-doctor-overview thead th.jet-dynamic-table__col {
	font-weight: 700;
	text-transform: uppercase;
	border-bottom: 1px solid var(--tp-charcoal-tint);
}

.tp-doctor-overview tbody td.jet-dynamic-table__col {
	/*
	 * rgb(45,47,52). Taken from the rendered tables and NOT --tp-charcoal — a
	 * near-black the theme uses for table BODY text, distinct from the brand
	 * charcoal the headings now use. Written literally because matching the
	 * neighbouring table is the point; promoting it to a token would imply it is
	 * part of the palette, which it is not.
	 */
	color: #2d2f34;
	font-weight: 400;
	/* rgb(228,228,228) — the row hairline on the rendered tables. */
	border-bottom: 1px solid #e4e4e4;
}

/* ------------------------------------------------------------------
 * Filter autocomplete
 *
 * assets/tp-filters.js puts an <input list> in front of the Doctor and Practice
 * JetSmartFilters selects, because JetSmartFilters has no autocomplete filter
 * type and those two lists grow with the programme.
 * ------------------------------------------------------------------ */

/*
 * Matched to the native JetSmartFilters selects it sits beside — Rx filled and
 * Survey status — so the four filters read as one row of controls rather than
 * three of one kind and one of another.
 *
 * Values are those selects as rendered: 1px #ccc, square corners, black text,
 * 1.8rem/normal, 10px padding all round. The greys here are the theme's control
 * colours, not brand tokens: #ccc is not --tp-charcoal-control (#394A60) and the
 * text is #000, which the design file does specify as primary body copy.
 * Written literally because matching the neighbouring control is the point.
 */
.tp-filter-autocomplete {
	display: block;
	box-sizing: border-box;
	/*
	 * The selects are intrinsically 132px wide; this fills its container
	 * instead. Deliberate — 132px truncates "Cascade Eye Associates" to about
	 * six characters, which a select gets away with and a text box you are
	 * typing into does not. Set a width on the Elementor widget if the four
	 * controls need to be identical.
	 */
	width: 100%;
	/*
	 * !important on these two only. Elementor styles form controls inside its
	 * widget containers through a more specific selector than this one, and it
	 * won both the padding and the text colour — the box rendered tighter than
	 * the selects beside it, in the theme's grey rather than black.
	 *
	 * Kept to the two declarations that were actually being overridden. Adding
	 * it to the whole block would take the control out of the page author's
	 * hands for properties nothing is fighting over.
	 */
	padding: 1.1rem !important;
	border: 1px solid #ccc;
	border-radius: 0;
	background: #fff;
	color: #000 !important;
	font-family: var(--tp-font);
	font-size: 1.8rem;
	font-weight: 400;
	line-height: normal;
	text-align: start;
}

/*
 * Focus is NOT in the matched spec — the values above are the selects at rest.
 * Kept because a text box with no focus ring is unusable by keyboard, and this
 * is the one filter you type into.
 *
 * Thicker border rather than an outline, and safe to do here because
 * box-sizing is border-box: the extra 2px comes out of the padding, so the
 * control keeps its size and the filter row does not shift.
 */
.tp-filter-autocomplete:focus {
	border-width: 3px;
	border-color: var(--tp-charcoal-control);
	outline: none;
}

/*
 * Full black, not a faded one.
 *
 * `opacity: 1` is doing real work here: Firefox applies its own opacity to
 * placeholder text, so setting the colour alone still renders it grey there and
 * black in Chrome — the same control looking different per browser.
 *
 * Note this makes the placeholder read as a value rather than a prompt. That is
 * right for these two filters specifically: the placeholder is "All doctors" /
 * "All practices", which IS the current state when the box is empty, not a hint
 * about what to type.
 */
.tp-filter-autocomplete::placeholder {
	color: #000 !important;
	opacity: 1;
}

/*
 * The real select. Still in the DOM, still the thing JetSmartFilters reads and
 * writes — only hidden.
 *
 * NOT `display: none`. A hidden form control is skipped by some browsers in ways
 * that are hard to predict, and this one has to keep working: it is the filter.
 * Clipped instead, which leaves it a real, functional control that simply
 * occupies no space.
 */
.tp-filter-native-hidden {
	position: absolute !important;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ------------------------------------------------------------------
 * Review dashboard tiles
 * ------------------------------------------------------------------ */

.tp-stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
	gap: 0;
	border: 1px solid var(--tp-charcoal-tint);
}

.tp-stat {
	display: flex;
	flex-direction: column;
	gap: 0.8rem;
	padding: 2.4rem;
	border-right: 1px solid var(--tp-charcoal-tint);
}

.tp-stat:last-child {
	border-right: 0;
}

.tp-stat__label {
	color: var(--tp-charcoal);
	font-size: 1.6rem;
	opacity: 0.8;
}

.tp-stat__value {
	color: var(--tp-charcoal);
	font-family: var(--tp-font);
	font-size: 4.2rem;
	font-weight: 400;
	line-height: 1.1;
}

/*
 * Submit row on the survey. The class is added by tp-survey.js once the draft
 * button has been moved in, so it only applies when there are two buttons.
 *
 * The selector is doubled up with JetFormBuilder's own wrapper class on
 * purpose. `.jet-form-builder__submit-wrap` is the same specificity as
 * `.tp-submit-row` and its stylesheet loads after this one, so a single-class
 * selector loses the tie and the buttons fall back to JFB's alignment.
 */
.jet-form-builder__submit-wrap.tp-submit-row,
.jet-form-builder__action-button-wrapper.tp-submit-row {
	display: flex;
	flex-wrap: wrap;
	/*
	 * center is now cosmetic — both buttons are a fixed 4.8rem by the button
	 * spec above, so there is no height difference left to reconcile. It was
	 * `stretch` when the two boxes could differ; kept as an explicit value so a
	 * future button of another height still lines up rather than sitting on the
	 * row's baseline.
	 */
	align-items: center;
	gap: 1.6rem;
}

/*
 * Margin and alignment only. The geometry comes from the shared button rule (and
 * for Save, from the three-class rule that reaches past Elementor); repeating it
 * here is how the two drift apart.
 */
.tp-submit-row > .tp-save-draft,
.tp-submit-row > .jet-form-builder__submit {
	margin: 0;
	align-self: center;
}

/*
 * "Draft saved." — written into the submit row by tp-survey.js.
 *
 * flex-basis: 100% puts it on its own line beneath the buttons rather than
 * beside them, where it would push Save sideways every time it appeared.
 */
.tp-draft-notice {
	flex-basis: 100%;
	margin: 0;
}

/*
 * Two-column form rows — vertical alignment.
 *
 * JetFormBuilder emits a per-field `<style>` element as a sibling of the field
 * row when the block has colour overrides set. It is display:none, but WP's
 * flow layout counts it as a sibling all the same:
 *
 *   :where(.is-layout-flow) > :not(:first-child) { margin-block-start: … }
 *
 * So a column containing that `<style>` pushes its field down by one block gap
 * and a column without one does not — the two labels stop lining up. Zeroing
 * the margin on a row that follows a `<style>` restores the match.
 *
 * WP's rule is inside :where(), which is zero specificity, so a plain selector
 * beats it. No !important needed.
 */
.wp-block-column > style + .jet-form-builder-row,
.wp-block-column > style + * {
	margin-block-start: 0;
}

/*
 * Columns start at the same top edge regardless of what is inside them. Guards
 * the same alignment against a column whose field happens to be taller — a
 * wrapped label, or a visible error message on one side only.
 */
.wp-block-columns > .wp-block-column {
	align-self: flex-start;
}

/* ------------------------------------------------------------------
 * Sub-question
 *
 * A field that qualifies the question above it rather than asking a new one —
 * the multifocal checkbox under Current Correction Strategy. Marked by
 * tp-survey.js, indented here so it reads as subordinate.
 *
 * NOT moved into the option list. That was tried and reverted: the option list
 * carries `data-jfb-sync`, and a foreign node inside it broke JetFormBuilder's
 * conditional engine for the whole field. See the note in tp-survey.js.
 *
 * Its own label is hidden: the checkbox's text already says what it is
 * ("Check here if patient is a multifocal lens wearer"), and a question heading
 * above it would only repeat that.
 * ------------------------------------------------------------------ */

.tp-nested-field {
	margin-top: -0.8rem;
	padding-left: 3rem;
}

.tp-nested-field .jet-form-builder__label {
	display: none;
}

/* ------------------------------------------------------------------
 * Survey validation — FALLBACK ONLY
 *
 * The class names here — error-message, field-has-error, was-validated,
 * dynamic-required-star — are the site's, not this plugin's. The ECP survey at
 * /ecp-survey/ established them, the theme's survey.css styles them, and
 * assets/tp-validate.js emits them so one stylesheet dresses both surveys.
 *
 * THIS BLOCK IS THE FALLBACK for a page where survey.css is not loaded. Every
 * rule is written without !important and at low specificity, so survey.css —
 * which uses !important throughout — wins wherever both apply. Do not "fix"
 * these by adding !important: that inverts the relationship and the program
 * survey stops matching the ECP survey.
 *
 * The one thing survey.css cannot do is show the message on a page it is not
 * loaded on, because its base rule hides `.error-message` outright. That is
 * what the display rules below are for.
 * ------------------------------------------------------------------ */

:root {
	/*
	 * survey.css uses #E90725. Matched here rather than picking a different red,
	 * so a page missing that stylesheet degrades to the same colour rather than
	 * to a visibly different design. Figma uses the same value.
	 */
	--tp-error: #e90725;

	/*
	 * "YUV Charcoal" in the Figma variables, and a DIFFERENT token from
	 * --tp-charcoal (#2F415B). The design file uses both on purpose: #2F415B for
	 * subheads and labels, #394A60 for every form control border and mark. Nine
	 * points apart, and both clear 4.5:1 — do not collapse them into one.
	 */
	--tp-charcoal-control: #394a60;
}

/*
 * Hidden until the form has been submitted once. Complaining about empty fields
 * on a form nobody has filled in yet is noise, and it trains staff to ignore the
 * colour before they have entered anything.
 *
 * FIGTREE, overriding the design file. Figma draws this one component in Roboto
 * Flex Regular 14px — deliberately, in all four state panels — and it shipped
 * that way in v1.34.0. It is Figtree now because the brief is one family
 * everywhere, and a single 14px line in a second family is not worth being the
 * exception that has to be explained every time someone reads this file.
 *
 * Worth knowing: the theme's survey.css sets Roboto Flex here with !important,
 * so on any page that loads it the error message stays Roboto Flex regardless of
 * this rule. To make the change stick everywhere, that stylesheet has to change
 * too — it is not in this repo.
 */
.jet-form-builder-row .error-message {
	display: none;
	margin: 1rem 0 0;
	color: var(--tp-error);
	font-family: var(--tp-font);
	font-size: 1.4rem;
	font-weight: 400;
	line-height: 1.2;
}

.was-validated .field-has-error .error-message {
	display: block;
}

/*
 * The question text AND its option labels go red together.
 *
 * This reverses an earlier decision. The previous rule recoloured the question
 * only, on the reasoning that a red ring on ten unselected options reads as ten
 * errors rather than as one unanswered question. Figma's "Error states" and
 * "Focus + error states" panels are explicit: every option label is #E90725 and
 * every control carries a red border. The design file is the client's call and
 * it is unambiguous, so it wins.
 *
 * Worth raising with them anyway — it is the one place where the design asks for
 * more red than the error justifies. It is a legibility opinion, not an
 * accessibility failure: #E90725 on white is 4.8:1 and clears AA at these sizes.
 */
.was-validated .field-has-error .jet-form-builder__label-text,
.was-validated .field-has-error .jet-form-builder__field-label span,
.was-validated .field-has-error .jet-form-builder__field-label {
	color: var(--tp-error);
}

/*
 * Radio and checkbox controls — the four states Figma draws.
 *
 * Box is identical in all four: 21px square, 2px radius, #EAECEE fill. Only the
 * border changes.
 *
 *   default        1px  #394A60
 *   error          2px  #E90725
 *   focus          3px  #394A60
 *   focus + error  3px  #E90725
 *
 * Border WIDTH carries the focus state, which is why focus is not an outline
 * here — an outline would sit outside the 21px box and break the alignment with
 * the label text beside it.
 *
 * Radios keep 50% radius: Figma draws these panels with checkboxes only, and a
 * square radio would misreport single-choice as multi-choice.
 */
.jet-form-builder-row input[type="checkbox"],
.jet-form-builder-row input[type="radio"] {
	appearance: none;
	flex: none;
	box-sizing: border-box;
	width: 2.1rem;
	height: 2.1rem;
	border: 1px solid var(--tp-charcoal-control);
	border-radius: 2px;
	background: var(--tp-charcoal-tint);
}

.jet-form-builder-row input[type="radio"] {
	border-radius: 50%;
}

.jet-form-builder-row input[type="checkbox"]:checked,
.jet-form-builder-row input[type="radio"]:checked {
	background: var(--tp-charcoal-control);
	box-shadow: inset 0 0 0 0.3rem var(--tp-charcoal-tint);
}

.jet-form-builder-row input[type="checkbox"]:focus-visible,
.jet-form-builder-row input[type="radio"]:focus-visible {
	border-width: 3px;
	outline: none;
}

.was-validated .field-has-error input[type="checkbox"],
.was-validated .field-has-error input[type="radio"] {
	border-width: 2px;
	border-color: var(--tp-error);
}

.was-validated .field-has-error input[type="checkbox"]:focus-visible,
.was-validated .field-has-error input[type="radio"]:focus-visible {
	border-width: 3px;
}

.was-validated .field-has-error input[type="text"],
.was-validated .field-has-error input[type="date"],
.was-validated .field-has-error input[type="email"],
.was-validated .field-has-error input[type="number"],
.was-validated .field-has-error input[type="tel"],
.was-validated .field-has-error textarea,
.was-validated .field-has-error select {
	border-color: var(--tp-error);
}

.dynamic-required-star {
	color: var(--tp-error);
}
