/* ==========================================================================
   BASE — reset, document defaults, layout rails, shared primitives.
   WP Phase 2: enqueue immediately after design-tokens.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. RESET
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* The masthead is fixed, so an anchor jump would park the target section
   underneath it and hide its own title. Every section reserves that height. */
main > section,
[id] { scroll-margin-block-start: calc(var(--header-h) + var(--sp-4)); }

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ol, ul {
  margin: 0;
  padding: 0;
}

ul[class], ol[class] { list-style: none; }

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

input, button, textarea, select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  cursor: pointer;
}

a { color: inherit; text-decoration: none; }

::selection {
  background: var(--c-selection-bg);
  color: var(--c-selection-fg);
}

/* --------------------------------------------------------------------------
   2. DOCUMENT
   -------------------------------------------------------------------------- */

body {
  background: var(--c-bg);
  color: var(--c-ink);
  font-family: var(--ff-body);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-normal);
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

:focus-visible {
  outline: var(--bw-rule) solid var(--c-accent);
  outline-offset: 3px;
}

.u-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.u-skip-link {
  position: absolute;
  top: var(--sp-3);
  left: var(--sp-3);
  z-index: 999;
  padding: var(--sp-2) var(--sp-4);
  background: var(--c-ink);
  color: var(--c-bg);
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-sm);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  transform: translateY(-200%);
}
.u-skip-link:focus { transform: translateY(0); }

/* --------------------------------------------------------------------------
   3. LAYOUT RAIL — .l-shell
   A 12-column editorial grid with named bleed lines. Children opt in by
   naming grid lines; nothing is centred by default.

   IMPORTANT: the rail opens with a margin track, so RAW NUMERIC line indices
   are offset by one. Always address content columns through the `col` name:

     grid-column: content-start / content-end;  -> the full 12-column well
     grid-column: content-start / span 7;       -> columns 1 through 7
     grid-column: col 4 / full-end;             -> column 4, bleeding off-screen
     grid-column: col 9 / content-end;          -> columns 9 through 12
   -------------------------------------------------------------------------- */

.l-shell {
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--margin), 1fr)
    [content-start] repeat(var(--grid-columns), [col] minmax(0, calc(var(--content-max) / 12)))
    [content-end] minmax(var(--margin), 1fr)
    [full-end];
  column-gap: var(--gutter);
}

/* Default: unclassed direct children sit in the content well. */
.l-shell > * { grid-column: content-start / content-end; }

.l-bleed  { grid-column: full-start / full-end; }
.l-bleed-right { grid-column: content-start / full-end; }
.l-bleed-left  { grid-column: full-start / content-end; }

/* --------------------------------------------------------------------------
   4. TYPE PRIMITIVES
   -------------------------------------------------------------------------- */

.t-display {
  font-family: var(--ff-display);
  font-optical-sizing: auto;
  font-variation-settings: "SOFT" 0, "WONK" 1;
  font-weight: var(--fw-light);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  text-wrap: balance;
}

.t-serif {
  font-family: var(--ff-display);
  font-weight: var(--fw-regular);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
}

.t-mono {
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-sm);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  line-height: 1;
}

.t-mono--wide { letter-spacing: var(--tr-mono-wide); }
.t-mono--xs   { font-size: var(--fs-mono-xs); }

.t-lede {
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  color: var(--c-ink-soft);
  text-wrap: pretty;
}

.t-accent { color: var(--c-accent-text); }

/* Editorial italic inside display headings — the one flourish we allow. */
.t-display em {
  font-style: italic;
  font-variation-settings: "SOFT" 40, "WONK" 1;
  letter-spacing: -0.045em;
}

/* --------------------------------------------------------------------------
   5. RULES — hairline dividers used as structural punctuation
   -------------------------------------------------------------------------- */

.c-rule {
  border: 0;
  border-top: var(--bw-hair) solid var(--c-line);
  block-size: 0;
  inline-size: 100%;
}

.c-rule--strong { border-top-color: var(--c-line-strong); }


/* --------------------------------------------------------------------------
   6. FULL-BLEED MEDIA BEDS
   Any section can sit on photography or video. The bed is always three
   layers: media, grade, scrim. Content sits above all three.
   -------------------------------------------------------------------------- */

.c-bed {
  position: relative;
  isolation: isolate;
  overflow: clip;

  /* THE MUTED STEP IS LIFTED OVER A PICTURE.

     --slate-300 measures 4.7:1 on the scrim, which passes AA and still reads
     badly: the form's placeholders and the mono labels sit on a photograph
     rather than on a flat colour, and a number that holds for one uniform
     background does not describe type over sixteen per cent of a balcony.
     Contrast is a floor, not a design.

     Mixed towards the ink it takes the same type to about 6.5:1 without
     collapsing the difference between a label and a line of copy, which is
     what setting it to the soft step would have done. */
  --c-ink-muted: color-mix(in oklab, var(--slate-300) 62%, var(--stone-50));
}

.c-bed__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  filter: var(--grade-media);
}

/* Slow drift keeps a still photograph from reading as a dead backdrop.
   
   `will-change` because this one is worth promoting and there are only two of
   them on the page. It tells Chrome to give the image its own compositor
   layer up front, so the scale animation moves a texture instead of
   re-rasterising the photograph. Used sparingly on purpose: every promoted
   layer is GPU memory, and a page that promotes everything is slower than one
   that promotes nothing. */
.c-bed--drift .c-bed__media {
  animation: bed-drift 34s var(--ease-inout) infinite alternate;
  transform-origin: center;
  will-change: transform;
}

@keyframes bed-drift {
  from { transform: scale(1.04) translate3d(0, 0, 0); }
  to   { transform: scale(1.12) translate3d(-1.4%, -1.2%, 0); }
}

/*
  THE PLATE.
  Every bed rests on a designed surface, never on nothing. It is pure CSS —
  limestone catching light in the top right, deep water falling away to the
  bottom left — so a section is a finished composition before any film has
  loaded, and stays one if the visitor blocks third-party media entirely.

  This exists because the channel's thumbnails carry baked-in title graphics.
  They are correct on a video card and unusable as a background, so beds are
  fed by live video frames or by this plate. Never by a thumbnail.
*/
.c-bed__plate {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(130% 95% at 80% 8%,
      color-mix(in oklab, var(--slate-700) 62%, var(--stone-300)) 0%,
      transparent 56%),
    linear-gradient(168deg,
      var(--slate-800) 0%,
      var(--slate-900) 42%,
      var(--slate-950) 100%);
}

.c-bed__stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s var(--ease-out);
}

.c-bed[data-bed-ready] .c-bed__stage { opacity: 1; }

/*
  The frame is sized and offset by placeFrame() in site-ui.js, where the box
  dimensions are known. This rule only applies what it is given.

  --bed-x slides the player sideways far enough that its centre — and the
  round button drawn there — falls outside the visible box. That is done only
  where it costs nothing, which is on tall narrow boxes; on a wide one it
  would double the frame and halve the picture. See the note in site-ui.js.

  On a wide box the frame is the smallest 16:9 rectangle that covers it,
  which is the least-cropped framing available, and there is no zoom at all.

  On the narrow boxes where the sideways slide is already running, the frame
  is ALSO grown vertically. The player's title bar does appear, for a second
  or two at the start of every segment, and on a phone it printed across the
  top-left of the hero; every embed parameter that could suppress it is
  already set. So the frame is made tall enough that the player's top and
  bottom chrome bands fall outside the visible box. It costs a little scale
  on a frame that is already three times the box's width, where it does not
  read as zoom — and nothing at all on desktop.

  The caption track is switched off at the embed and again through the API:
  cc_load_policy is documented as "based on user preference", not "off".
*/
.c-bed__stage iframe {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(
    calc(-50% + var(--bed-x, 0px)),
    calc(-50% + var(--bed-y, 0px))
  );
  transform-origin: center;
  border: 0;
  filter: var(--grade-media);
}

/* Bed stacking order, fixed in one place:
     0  plate   — the designed surface
     1  stage   — live film
     2  scrim   — legibility layer, ABOVE the media
     3  content — never dimmed by its own scrim
*/
.c-bed__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

/* Weighted to the bottom-left, where editorial type always lands, with a
   shallow band along the top so the masthead stays legible against a bright
   sky. Three stops, not a flat wash — the footage still reads as footage. */
/* THE HERO'S SCRIM, AND WHY IT IS ITS OWN SET OF NUMBERS

   Three gradients overlap in the bottom-left corner, which is exactly where
   the masthead sits — and they stack multiplicatively. At the shared scrim
   values that corner came out at 97% black: the video behind the largest type
   on the site was showing through at three per cent, which is why the hero
   read as dark footage rather than as footage.

   Measured against the poster's brightest 5%, with the masthead in
   --stone-50:

       97% (was)      15.4:1     the video is barely there
       76%             7.6:1
       69% (now)       6.5:1     ten times more picture, still generous
       50%             4.6:1     the AA floor for BODY text

   The masthead is display type at 3rem and up, where AA asks for 3:1, so 6.5
   is not a compromise — it is still well clear, and the picture is visible.

   Its own tokens rather than the shared ones because the section beds are a
   different problem: those carry small mono labels and a whole contact form,
   and their 82% was derived for that type at that size. */
.c-bed__scrim--corner {
  background:
    linear-gradient(to bottom, var(--scrim-hero-edge) 0%, transparent 20%),
    linear-gradient(to top right, var(--scrim-hero-deep) 0%, var(--scrim-hero-mid) 40%, transparent 76%),
    linear-gradient(to top, var(--scrim-hero-edge) 0%, transparent 46%);
}

.c-bed__scrim--flood {
  background: linear-gradient(to right, var(--scrim-deep) 0%, var(--scrim-mid) 52%, var(--scrim-veil) 100%);
}

.c-bed__scrim--even { background: var(--scrim-flood); }

/* A photograph with a whole section's worth of copy on top of it, rather than
   a headline in one corner. Flat, because the content is not on one edge, and
   78% rather than the 70% `--even` carries.
   
   That figure is measured, not chosen. The type that decides it is the muted
   mono label, --slate-300 on the bed; against the brightest 5% of the three
   installed photographs it lands at 4.6-4.7:1, over the 4.5 AA asks for, with
   body copy at 12.5:1. At 70% the same labels measured 3.8-4.0:1 and failed.
   The installer levels the photographs' highlights so one figure can serve
   all three — see tools/install-section-beds.py. */
.c-bed__scrim--read { background: var(--scrim-read); }

/*
  THE WASH.
  A still from the channel, blurred far past the point where its baked-in
  title graphics can be read, then dimmed. What survives is the colour and
  the light of a real Maltese afternoon — texture, not a photograph. This is
  the only way the channel's stills are allowed near a background.
*/
/* THE WASH, AND WHY IT NO LONGER MOVES
   
   Reported as scroll stutter on a MacBook in Chrome, and this was the largest
   single cause on the page. Measured live, there were three of these:
   
       hero        1611 x 1105   1.78 million pixels
       about       1611 x 1212   1.95 million pixels
       relocation  1546 x 1021   1.58 million pixels
   
   Each carrying `filter: blur(64px)`, and each ANIMATED — the first two on a
   46s drift, the third on a scroll-driven parallax.
   
   A transform animation is normally free: the compositor moves a layer it has
   already rasterised. That stops being true when the layer carries a blur and
   the transform SCALES it, because the blurred bitmap is only valid at the
   scale it was rasterised at. So every frame re-ran a 64px Gaussian over ~2M
   pixels, three times over, and the scroll-driven one did it in response to
   scrolling — which is exactly when it was felt.
   
   Two changes, neither of them visible:
   
   The animation is gone. This is an abstract colour field at 0.42 opacity
   behind a scrim; nothing about it reads as movement, and it was costing the
   most of anything on the page.
   
   The blur is 28px rather than 64px. The `--wash` these sections point at is
   a 1280x720 thumbnail and the radius was doing the work of making it
   unrecognisable. At 0.42 opacity under a scrim, 28px is indistinguishable
   from 64px and roughly five times cheaper to rasterise — blur cost grows
   with the radius, not linearly with it.
   
   The Ken Burns on the section PHOTOGRAPHS is untouched. Those are <img>
   elements with no filter, which is the case where a scaling transform really
   is free. */
.c-bed__wash {
  position: absolute;
  inset: -8%;
  z-index: 0;
  background-image: var(--wash);
  background-size: cover;
  background-position: center;
  filter: blur(28px) saturate(0.75) brightness(0.5);
  opacity: 0.42;
  pointer-events: none;
}

/* Panels put copy on one edge and a ledger on the other, so both edges need
   cover and the middle is where the film is allowed to show through. */
.c-bed__scrim--panel {
  background:
    linear-gradient(to bottom, var(--scrim-flood) 0%, transparent 16%),
    linear-gradient(100deg,
      var(--scrim-deep) 0%,
      var(--scrim-mid) 46%,
      var(--scrim-flood) 100%);
}

.c-bed > .l-shell,
.c-bed > .c-bed__content { position: relative; z-index: 3; }

/* --------------------------------------------------------------------------
   7. SCROLL REVEAL

   The hidden state is a class the SCRIPT ADDS and then REMOVES, rather than a
   default that an override has to beat. That ordering matters: an earlier
   version hid by default and revealed with a more specific selector, and a
   section could be left permanently invisible if the reveal landed while the
   subtree was not being rendered. Arming can only happen if the script is
   running, and un-arming is a class removal, so there is no state to get
   stuck in and no specificity race to lose.

   site-ui.js also un-arms everything unconditionally after a few seconds, so
   even a broken observer cannot hide content.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .is-armed {
    opacity: 0;
    transform: translateY(1.25rem);
  }

  [data-reveal] {
    transition:
      opacity var(--dur-slow) var(--ease-out),
      transform var(--dur-slow) var(--ease-out);
    transition-delay: calc(var(--reveal-delay, 0) * 90ms);
  }
}

/* --------------------------------------------------------------------------
   8. SECTION HEAD
   A centred stack: the small label sits ABOVE the heading, both centred on
   the rail. The old version put the label in column 1 and the heading in
   column 2, which read as a caption hanging off the side of a title rather
   than as one masthead.
   -------------------------------------------------------------------------- */

.c-head {
  row-gap: var(--sp-4);
  padding-block-end: var(--section-gap);
  justify-items: center;
  text-align: center;
}

.c-head > * { grid-column: content-start / content-end; }

.c-head__index {
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-mono-wide);
  text-transform: uppercase;
  color: var(--c-accent-text);
}

.c-head__title {
  font-family: var(--ff-display);
  font-optical-sizing: auto;
  font-variation-settings: "SOFT" 0, "WONK" 1;
  font-weight: var(--fw-light);
  font-size: var(--fs-3xl);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  text-wrap: balance;
}

.c-head__title em {
  font-style: italic;
  font-variation-settings: "SOFT" 60, "WONK" 1;
  color: var(--c-accent-text);
}

.c-head__standfirst {
  max-inline-size: 70ch;
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
}

/* --------------------------------------------------------------------------
   9. ACTIONS — link-first. No oversized pill buttons anywhere on this site.
   -------------------------------------------------------------------------- */

.c-action {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  padding-block-end: var(--sp-2);
  border-block-end: var(--bw-rule) solid var(--c-accent);
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-sm);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--c-ink);
  transition: color var(--dur-fast) var(--ease-out);

  /* An inline-flex box shrink-wraps to its content and will happily run past
     its container. "Request a private tour quote" did exactly that at 320px
     and put 18px of horizontal scroll on the whole document. Capped, the
     label wraps instead — which is the right answer for a button whose text
     is a sentence. */
  max-inline-size: 100%;
}

.c-action svg { inline-size: 1.5rem; block-size: auto; transition: transform var(--dur-base) var(--ease-out); }
.c-action:hover { color: var(--c-accent-text); }
.c-action:hover svg { transform: translateX(0.4rem); }

.c-action--solid {
  padding: var(--sp-4) clamp(var(--sp-4), 4vw, var(--sp-6));
  border: 0;
  background: var(--c-accent);
  color: var(--c-accent-on);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.c-action--solid:hover { background: var(--c-accent-hover); color: var(--c-accent-on); }

.c-action--ghost {
  padding: var(--sp-4) var(--sp-6);
  border: var(--bw-hair) solid var(--c-line-strong);
  color: var(--c-ink);
  transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.c-action--ghost:hover { border-color: var(--c-ink); background: color-mix(in oklab, var(--c-ink) 6%, transparent); }



/* Headings never break unevenly. */
h1, h2, h3, .c-head__title, .c-about__title, .c-panel__title,
.c-shop__title, .c-booking__title, .c-entry__title {
  text-wrap: balance;
}


/* ==========================================================================
   THE WRAP LAW
   The single source of truth for how text is allowed to break. Three tiers,
   and everything on the site belongs to exactly one of them.

   TIER 1 — NEVER WRAPS.
   Labels, chips, buttons, stamps, badges, eyebrows, datestamps, metadata,
   navigation. These are short by construction. If one of them is long enough
   to wrap, THE COPY IS WRONG and must be shortened — the layout is not at
   fault and must not be widened to accommodate it.

   TIER 2 — WRAPS EVENLY, NEVER RAGGEDLY.
   Headings and short card copy. These have to break on narrow screens, but
   they break into balanced lines with no word stranded alone on the last one.

   TIER 3 — PROSE.
   Body paragraphs. A paragraph must wrap; that is what a paragraph is. It is
   held to a sane measure and prevented from leaving an orphan.
   ========================================================================== */

/* --- TIER 1 : never wraps ------------------------------------------------- */

.t-mono,
.c-action,
.c-site-header__link,
.c-site-header__wordmark,
.c-site-header__badge,
.c-site-header__cta,
.c-hero__control,
.c-hero__eyebrow,
.c-hero__coords,
.c-hero__now-tag,
.c-hero__now-cue,
.c-hero__figure dt,
.c-head__index,
.c-archive__chip,
.c-archive__readout span,
.c-channel__cue,
.c-entry__stamp,
.c-entry__topic,
.c-entry__duration,
.c-entry__play,
.c-entry__foot,
.c-about__eyebrow,
.c-about__caption,
.c-about__figure dt,
.c-socials__text small,
.c-socials__text b,
.c-events__badge,
.c-events__list span,
.c-panel__eyebrow,
.c-panel__figure dt,
.c-shop__eyebrow,
.c-shop__price small,
.c-shop__rating,
.c-shop__contents li,
.c-cover__mark,
.c-services__tag,
.c-services__row-name,
.c-consult__meta div,
.c-mailcard__text small,
.c-mailcard__text b,
.c-mailcard__cue,
.c-tours__stops li,
.c-field label,
.c-site-footer__legal > span,
.c-site-footer__by span,
.c-site-footer__channel small {
  white-space: nowrap;
}

/* --- TIER 2 : wraps evenly ------------------------------------------------ */

h1, h2, h3, h4,
.c-head__title,
.c-hero__title,
.c-about__title,
.c-panel__title,
.c-shop__title,
.c-consult__title,
.c-contact__title,
.c-events__title,
.c-entry__title,
.c-tours__name,
.c-head__standfirst,
.c-tours__body p,
.c-entry__dek,
.c-services__panel h3,
.c-channel__text b {
  text-wrap: balance;
}

/* --- TIER 3 : prose ------------------------------------------------------- */

p,
li,
.c-hero__lede,
.c-about__copy,
.c-shop__copy,
.c-consult__copy,
.c-contact__copy,
.c-events__copy,
.c-services__panel p,
.c-post__body p {
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   NO-WRAP, tier 1 — text we do not write.
   Video titles and deks arrive from YouTube at arbitrary length, so they are
   clamped rather than edited: one line, ellipsis, never a second line at any
   viewport. Everything we author is kept short enough not to need this.
   -------------------------------------------------------------------------- */

.c-hero__now .c-hero__now-title,
.c-entry__title,
.c-entry__title span,
.c-entry__dek {
  display: block;
  inline-size: 100%;
  max-inline-size: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-wrap: nowrap;
}


/* --------------------------------------------------------------------------
   TOUCH: tap targets

   A text link with an arrow is 25px tall, which is fine for a cursor and not
   for a thumb. On a touch screen every action gets a 44px target — the
   padding grows around the text rather than the text growing, so nothing
   about the typography changes.
   -------------------------------------------------------------------------- */

@media (hover: none) {
  .c-action {
    min-block-size: 2.75rem;
    display: inline-flex;
    align-items: center;
  }

  .c-action--solid { padding-block: var(--sp-4); }
  .c-site-footer__by { min-block-size: 2.75rem; align-items: center; }

  /* THE FIRST TAP MUST DO THE THING.

     On a touch screen, Safari and Chrome emulate a hover before the click
     whenever an element's APPEARANCE changes on :hover — the first tap paints
     the hover state and is swallowed, and only the second one activates. That
     is why the buy button needed pressing twice on a phone.

     These rules say "hovering looks exactly like not hovering" on a device
     with no real pointer, so there is no hover state to paint and the first
     tap is the click. The sizing above already recognised these as touch
     targets; this finishes the thought.

     :active is deliberately untouched. It fires on the real press and is the
     feedback a thumb actually wants. */
  .c-action:hover        { color: inherit; }
  .c-action:hover svg    { transform: none; }
  .c-action--solid:hover { background: var(--c-accent); color: var(--c-accent-on); }
  .c-action--ghost:hover { border-color: var(--c-line); background: none; }

  /* And no pause to see whether a second tap is coming. */
  .c-action,
  button,
  summary,
  [role="button"] { touch-action: manipulation; }
}

/* --------------------------------------------------------------------------
   PHONE: third-party titles wrap rather than truncate

   The override for this does NOT live here. timeline.css and hero.css each
   set their own `white-space: nowrap` on these elements and load after this
   file, so anything written here loses the cascade — which is exactly what
   happened on the first attempt: the line-clamp applied and the white-space
   did not, leaving a one-line ellipsis with a two-line clamp on it.

   See the PHONE blocks at the foot of timeline.css and hero.css.
   -------------------------------------------------------------------------- */


/* --------------------------------------------------------------------------
   THE FLAG

   Three placements, and only three: the coordinate rail beside the hero, the
   imprint line on the guide book's cover, and the copyright at the foot of
   the page. Those are the spots that already name the place — the rail names
   it in latitude — so the flag finishes a sentence rather than decorating a
   heading.

   It is NOT on the hero's "Malta, as it actually is." or on any other display
   type. A flag next to a 7rem headline stops being a mark and becomes a
   graphic, and this palette was picked off a luzzu precisely so the site
   could be Maltese without saying so twice.

   Drawn rather than loaded: two rectangles and the George Cross as a stroked
   outline, about 250 bytes, no request, and it inherits its size from the
   type it sits next to.
   -------------------------------------------------------------------------- */

.c-flag {
  display: inline-block;
  inline-size: 1.05em;
  block-size: auto;
  margin-inline-end: 0.55em;
  vertical-align: -0.12em;
  /* The white half is pure white on a near-black page and the red is fully
     saturated; both were louder than the mono line they sit in. */
  opacity: 0.82;
}

/* In the rail the flag is the only thing there, so it stands alone and a
   little larger, under the accent stroke. */
.c-hero__rail .c-flag {
  inline-size: 1.4rem;
  margin-inline-end: 0;
  opacity: 0.9;
}

/* In the footer the flag follows the word rather than leading the line, so
   the space it carries has to move to the other side of it. */
.c-site-footer__legal .c-flag { margin-inline: 0.5em 0; }

/* --------------------------------------------------------------------------
   THE HONEYPOT

   Off-screen rather than `display: none`. A field that is display:none is
   trivially detectable and the better form bots skip it, which defeats the
   whole point of having one — it has to look like a field that a careless
   script would fill in.

   `aria-hidden` on the wrapper and `tabindex="-1"` on the input keep it away
   from screen readers and out of the tab order, so no real visitor ever meets
   it.
   -------------------------------------------------------------------------- */

.u-honeypot {
  position: absolute;
  inset-inline-start: -9999px;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
}
