/* ==========================================================================
   GrantHolleran.com — main.css
   Stage 1: Design system foundation.

   Structure of this file:
     1.  Design tokens (:root custom properties)
     2.  Reset & base elements
     3.  Accessibility utilities
     4.  Typography
     5.  Layout primitives
     6.  Links
     7.  Buttons
     8.  Cards
     9.  Forms
     10. Media / images
     11. Site header & primary navigation
     12. Site footer
     13. Homepage
     14. Books
     15. Resources
     16. Companion hubs
     17. Updates
     18. About
     19. Contact

   Conventions:
     - Mobile is the default. Media queries only ever add, never undo.
     - Use tokens (var(--space-4)) rather than raw values.
     - No !important, no deep nesting, no framework.
   ========================================================================== */


/* ==========================================================================
   1. Design tokens
   ========================================================================== */

:root {

  /* ---- Colour ----------------------------------------------------------
     Palette intent: warm, mature, readable. A deep blue-green carries the
     brand, a warm off-white replaces stark white, and a single terracotta
     accent is used sparingly for emphasis.

     Contrast against --color-background (checked, WCAG AA):
       text          #24282D  ~14.5:1
       text-muted    #5A6169  ~5.7:1
       heading       #16303D  ~12.4:1
       primary       #1B4C5C  ~8.9:1
       accent        #B4552D  ~4.7:1  (AA normal text; used mainly for
                                       emphasis and large/UI elements)
     White on --color-primary is ~9.4:1. White on --color-accent is ~4.9:1.
     -------------------------------------------------------------------- */

  --color-background:     #fbf9f6;  /* warm off-white page ground          */
  --color-surface:        #ffffff;  /* cards, inputs                       */
  --color-surface-sunken: #f3eee7;  /* alternating sections, swatch wells  */

  --color-text:           #24282d;  /* body copy                           */
  --color-text-muted:     #5a6169;  /* captions, help text, meta           */
  --color-heading:        #16303d;  /* headings                            */
  --color-text-inverse:   #ffffff;  /* text on primary/dark surfaces       */

  --color-primary:        #1b4c5c;  /* brand deep blue-green               */
  --color-primary-hover:  #123642;  /* darker for hover/active             */
  --color-primary-soft:   #e6eef1;  /* tinted background wash              */

  --color-accent:         #b4552d;  /* warm terracotta, used sparingly     */
  --color-accent-hover:   #8f4223;
  /* Darker accent for accent-coloured *text*. #b4552d clears AA on the
     page background (4.67:1) but not on the sunken surface (4.25:1), so
     text always uses this shade instead. */
  --color-accent-text:    var(--color-accent-hover);

  --color-border:         #e3dcd2;  /* decorative hairlines, card edges    */
  /* Boundaries of interactive controls (inputs, secondary buttons). Meets
     the 3:1 non-text contrast requirement against the control fill, the
     page background, and the sunken surface alike. */
  --color-border-strong:  #8f8172;

  --color-focus:          #b4552d;  /* focus ring — visible on light and
                                       on the deep primary alike           */

  --color-error:          #a1301f;
  --color-error-soft:     #fbecea;
  --color-success:        #1f6b4a;

  /* ---- Typography -------------------------------------------------------
     System font stacks only at this stage: fast, familiar, no network cost.
     --font-heading is kept separate so a display face can be swapped in
     later without touching every rule.
     -------------------------------------------------------------------- */

  --font-sans:
    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans", sans-serif;
  --font-serif:
    Georgia, "Iowan Old Style", "Times New Roman", Times, serif;

  --font-body:    var(--font-sans);
  --font-heading: var(--font-sans);

  /* Type scale. Fluid between roughly 375px and 1200px viewports.
     Body lands at 16px on phones and 18px on large screens.
     Headings are deliberately restrained so an H1 does not swallow a
     375px phone screen. */

  --text-sm:    0.9375rem;                                    /* 15px      */
  --text-base:  clamp(1rem,      0.958rem + 0.18vw, 1.125rem); /* 16→18    */
  --text-lead:  clamp(1.125rem,  1.045rem + 0.34vw, 1.3125rem);/* 18→21    */
  --text-h4:    clamp(1.125rem,  1.085rem + 0.17vw, 1.25rem);  /* 18→20    */
  --text-h3:    clamp(1.25rem,   1.152rem + 0.42vw, 1.5rem);   /* 20→24    */
  --text-h2:    clamp(1.5rem,    1.304rem + 0.83vw, 2rem);     /* 24→32    */
  --text-h1:    clamp(1.875rem,  1.522rem + 1.51vw, 2.75rem);  /* 30→44    */

  --leading-tight:  1.2;   /* large headings   */
  --leading-snug:   1.3;   /* small headings   */
  --leading-normal: 1.6;   /* body copy        */
  --leading-loose:  1.7;   /* long-form prose  */

  --weight-normal:   400;
  --weight-medium:   500;
  --weight-semibold: 600;
  --weight-bold:     700;

  --tracking-tight: -0.01em;

  /* ---- Spacing ----------------------------------------------------------
     A 4px-based scale. Use these rather than arbitrary values.
     -------------------------------------------------------------------- */

  --space-1: 0.25rem;   /*  4px */
  --space-2: 0.5rem;    /*  8px */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.5rem;    /* 24px */
  --space-6: 2rem;      /* 32px */
  --space-7: 3rem;      /* 48px */
  --space-8: 4rem;      /* 64px */

  /* Vertical rhythm between major page sections. Fluid so mobile stays
     comfortable without wasting screen height, and desktop breathes. */
  --space-section: clamp(2.5rem, 1.75rem + 3.2vw, 5rem);

  /* ---- Layout ---------------------------------------------------------- */

  --width-content: 1140px;  /* general page content                        */
  --width-narrow:  720px;   /* readable prose measure (~70 characters)     */
  --measure:       68ch;    /* max line length for standalone text blocks  */

  /* Horizontal gutter. Never below 16px, never wasteful on wide screens. */
  --gutter: clamp(1rem, 0.75rem + 1.2vw, 2rem);

  /* ---- Borders, radii, elevation --------------------------------------- */

  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 10px;

  --border-width: 1px;

  /* Restrained elevation — soft, low-contrast, never "dashboard". */
  --shadow-sm: 0 1px 2px rgb(35 40 45 / 0.06);
  --shadow-md: 0 2px 6px rgb(35 40 45 / 0.07), 0 1px 2px rgb(35 40 45 / 0.05);

  /* ---- Interaction ------------------------------------------------------ */

  --touch-target: 2.75rem;  /* 44px minimum tap target */
  --focus-width:  3px;
  --focus-offset: 2px;
  --transition:   150ms ease;
}


/* ==========================================================================
   2. Reset & base elements
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Margins are opted into via the layout primitives, not inherited by
   default — this keeps vertical rhythm predictable. */
body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd,
ul, ol {
  margin: 0;
}

html {
  /* Respect the user's browser font size; never set a px root size. */
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  background-color: var(--color-background);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  min-height: 100svh;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

ul, ol {
  padding-inline-start: var(--space-5);
}

/* Lists used as layout scaffolding (cards, nav) opt out of markers. */
.list-bare {
  list-style: none;
  padding-inline-start: 0;
}

hr {
  border: 0;
  border-block-start: var(--border-width) solid var(--color-border);
  margin-block: var(--space-6);
}

code, pre, kbd, samp {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;
  font-size: 0.9em;
}

code {
  background-color: var(--color-surface-sunken);
  border-radius: var(--radius-sm);
  padding: 0.15em 0.35em;
}

table {
  border-collapse: collapse;
  width: 100%;
}


/* ==========================================================================
   3. Accessibility utilities
   ========================================================================== */

/* One consistent focus treatment everywhere. Never remove it without
   providing an equally visible replacement. */
:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius-sm);
}

/* Visually hidden but available to screen readers. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Skip link — hidden until focused, then pinned to the top-left. */
.skip-link {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: 100;
  transform: translateY(calc(-100% - var(--space-4)));
  background-color: var(--color-primary);
  color: var(--color-text-inverse);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  transition: transform var(--transition);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* Honour the user's motion preference across the whole site. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ==========================================================================
   4. Typography
   ========================================================================== */

h1, h2, h3, h4, h5, h6 {
  color: var(--color-heading);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  /* Keeps long headings from breaking awkwardly on narrow screens. */
  text-wrap: balance;
  overflow-wrap: break-word;
}

h1 {
  font-size: var(--text-h1);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

h2 {
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-tight);
}

h3 { font-size: var(--text-h3); }

h4 {
  font-size: var(--text-h4);
  font-weight: var(--weight-semibold);
}

/* Heading appearance without heading semantics. Use when an element must
   *look* like a given level but sit elsewhere in the document outline —
   e.g. a card title that reads as an H2 but is correctly an H3. Never use
   these to replace a real heading. */
.h1, .h2, .h3, .h4 {
  color: var(--color-heading);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  text-wrap: balance;
}

.h1 {
  font-size: var(--text-h1);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.h2 {
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-tight);
}

.h3 { font-size: var(--text-h3); }

.h4 {
  font-size: var(--text-h4);
  font-weight: var(--weight-semibold);
}

p {
  text-wrap: pretty;
}

/* Opening paragraph of a page or section. */
.lead {
  color: var(--color-text-muted);
  font-size: var(--text-lead);
  line-height: var(--leading-normal);
}

.text-small { font-size: var(--text-sm); }
.text-muted { color: var(--color-text-muted); }

/* Constrain any standalone block of prose to a comfortable measure. */
.prose {
  max-width: var(--measure);
}

.prose p,
.prose li {
  line-height: var(--leading-loose);
}

.prose > * + * {
  margin-block-start: var(--space-4);
}

.prose h2,
.prose h3 {
  margin-block-start: var(--space-6);
}

/* Small uppercase label above a heading. Tracking is widened because
   uppercase text at small sizes is hard to read without it. */
.eyebrow {
  color: var(--color-accent-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}


/* ==========================================================================
   5. Layout primitives
   A deliberately small toolkit. Compose these rather than writing
   one-off margins and widths.
   ========================================================================== */

/* Centred page container with responsive gutters. */
.container {
  width: 100%;
  max-width: calc(var(--width-content) + (var(--gutter) * 2));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Same gutters, narrower measure — for articles and text-led pages. */
.container--narrow {
  max-width: calc(var(--width-narrow) + (var(--gutter) * 2));
}

/* Vertical rhythm between major page sections. */
.section {
  padding-block: var(--space-section);
}

.section--sunken {
  background-color: var(--color-surface-sunken);
}

/* Owl selector: even spacing between siblings, no margin on the edges.
   Override per-instance with --flow-space. */
.stack > * + * {
  margin-block-start: var(--flow-space, var(--space-4));
}

.stack--tight > * + * { --flow-space: var(--space-2); }
.stack--loose > * + * { --flow-space: var(--space-6); }

/* Horizontal group that wraps cleanly instead of overflowing. */
.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap, var(--space-3));
  align-items: center;
}

.cluster--between { justify-content: space-between; }

/* Responsive grid with no breakpoints. Columns appear only when each
   one can still hold a readable ~17rem of content, so cards stack on
   phones automatically. min() keeps it from overflowing narrow screens. */
.grid {
  display: grid;
  gap: var(--gap, var(--space-5));
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(100%, var(--grid-min, 17rem)), 1fr)
  );
}

/* Wider minimum for content that needs more room per column. */
.grid--wide { --grid-min: 22rem; }

/* Centre a block and cap its width. */
.center-column {
  margin-inline: auto;
  max-width: var(--measure);
}

/* Introductory text at the head of a full-width section. Caps the measure
   without nesting a second .container and doubling the gutters. */
.section__intro {
  max-width: var(--width-narrow);
}


/* ==========================================================================
   6. Links
   Body links stay obviously link-like: colour plus underline, never
   colour alone.
   ========================================================================== */

a {
  color: var(--color-primary);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color var(--transition), text-decoration-color var(--transition);
}

a:hover {
  color: var(--color-accent-hover);
  text-decoration-thickness: 2px;
}

a:active {
  color: var(--color-primary-hover);
}

/* For links inside running prose: slightly softer underline until hover. */
.prose a {
  text-decoration-color: color-mix(in srgb, var(--color-primary) 45%, transparent);
}

.prose a:hover {
  text-decoration-color: currentColor;
}

/* Opt-out for links that are visually buttons or cards. */
.link-plain {
  text-decoration: none;
}

.link-plain:hover {
  text-decoration: underline;
}


/* ==========================================================================
   7. Buttons
   Understated, rectangular with a small radius — not pills.
   Every variant meets the 44px touch target.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-3) var(--space-5);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-md);
  background-color: transparent;
  color: inherit;
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--transition),
    border-color var(--transition),
    color var(--transition);
}

.btn:hover {
  text-decoration: none;
}

.btn--primary {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}

.btn--primary:hover {
  background-color: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  color: var(--color-text-inverse);
}

.btn--secondary {
  background-color: var(--color-surface);
  border-color: var(--color-border-strong);
  color: var(--color-primary);
}

.btn--secondary:hover {
  background-color: var(--color-primary-soft);
  border-color: var(--color-primary);
  color: var(--color-primary-hover);
}

/* Lowest-emphasis action — reads as a link but keeps the tap target. */
.btn--quiet {
  padding-inline: var(--space-3);
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.btn--quiet:hover {
  background-color: var(--color-primary-soft);
  color: var(--color-primary-hover);
  text-decoration: underline;
}

.btn[disabled],
.btn[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: 0.55;
  /* Do not shift colours on hover when disabled. */
  pointer-events: none;
}

/* Full-width button. Useful for the primary action in a form or a
   single call to action on a phone. */
.btn--block {
  display: flex;
  width: 100%;
}

/* Below ~480px a lone primary action is easier to hit at full width.
   Applied only where the author opts in via .btn--block\@sm. */
@media (max-width: 29.99em) {
  .btn--block\@sm {
    display: flex;
    width: 100%;
  }
}


/* ==========================================================================
   8. Cards
   A single generic card. Book, resource and article cards will be built
   on top of this in later stages — do not fork it.
   ========================================================================== */

.card {
  display: flex;
  flex-direction: column;
  background-color: var(--color-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden; /* clips the image to the rounded corners */
}

/* Hover lift is intentionally omitted: it adds motion without meaning.
   Interactive cards get their affordance from the link inside them. */

.card__image {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background-color: var(--color-surface-sunken);
}

/* Portrait crop, for book covers and author photography later on. */
.card__image--portrait {
  aspect-ratio: 3 / 4;
}

.card__content {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  /* Pushes .card__actions to the bottom so cards in a row align. */
  flex-grow: 1;
}

.card__title {
  font-size: var(--text-h4);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.card__text {
  color: var(--color-text-muted);
  margin: 0;
}

.card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: auto;
  padding-block-start: var(--space-2);
}


/* ==========================================================================
   9. Forms
   Inputs are 16px+ so mobile browsers do not zoom on focus.
   ========================================================================== */

.form {
  max-width: var(--width-narrow);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.field + .field {
  margin-block-start: var(--space-5);
}

.label {
  color: var(--color-heading);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
}

/* Marks a required field visually; the input still carries `required`
   so assistive technology is not relying on the asterisk. */
.label__required {
  color: var(--color-accent-text);
  font-weight: var(--weight-normal);
}

/* Short helper text under a label. Associate it with the input using
   aria-describedby. */
.field__hint {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.input,
.textarea,
.select {
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-3) var(--space-4);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  color: var(--color-text);
  font-family: inherit;
  /* Never below 16px: smaller values trigger zoom-on-focus in iOS Safari. */
  font-size: max(1rem, var(--text-base));
  line-height: var(--leading-normal);
  transition: border-color var(--transition), box-shadow var(--transition);
}

.input::placeholder,
.textarea::placeholder {
  color: var(--color-text-muted);
  opacity: 1; /* Firefox dims placeholders by default */
}

.input:hover,
.textarea:hover,
.select:hover {
  border-color: var(--color-primary);
}

.textarea {
  min-height: 8rem;
  resize: vertical;
}

/* Error state. Colour alone is never the only signal — pair this with
   a .field__error message and aria-invalid on the control. */
.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select[aria-invalid="true"] {
  border-color: var(--color-error);
  background-color: var(--color-error-soft);
}

.field__error {
  color: var(--color-error);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.field__error::before {
  content: "⚠ ";
}

.form__actions {
  margin-block-start: var(--space-6);
}

/* Checkbox / radio row with a comfortably sized hit area. */
.field--check {
  flex-direction: row;
  align-items: flex-start;
  gap: var(--space-3);
}

.field--check input {
  /* 24px minimum, per WCAG 2.5.8 Target Size (Minimum). */
  width: 1.5rem;
  height: 1.5rem;
  margin-block-start: 0.1em;
  accent-color: var(--color-primary);
  flex-shrink: 0;
}

.field--check label {
  font-weight: var(--weight-normal);
}


/* ==========================================================================
   10. Media / images
   The site assumes most production imagery is WebP served from absolute
   Cloudflare R2 URLs. Nothing here depends on the file living locally,
   on its format, or on its intrinsic size.
   ========================================================================== */

img,
picture,
video,
svg {
  display: block;
  max-width: 100%;
  height: auto;
}

/* Where width/height attributes are present, the browser reserves the
   right space from the intrinsic ratio and no layout shift occurs. */
img[width][height] {
  height: auto;
}

/* Fluid image that keeps its natural proportions. */
.media {
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  background-color: var(--color-surface-sunken);
}

/* Fixed-ratio crop, for banners and cards where the source images vary. */
.media--crop {
  aspect-ratio: var(--ratio, 16 / 9);
  object-fit: cover;
}

.media--square   { --ratio: 1 / 1; }
.media--portrait { --ratio: 3 / 4; }

/* Book covers: never crop, never stretch. */
.media--cover {
  width: auto;
  max-height: 22rem;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
}

.figure {
  margin: 0;
}

.figure__caption {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-block-start: var(--space-2);
}


/* ==========================================================================
   11. Site header & primary navigation

   PROGRESSIVE ENHANCEMENT
   The navigation is visible and usable with no JavaScript at all: the nav
   is displayed, and .nav-toggle is hidden. main.js adds `js-nav` to <html>
   before first paint, and only then does the collapsible small-screen
   behaviour exist. If the script fails for any reason the class is never
   added, so the nav simply stays open — it is never hidden behind a button
   that cannot work. See assets/js/main.js.

   The single source of truth for the open state is the toggle's
   aria-expanded attribute, which CSS reads directly.

   Sticky-footer scaffolding also lives here: body already carries
   min-height: 100svh from the base layer, so making it a column flex
   container keeps a short page's footer at the bottom of the viewport.
   ========================================================================== */

body {
  display: flex;
  flex-direction: column;
}

.site-main {
  flex-grow: 1;
}

.site-header {
  border-block-end: var(--border-width) solid var(--color-border);
  background-color: var(--color-background);
  /* Deliberately not sticky — the header scrolls with the document. */
}

.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-4);
}

/* ---- Wordmark ---------------------------------------------------------- */

.site-brand {
  color: var(--color-heading);
  font-size: 1.25rem;
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.1;
  text-decoration: none;
  /* Keeps the name on one line so it never crowds the menu button. */
  white-space: nowrap;
}

.site-brand:hover {
  color: var(--color-primary);
  text-decoration: none;
}

@media (min-width: 46em) {
  .site-brand {
    font-size: 1.375rem;
  }
}

/* ---- Menu button ------------------------------------------------------- */

/* Hidden unless JavaScript has enhanced the page. */
.nav-toggle {
  display: none;
}

.js-nav .nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  color: var(--color-primary);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: background-color var(--transition), border-color var(--transition);
}

.js-nav .nav-toggle:hover {
  background-color: var(--color-primary-soft);
  border-color: var(--color-primary);
}

/* Open state is conveyed by the tinted button and the visible panel, not
   by animation — the control still reads correctly with motion reduced. */
.js-nav .nav-toggle[aria-expanded="true"] {
  background-color: var(--color-primary-soft);
  border-color: var(--color-primary);
}

/* CSS hamburger: three bars drawn from the element and its pseudo
   elements. No icon library, no image file. */
.nav-toggle__icon {
  position: relative;
  display: block;
  width: 1.125rem;
  height: 2px;
  background-color: currentColor;
  border-radius: 1px;
}

.nav-toggle__icon::before,
.nav-toggle__icon::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background-color: currentColor;
  border-radius: 1px;
}

.nav-toggle__icon::before { top: -6px; }
.nav-toggle__icon::after  { top: 6px; }

/* ---- Navigation -------------------------------------------------------- */

/* Mobile default: a full-width block beneath the wordmark. */
.site-nav {
  flex-basis: 100%;
}

/* Collapsed only once JavaScript is confirmed present. */
.js-nav .site-nav {
  display: none;
}

.js-nav .nav-toggle[aria-expanded="true"] + .site-nav {
  display: block;
}

.site-nav__list {
  display: flex;
  flex-direction: column;
  border-block-start: var(--border-width) solid var(--color-border);
}

.site-nav__link {
  display: block;
  /* Full-width target: easy to hit one-handed on a phone. */
  padding: var(--space-3) var(--space-2);
  min-height: var(--touch-target);
  border-block-end: var(--border-width) solid var(--color-border);
  color: var(--color-primary);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

.site-nav__link:hover {
  background-color: var(--color-primary-soft);
  color: var(--color-primary-hover);
  text-decoration: none;
}

/* Current page. Weight and an accent bar carry the state, so it does not
   depend on colour alone. */
.site-nav__link[aria-current="page"] {
  box-shadow: inset 3px 0 0 0 var(--color-accent);
  color: var(--color-heading);
  font-weight: var(--weight-bold);
}

/* ---- Desktop navigation ------------------------------------------------
   Content-driven: 46em is the point at which the wordmark and five links
   sit comfortably on one line. main.js matches this value. */

@media (min-width: 46em) {
  .site-header__inner {
    flex-wrap: nowrap;
    padding-block: var(--space-5);
  }

  /* The toggle is irrelevant at this width, with or without JavaScript. */
  .nav-toggle,
  .js-nav .nav-toggle {
    display: none;
  }

  .site-nav,
  .js-nav .site-nav,
  .js-nav .nav-toggle[aria-expanded="true"] + .site-nav {
    display: block;
    flex-basis: auto;
  }

  .site-nav__list {
    flex-direction: row;
    align-items: center;
    gap: var(--space-5);
    border-block-start: 0;
  }

  .site-nav__link {
    padding: var(--space-2) 0;
    min-height: 0;
    border-block-end: 2px solid transparent;
  }

  .site-nav__link:hover {
    background-color: transparent;
    border-block-end-color: var(--color-border-strong);
  }

  .site-nav__link[aria-current="page"] {
    box-shadow: none;
    border-block-end-color: var(--color-accent);
  }
}


/* ==========================================================================
   12. Site footer
   Compact and author-oriented. Deliberately not a multi-column sitemap.
   ========================================================================== */

.site-footer {
  border-block-start: var(--border-width) solid var(--color-border);
  background-color: var(--color-surface-sunken);
  padding-block: var(--space-7);
}

.site-footer__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.site-footer__name {
  color: var(--color-heading);
  font-size: 1.125rem;
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
}

.site-footer__tagline {
  color: var(--color-text-muted);
  margin-block-start: var(--space-2);
  max-width: 34ch;
}

.footer-nav__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
}

.footer-nav__link {
  display: inline-block;
  /* Clears the 44px target the rest of the site keeps, even though these
     links wrap inline rather than filling a row. */
  padding-block: var(--space-3);
  color: var(--color-primary);
  text-decoration: none;
}

.footer-nav__link:hover {
  color: var(--color-primary-hover);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.site-footer__copyright {
  border-block-start: var(--border-width) solid var(--color-border);
  padding-block-start: var(--space-5);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

@media (min-width: 46em) {
  .site-footer__inner {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-7);
  }

  .site-footer__copyright {
    /* Full-width rule beneath both columns. */
    flex-basis: 100%;
  }
}


/* ==========================================================================
   13. Homepage
   Section-level composition for the homepage. Everything here builds on
   the primitives above rather than restyling them. Rhythm comes from
   whitespace and alternating surfaces, not from boxing every section.
   ========================================================================== */

/* ---- Hero --------------------------------------------------------------
   Slightly more air above than a standard section, but deliberately not
   tall: the featured book should still be within easy reach on a phone. */

.hero {
  padding-block: calc(var(--space-section) * 1.15) var(--space-section);
}

.hero__title {
  max-width: 18ch;
}

.hero__lead {
  max-width: 54ch;
}

.hero__actions {
  margin-block-start: var(--space-2);
}

@media (min-width: 46em) {
  .hero__title {
    /* A touch larger than the base H1, still inside the approved scale. */
    font-size: clamp(2.5rem, 1.9rem + 1.6vw, 3.25rem);
  }
}

/* ---- Featured book -----------------------------------------------------
   Two columns on wider screens: the case for the book, then what it
   covers. No cover image is fabricated when no real asset exists. */

.featured {
  display: grid;
  gap: var(--space-6);
}

@media (min-width: 56em) {
  .featured {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: var(--space-8);
    align-items: start;
  }
}

/* Small label above a title: "Featured book", "In development". Weight and
   letterspacing carry it, so it never depends on colour alone. */
.label-eyebrow {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ---- Topic list --------------------------------------------------------
   What a book covers, at a glance. Not a chapter list. */

.topic-list {
  display: grid;
  gap: var(--space-3);
  padding-inline-start: 0;
  list-style: none;
}

.topic-list > li {
  border-block-start: var(--border-width) solid var(--color-border);
  padding-block-start: var(--space-3);
}

/* ---- Two-up prose blocks ----------------------------------------------
   Used where a section carries two related ideas that are not products,
   so they get headings and space rather than card borders. */

.split {
  display: grid;
  gap: var(--space-6);
}

@media (min-width: 46em) {
  .split {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-7);
  }
}

.split__heading {
  font-size: var(--text-h3);
}

/* ---- Series cards ------------------------------------------------------
   Built on .card. auto-fit means further titles can be added later with no
   redesign, and two titles do not stretch across the full width. */

.series-grid {
  --grid-min: 18rem;
}

@media (min-width: 62em) {
  .series-grid {
    /* Keeps a two-book row from becoming uncomfortably wide. */
    max-width: 60rem;
  }
}

.card__status {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ---- Standalone text link ----------------------------------------------
   A link that ends a section and acts as a call to action, rather than
   sitting inline in a sentence. Keeps the site's 44px target without
   becoming a button, and stays flush with the text above it (unlike
   .btn--quiet, which is padded like a control). */

.link-cta {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  font-weight: var(--weight-semibold);
}

/* ---- Closing author note ---------------------------------------------- */

.author-note {
  max-width: 60ch;
}


/* ==========================================================================
   14. Books

   Built to grow. The page is composed of two independent patterns:

     .book-feature  one prominent title, cover beside its details
     .book-entry    any number of further titles, stacked

   Adding a third or fourth book means adding another .book-entry, not
   redesigning the page. A published entry can take an optional small
   cover via .book-entry--with-cover; an in-development entry simply has
   no cover and no call to action, and never a placeholder rectangle
   standing in for one.
   ========================================================================== */

.book-feature {
  display: grid;
  gap: var(--space-6);
}

/* Cover beside the details once there is room for both. The columns are
   deliberately uneven: the details carry more weight than the cover. */
@media (min-width: 52em) {
  .book-feature {
    grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
    gap: var(--space-8);
    align-items: start;
  }
}

/* ---- Cover -------------------------------------------------------------
   Remote production media from the R2 origin. Intrinsic width/height are
   set in the markup, and aspect-ratio repeats the same 5:8 here so the
   space is reserved even if the attributes are ever dropped. Never
   cropped, never distorted: the framing comes from CSS only. */

.book-cover {
  display: block;
  width: 100%;
  /* Sized for reading, not for poster impact. */
  max-width: 15rem;
  aspect-ratio: 5 / 8;
  height: auto;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  /* Holds the shape while the image is still arriving. */
  background-color: var(--color-surface-sunken);
  /* Single-column layout: centre the capped cover in the content area
     rather than letting it sit against the left gutter. */
  margin-inline: auto;
}

@media (min-width: 52em) {
  .book-cover {
    max-width: 20rem;
    /* Two columns: the cover leads its own column, so it returns to the
       left edge instead of floating in the middle of that column. */
    margin-inline: 0;
  }
}

/* Small variant, for places that reference a book without selling it.
   Declared after the rules above so it wins at every width.

   The width is definite rather than `100%` on purpose: this variant sits
   in an `auto` grid track on .book-entry--with-cover, and a percentage
   width against an indefinite track resolves to zero, collapsing the
   image. max-width keeps it inside a narrow phone. */
.book-cover--sm {
  width: 7.5rem;
  max-width: 100%;
}

/* ---- Book details ------------------------------------------------------ */

.book__title {
  margin-block-start: var(--space-1);
}

.book__subtitle {
  color: var(--color-text);
  font-size: var(--text-lead);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  max-width: 46ch;
}

.book__author {
  color: var(--color-text-muted);
}

/* ---- Reader-fit list ---------------------------------------------------
   Short statements, not a feature grid. Each one gets its own line and a
   little air so the block stays scannable on a phone. */

.reader-fit {
  display: grid;
  gap: var(--space-3);
  padding-inline-start: 0;
  list-style: none;
  max-width: 56ch;
}

.reader-fit > li {
  border-block-start: var(--border-width) solid var(--color-border);
  padding-block-start: var(--space-3);
}

/* ---- Further titles ----------------------------------------------------
   One block per additional book. Stacking rather than tiling keeps a
   single entry from sprawling, and still reads correctly at four or five. */

.book-entries {
  display: grid;
  gap: var(--space-5);
  max-width: 48rem;
}

.book-entry {
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
  padding: var(--space-5);
}

@media (min-width: 46em) {
  .book-entry {
    padding: var(--space-6);
  }
}

/* Status sits above the title and is always read as text, never conveyed
   by colour alone. */
.book-entry__status,
.book__status {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Sized below the featured book's title so the hierarchy between the
   current title and further ones is never in doubt. */
.book-entry__title {
  font-size: var(--text-h3);
}

/* An unavailable title states so in plain words directly under its
   heading, so it cannot be mistaken for something on sale. */
.book-entry__availability {
  color: var(--color-heading);
  font-weight: var(--weight-semibold);
}


/* ---- Book detail pages -------------------------------------------------
   The per-book pages reuse the patterns above rather than restating them:
   .book-feature for the hero, .book-cover for the cover, .topic-list for
   outcome lists, .reader-fit for audience statements, and .book-entry for
   a related title. Only the pieces below are specific to a detail page,
   so a second book page needs markup, not new CSS. */

/* Breadcrumb. Sits above the hero, quiet enough not to compete with it. */
.breadcrumb {
  font-size: var(--text-sm);
}

.breadcrumb__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-inline-start: 0;
  list-style: none;
}

/* Separator is decorative, so it is drawn in CSS and stays out of the
   accessibility tree. */
.breadcrumb__list > li + li::before {
  content: "/";
  color: var(--color-text-muted);
  margin-inline-end: var(--space-2);
}

/* Both the link and the current label carry the project's 44px target
   height, which also keeps the two vertically aligned with each other and
   with the separator. */
.breadcrumb__list a,
.breadcrumb__current {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
}

.breadcrumb__current {
  color: var(--color-text-muted);
}

/* ---- Format actions ----------------------------------------------------
   The pair of edition links. Side by side where there is room, full width
   on a phone so each is an easy target and neither looks like the
   afterthought. */

.format-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

@media (max-width: 29.99em) {
  .format-actions .btn {
    width: 100%;
  }
}

/* Quiet line beneath the format links, for describing the editions
   without making a claim about price or availability. */
.format-note {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* ---- Topic groups ------------------------------------------------------
   The subject areas a book covers. Columns appear only when each can hold
   a readable block, so the groups stack in reading order on a phone. */

.topic-groups {
  display: grid;
  gap: var(--space-6);
}

@media (min-width: 46em) {
  .topic-groups {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
    gap: var(--space-6) var(--space-7);
  }
}

/* Four items read better as an even 2x2 than as three-plus-one, so this
   variant fixes the column count instead of letting auto-fit decide. */
.topic-groups--pairs {
  grid-template-columns: 1fr;
}

@media (min-width: 46em) {
  .topic-groups--pairs {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.topic-group__heading {
  font-size: var(--text-h4);
  margin-block-end: var(--space-2);
}

.topic-group__text {
  color: var(--color-text-muted);
  max-width: 46ch;
}


/* ==========================================================================
   15. Resources

   The companion-material directory. Everything here is built to hold
   entries that do not exist yet without ever implying that they do:

     .book-entry--with-cover  a book plus its companion hub
     .planned-path            a reserved URL shown as text, never a link
     .resource-note           a quiet aside about future material

   Resource categories reuse .topic-groups from section 14, so adding a
   sixth category or a third book needs markup, not new CSS.
   ========================================================================== */

/* A book entry that carries a small cover beside its text. Stacks first,
   then sits side by side once there is room for both. */
@media (min-width: 34em) {
  .book-entry--with-cover {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--space-5);
    align-items: start;
  }

  /* The stack owl would otherwise add margins between the two columns. */
  .book-entry--with-cover > * + * {
    margin-block-start: 0;
  }

  .book-entry--with-cover .book-cover {
    margin-inline: 0;
  }
}

.book-entry__body > * + * {
  margin-block-start: var(--space-3);
}

/* ---- Planned paths -----------------------------------------------------
   A reserved URL rendered as plain text. Deliberately NOT a link and
   deliberately not styled like one: the destination does not exist yet.
   Long paths wrap inside their box rather than widening the page. */

.planned-path {
  /* Plain text, deliberately. A bordered, filled box reads as a field or a
     control, and these addresses are neither: they are not links and there
     is nothing to click. Weight and the monospace face are enough to mark
     it as an address. */
  color: var(--color-heading);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;
  font-size: 0.9375em;
  font-weight: var(--weight-medium);
  overflow-wrap: anywhere;
}

/* Sits directly under a planned path to say plainly that it is not live. */
.resource-note {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}


/* ==========================================================================
   16. Companion hubs

   The per-book hubs (/travel-tech/, and /cruise-tech/ later). These pages
   serve people who already own the book and typed a short URL from a
   printed page, so they are built for scanning rather than persuading:
   a compact hero, then the topic directory as the centre of gravity.

     .hub-hero    a deliberately short hero
     .callout     the book-versus-site orientation block
     .directory   the topic list, reusing the .grid primitive

   Directory entries are rules-and-text, not boxes. Nothing in here is
   interactive, which matters: the destinations do not exist yet, and a
   page of button-shaped things that cannot be clicked reads as broken.
   ========================================================================== */

/* Deliberately short. Someone who typed this URL off a printed page wants
   the topic list, not an introduction, so the whole block above the
   directory is kept as tight as readability allows. */
.hub-hero {
  padding-block: var(--space-4) var(--space-3);
}

/* Tighter rhythm between the breadcrumb, eyebrow, title, lead and
   orientation line than the default stack spacing. */
.hub-hero .stack > * + * {
  margin-block-start: var(--space-2);
}

.hub-hero__orientation {
  color: var(--color-heading);
  font-weight: var(--weight-semibold);
}

/* ---- Orientation callout ----------------------------------------------
   Distinct from the surrounding page without being a sales panel. */

/* The orientation block sits between the hero and the directory, so its
   section padding is trimmed too. Without this the callout's own slimming
   is cancelled out by the space around it. */
.hub-orientation {
  padding-block: var(--space-4);
}

/* Trimmed to sit closer to the directory. Same component, same border and
   surface: only the padding and heading scale changed. */
.callout {
  border-inline-start: 4px solid var(--color-primary);
  background-color: var(--color-surface);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  max-width: var(--width-narrow);
}

@media (min-width: 46em) {
  .callout {
    padding: var(--space-5);
  }
}

.callout__heading {
  font-size: var(--text-h4);
  line-height: var(--leading-snug);
  margin-block-end: var(--space-2);
}

/* ---- Topic directory ---------------------------------------------------
   Built on .grid. The 20rem minimum keeps descriptions readable: three
   columns at desktop rather than four cramped ones. Entries are marked by
   a rule above each title, which scans quickly and never suggests a
   clickable surface. */

/* The gap above "Find What You Need" is part of the space this page is
   trying not to waste. */
.hub-directory {
  padding-block-start: var(--space-5);
}

.directory {
  --grid-min: 20rem;
  --gap: var(--space-5) var(--space-6);
}

.directory__item {
  border-block-start: 2px solid var(--color-primary);
  padding-block-start: var(--space-3);
}

.directory__title {
  font-size: var(--text-h4);
  margin-block-end: var(--space-2);
}

.directory__text {
  color: var(--color-text-muted);
  max-width: 44ch;
}


/* ==========================================================================
   17. Updates

   The Technology Updates page. Its usual state is "nothing to report", so
   the status line has to read as a calm, deliberate answer rather than as
   an alert or an empty archive. It is set in the ordinary heading colour,
   inside the standard .callout, with no warning colour or icon.
   ========================================================================== */

.status-line {
  color: var(--color-heading);
  font-size: var(--text-lead);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}


/* ==========================================================================
   18. About

   The author hero pairs the introduction with the portrait. The copy leads
   in both DOM order and column width: the portrait is there to balance the
   right-hand space, not to become the subject of the page.
   ========================================================================== */

.author-hero {
  display: grid;
  gap: var(--space-6);
}

/* Two columns once there is room. The copy column is the wider of the two
   on purpose, and both tracks are fr-based rather than `auto`, so the
   portrait's percentage width resolves against a definite track. */
@media (min-width: 52em) {
  .author-hero {
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr);
    gap: var(--space-8);
    align-items: start;
  }
}

/* Holds the reading measure even though the hero is no longer inside
   .section__intro. */
.author-hero__copy {
  max-width: var(--width-narrow);
}

/* ---- Portrait ----------------------------------------------------------
   Square production media from the R2 origin, shown whole. aspect-ratio
   matches the source exactly, so nothing is cropped or stretched, and the
   space is reserved before the image arrives. No filter, overlay, frame,
   or animation: only the site's own corner radius. */

.author-portrait {
  display: block;
  width: 100%;
  /* Compact when stacked, so it never dominates the first phone screen. */
  max-width: 14rem;
  aspect-ratio: 1 / 1;
  height: auto;
  border-radius: var(--radius-lg);
  background-color: var(--color-surface-sunken);
  margin-inline: auto;
}

@media (min-width: 52em) {
  .author-portrait {
    max-width: 20rem;
    margin-inline: 0;
  }
}

/* ---- Author-name note --------------------------------------------------
   Deliberately minor. A hairline and small muted text keep it findable for
   anyone who looks without turning it into a section of its own, and it is
   never styled as a disclaimer or legal notice. */

.name-note {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-4);
  border-block-start: var(--border-width) solid var(--color-border);
  max-width: var(--measure);
}

.name-note__heading {
  color: var(--color-heading);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  margin-block-end: var(--space-1);
}

.name-note__text {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}


/* ==========================================================================
   19. Contact

   The form itself reuses the Stage 1 form primitives (.form, .field,
   .label, .input, .select, .textarea, .field__hint, .form__actions), which
   were built for exactly this and had not been used until now. Only the
   two rules below are specific to the contact page.
   ========================================================================== */

/* Netlify honeypot. Clipped rather than display:none, because a field that
   is display:none or hidden is ignored by some bots and by some browsers'
   autofill, which weakens the trap. The wrapper carries aria-hidden and the
   input carries tabindex="-1", so neither assistive technology nor the tab
   order ever reaches it. */
.form-honeypot {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The safety note above the submit button. Quiet on purpose: it is guidance,
   not a warning, so it takes the muted text colour rather than the error
   colour and carries no icon or border. */
.form-note {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  max-width: var(--measure);
}
