/* ============================================================================
   DS-03 PRECISION MODULAR GRID  ·  Quillfield Tutoring
   Sheet 2 of 3 — modules, the signature focus rule, registration ticks,
   controls, forms, the subscribe band and the indexed sitemap footer.

   FINGERPRINT ROWS THIS SHEET CARRIES
     signature effect: rule highlight on focus ·
     signature CSS: CSS grid, 1px rules and registration ticks ·
     indexed sitemap footer
   ========================================================================= */

/* =============================== the module ============================== */

/* Every content block on the site is a .mod. A module is a rectangle bounded
   by 1px rules, indexed by a two-digit number, and it is the unit the whole
   design is assembled from.

   THE SIGNATURE EFFECT LIVES IN THE NEXT TWO RULES AND NEEDS NO JAVASCRIPT.
   :focus-within turns the 1px bounding rules from the secondary accent to the
   highlight the moment anything inside the module takes focus. A keyboard
   user therefore always sees which module they are in, whether or not the
   script loaded. Section 15 warns that the focus effect must not delay
   access, and that is answered by the effect having nothing to delay. */
.mod {
  position: relative;
  border: 1px solid var(--frame);
  border-radius: 8px;
  background: var(--surface);
  padding: var(--s5);
  min-width: 0;
  transition: border-color var(--t);
}

.mod:focus-within { border-color: var(--high); }

@media (min-width: 768px) { .mod { padding: var(--s6); } }

/* The sliding rule is a refinement on top of that, and only a refinement.
   rules.ts measures the focused control and writes its centre into --rule-y;
   this paints a short highlight bar at that height on the left edge of the
   module. It is a pseudo-element, so assistive technology never sees it, and
   it takes no pointer events, so it can never sit in front of a control. With
   the script absent the bar simply never appears and the border change above
   still carries the whole effect on its own. */
.mod::after {
  content: "";
  position: absolute;
  left: -1px;
  top: 0;
  width: 3px;
  height: 28px;
  background: var(--high);
  opacity: 0;
  transform: translateY(calc(var(--rule-y, 0px) - 14px));
  transition: transform var(--t), opacity var(--t);
  pointer-events: none;
}

.mod[data-rule="on"]::after { opacity: 1; }

/* a module that is purely structural and draws no rules of its own */
.mod--bare { border: 0; padding: 0; }
.mod--bare::after { display: none; }

/* the two-digit index every module carries */
.mod__no {
  display: block;
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  margin-bottom: var(--s3);
}
.mod__no b {
  font-family: var(--mono);
  font-weight: 400;
  color: var(--high-ink);
}

.mod__head { margin-bottom: var(--s5); }
.mod__body > * + * { margin-top: var(--s4); }

/* ========================== registration ticks =========================== */

/* Twelve 1px ticks across the full width of the strip, one for each column of
   the grid, drawn entirely by a repeating gradient. They are static: nothing
   here animates at any motion setting, which is exactly the animation-off
   state the specification names. */
.reg {
  height: 8px;
  border-top: 1px solid var(--second);
  background-image: repeating-linear-gradient(
    to right,
    var(--ink) 0 1px,
    transparent 1px 100%
  );
  background-size: calc(100% / 12) 5px;
  background-repeat: repeat-x;
  background-position: 0 0;
}

.reg--soft {
  background-image: repeating-linear-gradient(
    to right,
    var(--second) 0 1px,
    transparent 1px 100%
  );
}

/* a single tick used to mark the start of a run of metadata */
.tick {
  display: inline-block;
  width: 12px;
  height: 1px;
  background: var(--high);
  vertical-align: middle;
  margin-right: var(--s2);
}

/* =============================== typography ============================== */

.eyebrow {
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 var(--s4);
}

.lede {
  font-size: 1.0625rem;
  line-height: 1.6;
  max-width: var(--measure);
}
@media (min-width: 768px) { .lede { font-size: 1.1875rem; } }

.meta {
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--ink-soft);
}

.label {
  font-family: var(--sans);
  font-size: 0.875rem;
  line-height: 1.35;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 600;
}

/* A display numeral. At this size the highlight clears the 3:1 that large
   text needs, which is why the highlight is allowed to be the ink here and
   nowhere below 1.75rem. */
.numeral {
  font-family: var(--sans);
  font-weight: 700;
  font-size: clamp(1.75rem, 4vw, 3rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--high);
}

.rule-top { border-top: 1px solid var(--second); padding-top: var(--s5); }
.rule-bottom { border-bottom: 1px solid var(--second); padding-bottom: var(--s5); }

/* ================================ controls =============================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: var(--control);
  padding: var(--s3) var(--s5);
  border: 1px solid transparent;
  border-radius: 0;
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--t), color var(--t), border-color var(--t);
}

/* The specification sets a minimum horizontal padding of 24px on the primary
   action, which is --s5 on each side. */
.btn--primary {
  background: var(--action);
  color: #ffffff;
  padding-inline: var(--s5);
}
.btn--primary:hover { background: var(--action-hover); color: #ffffff; }
.btn--primary:active { background: var(--action-active); color: #ffffff; }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--ink);
}
.btn--ghost:hover { background: var(--ink); color: var(--page); }

.btn--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--second);
  padding-inline: var(--s4);
}
.btn--quiet:hover { border-color: var(--high); color: var(--high-ink); }

/* At 375px two actions share a row only when each is at least 148px wide.
   Below that the flex basis cannot be met, so they stack with a 12px gap. */
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
}
.actions > .btn { flex: 1 1 148px; }
@media (min-width: 414px) { .actions > .btn { flex: 0 0 auto; } }

/* an inline link that reads as part of the grid rather than as prose */
.jump {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-family: var(--mono);
  font-size: 0.9375rem;
  color: var(--action);
  text-decoration: underline;
  min-height: var(--control);
}
.jump:hover { color: var(--high-ink); }

/* ================================= forms ================================= */

.field { display: block; margin-bottom: var(--s5); }
.field:last-child { margin-bottom: 0; }

.field__label {
  display: block;
  font-family: var(--sans);
  font-size: 0.875rem;
  line-height: 1.35;
  font-weight: 600;
  letter-spacing: 0.04em;
  margin-bottom: var(--s2);
}

.field__hint {
  display: block;
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--ink-soft);
  margin-bottom: var(--s2);
}

.field__req { color: var(--high-ink); }

/* The optional marker was a class with no rule anywhere in the three
   stylesheets, so "(optional)" rendered in the label's own 600 weight and read
   as emphatic as "(required)"; the telephone field had been written with
   .meta instead, which meant one page marked the same thing two ways. One
   rule, one markup. */
.field__opt { color: var(--ink-soft); font-weight: 400; }

.input,
.select,
.textarea {
  display: block;
  width: 100%;
  min-height: var(--control);
  padding: var(--s3) var(--s4);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 0;
  font-family: var(--mono);
  font-size: 1rem;
  line-height: 1.5;
  transition: border-color var(--t);
}

.textarea { min-height: 168px; resize: vertical; }

/* The select keeps the native control. Its scrollWidth reports the width of
   its widest option rather than its rendered box, which is a measurement
   artefact and not an overflow; max-width keeps the rendered box in column. */
.select { max-width: 100%; }

.input:hover, .select:hover, .textarea:hover { border-color: var(--high); }

.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--high-ink);
  border-width: 2px;
}

.field__error {
  display: block;
  min-height: 1px;
  margin-top: var(--s2);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--high-ink);
}

/* The status region is in the markup from first paint and reserves no height
   until it holds text, so announcing a result moves nothing above it. */
.form__status {
  margin-top: var(--s5);
  font-family: var(--mono);
  font-size: 0.9375rem;
  line-height: 1.6;
}
.form__status:empty { margin-top: 0; }
.form__status[data-state="ok"] {
  border-left: 3px solid var(--action);
  padding-left: var(--s4);
}
.form__status[data-state="error"] {
  border-left: 3px solid var(--high);
  padding-left: var(--s4);
}

/* ====================== the subscribe form, on ground ==================== */

/* This form used to live in a full-bleed ink section of its own between the
   end of main and the footer. The specification names a labelled subscribe
   form with a privacy link and a visible result region as one of the footer's
   four content zones, and that arrangement left 404.html -- the one page
   carrying no such section -- with three zones and no subscribe route at all.
   The form is a footer zone now, on the page ground, so the ink treatment and
   the inverted input, button, link and status colours are gone with it. */
.sub-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s4);
}
.sub-form .field__label { flex: 1 1 100%; margin-bottom: 0; }
.sub-form .input { flex: 1 1 220px; }
.sub-form .btn { flex: 0 0 auto; }

/* The form is a flex row, so the three block-level children that follow the
   input and the button have to be told to take a whole row of their own.
   Without this they try to sit on the same line and the zone reflows the
   moment a status message arrives. */
.sub-form .field__error,
.sub-form .form__status,
.sub-form__note { flex: 1 1 100%; margin-top: 0; }

/* The form's own gap is the only separation these rows need. Both of these
   carry a 16px bottom margin from the contact page, where they sit as block
   siblings with nothing between them, and leaving it here counted the same
   space twice while both were still empty. */
.sub-form .field__error,
.sub-form .form__status { margin-bottom: 0; }

/* ======================= the indexed sitemap footer ====================== */

/* Four content zones, not a dark rectangle added after the page: the brand
   with what the business does, the full indexed sitemap with the Book action,
   where to find the business, and the subscribe form. The registration rule at
   the top is this design's signature device -- twelve ticks, one per column,
   the same motif the section heads carry.

   Padding is 48px below 768 and 64px from 768: the specification's 40-56px
   mobile and 64-96px desktop ranges. It was 64px top and 32px bottom at every
   width, and 32px met neither range. */
.foot {
  background: var(--foot);
  color: #ffffff;
  border-top: 1px solid var(--foot);
  padding-block: var(--s7);
}

/* The zones carry their own grid rather than the page .grid, because .grid's
   column gap is the --gut ladder and at 1024 and above that is 48px against
   the specification's 24-40px footer column gap. 32px sits inside it at every
   width from 768 up; below 768 the single column makes the 24px row gap the
   group gap the specification asks for. */
.foot__zones {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s5);
  margin-top: var(--s6);
}

/* 13px labels against 16px useful text. Every text in this footer used to
   measure 13-15px, so there was no hierarchy between the two to read. */
.foot__h {
  font-family: var(--sans);
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 600;
  margin: 0 0 var(--s4);
  padding-bottom: var(--s3);
  border-bottom: 1px solid rgba(255, 255, 255, 0.28);
}

.foot__sum {
  margin: var(--s3) 0 0;
  font-size: 1rem;
  line-height: 1.6;
  max-width: 46ch;
}
.foot__line {
  margin: 0 0 var(--s2);
  font-size: 1rem;
  line-height: 1.5;
}

/* Nine links in two columns on a handset, which is the most the specification
   allows there, and three from 768 where the row has the width for them. */
.foot__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--s5);
}

.foot__link {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: var(--control);
  text-decoration: none;
  color: #ffffff;
  font-family: var(--mono);
  font-size: 1rem;
}
.foot__link:hover { color: var(--select); }
.foot__link[aria-current="page"] { color: var(--select); }
.foot__link .idx__no { color: var(--foot-soft); font-size: 0.8125rem; }

.foot__act { margin: var(--s5) 0 0; }

/* .hours is also a page module on contact.html, so the label-against-text
   sizes are scoped to the footer rather than changed underneath it. */
.foot .hours { margin: var(--s4) 0 0; }
.foot .hours dt { font-size: 0.8125rem; letter-spacing: 0.06em; text-transform: uppercase; }
.foot .hours dd { font-size: 1rem; }

/* The subscribe zone is the footer's second band, and the rule across the top
   of it is the deliberate divider between the two. */
.foot__z--sub {
  border-top: 1px solid rgba(255, 255, 255, 0.28);
  padding-top: var(--s6);
}

.foot__bottom {
  margin-top: var(--s7);
  padding-top: var(--s5);
  border-top: 1px solid rgba(255, 255, 255, 0.28);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  justify-content: space-between;
  align-items: baseline;
}

@media (min-width: 768px) {
  .foot { padding-block: var(--s8); }
  .foot__zones {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--s6);
    row-gap: var(--s7);
  }
  .foot__z--map,
  .foot__z--sub { grid-column: 1 / -1; }
  .foot__cols { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .foot__zones {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--s6);
  }
  .foot__z--brand { grid-column: 1 / span 3; }
  .foot__z--map { grid-column: 5 / span 4; }
  .foot__z--find { grid-column: 9 / span 4; }

  /* The subscribe band takes the full twelve and splits them itself, so the
     input is a reading-width field rather than a 1184px one. */
  .foot__z--sub {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: auto 1fr;
    column-gap: var(--s6);
    row-gap: var(--s4);
    align-items: start;
  }
  .foot__z--sub .foot__h { grid-column: 1 / span 5; grid-row: 1; margin-bottom: 0; }
  .foot__z--sub .foot__sum { grid-column: 1 / span 5; grid-row: 2; margin-top: 0; }
  .foot__z--sub .sub-form { grid-column: 7 / span 6; grid-row: 1 / span 2; margin-top: 0; }
}

/* ============================= reduced motion ============================ */

/* The animation-off state the specification names, word for word: all modules
   are visible immediately and registration ticks remain static. Nothing on
   this site reveals on scroll, so visible-immediately is the only state that
   has ever existed here and there is no reveal to switch off. The ticks are a
   painted gradient and have no animation to stop.

   The sliding rule still MOVES under this setting, because it is a position
   indicator rather than a decoration, and hiding it would take information
   away from the person most likely to be using it. What is removed is the
   travel: with the transition gone it jumps straight to the focused control. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ===================== Recommendation #5 footer ground ===================
   The footer sits on #211E38 with white text. Everything that was ink or
   muted ink on the pale ground is restated here, and the controls that would
   lose their 3:1 against the dark ground take the selected fill as an edge. */
.foot a:not(.btn) { color: #ffffff; }
.foot a:not(.btn):hover { color: var(--select); }
.foot .meta, .foot dt, .foot .field__label, .foot .sub-form__note, .foot .foot__bottom { color: var(--foot-soft); }
.foot .field__error, .foot .form__status { color: #ffffff; }
.foot .hours dd { color: #ffffff; }
.foot .btn--primary { border-color: var(--select); }
.foot .btn--ghost { color: #ffffff; border-color: #ffffff; }
.foot .btn--ghost:hover { background: #ffffff; color: var(--foot); }
.foot .reg {
  border-top-color: rgba(255, 255, 255, 0.28);
  background-image: repeating-linear-gradient(to right, var(--foot-soft) 0 1px, transparent 1px 100%);
}
.foot :focus-visible { outline-color: var(--select); }
.foot .emb [fill="#191825"] { fill: #ffffff; }
.foot .emb [fill="#241D6D"] { fill: var(--select); }

/* Literata's tall ascenders sit a two-line h2 on top of the lede that follows
   it at line-height 1.02, so a lede directly under a heading gets air. */
h2 + .lede:not([class*="mt-"]) { margin-top: var(--s3); }
