/* ============================================================================
   Splashframe — one stylesheet for all five surfaces.

   Every value here comes from the asset sheet (v1, Aug 2026). Copy from this
   file rather than from a screenshot; if a value is wrong it is wrong in one
   place.

   The rules that are easy to break by accident, so they are enforced here:
     - Square corners everywhere. The ONLY rounding is a device frame and the
       operator's own badge. There is no --radius token on purpose.
     - Blue (--blue) is reserved for the thing you want tapped. If two blue
       things sit together, one of them is wrong.
     - Status chips are a tint at 8% with the text at full colour. Never a
       bare dot, never a solid fill.
     - On the boat (the camera app) the action colour is SALT, not blue.
       Blue is not bright enough to find in direct sun through polarised
       sunglasses. See .sf-target.
   ========================================================================= */

/* ----------------------------------------------------------------------------
   Fonts are SELF-HOSTED, not pulled from Google's CDN. Three reasons, in order
   of how much they cost if ignored:
     1. The crew phone and the guest's phone are both on marginal cellular. A
        third-party font request is a second DNS lookup, a second TLS
        handshake, and a render your page does not control.
     2. Guests are international — the fixtures alone have Japan, Germany,
        France and Brazil. A Google Fonts request sends a European visitor's
        IP to a US ad company on page load, which is a GDPR question nobody
        wants to answer for a $60 photo sale.
     3. It works with no network at all, which is the state the boat is in for
        part of every trip.
   Latin subset, only the weights the asset sheet uses. 148 KB for all eight
   faces. Swap in a wider subset only when a real customer needs it.
   ------------------------------------------------------------------------- */

@font-face { font-family: 'Archivo'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/archivo-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: 'Archivo'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('fonts/archivo-latin-700-normal.woff2') format('woff2'); }
@font-face { font-family: 'Archivo'; font-style: normal; font-weight: 800; font-display: swap;
  src: url('fonts/archivo-latin-800-normal.woff2') format('woff2'); }

@font-face { font-family: 'Public Sans'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/public-sans-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'Public Sans'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/public-sans-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'Public Sans'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/public-sans-latin-600-normal.woff2') format('woff2'); }

@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/jetbrains-mono-latin-500-normal.woff2') format('woff2'); }

:root {
  /* --- colour ------------------------------------------------------------ */
  --ink:          #0F1A20;   /* text, dark surfaces, the mark */
  --salt:         #F5F3EE;   /* page background, reversed type. Paper, not screen */
  --blue:         #0A57C2;   /* PRIMARY ACTIONS ONLY */
  --blue-deep:    #0B469C;   /* panels inside a blue section, pressed states */
  --sky:          #68B0FF;   /* on dark only — reversed marks, dark-surface links */
  --cyan:         #0A6C7E;   /* eyebrows, section numbers, preview state. Never an action */
  --cyan-bright:  #3FD0E8;   /* rules and live indicators on dark. Non-text use on light */
  --deep-water:   #0E5A6E;   /* DEFAULT operator colour — replaced by the operator's own */

  --paid:         #0B5F55;
  --paid-tint:    #E3F0EA;
  --warning:      #8A5A0B;
  --warning-tint: #F7EFE0;
  --needs:        #A8300F;
  --needs-tint:   #FBE9E6;

  /* Operator theming. The guest gallery and the co-brand lockup read these.
     Set them per-operator from the API (branding.color) — never hardcode a
     customer's colour into a page. */
  --operator:      var(--deep-water);
  --operator-ink:  #FFFFFF;

  /* Derived, so a hex never gets retyped at a random opacity */
  --ink-90: rgba(15, 26, 32, 0.90);
  --ink-80: rgba(15, 26, 32, 0.80);
  --ink-60: rgba(15, 26, 32, 0.60);
  --ink-45: rgba(15, 26, 32, 0.45);   /* decorative only — 2.9:1, fails AA for text */
  --ink-55: rgba(15, 26, 32, 0.62);   /* the smallest type that carries meaning. 4.6:1 */
  --ink-22: rgba(15, 26, 32, 0.22);
  --ink-13: rgba(15, 26, 32, 0.13);
  --ink-08: rgba(15, 26, 32, 0.08);
  --salt-70: rgba(245, 243, 238, 0.70);
  --salt-45: rgba(245, 243, 238, 0.45);
  --salt-55: rgba(245, 243, 238, 0.58);   /* labels on the boat. 4.9:1 on #0A1015 */
  --salt-14: rgba(245, 243, 238, 0.14);

  /* --- type -------------------------------------------------------------- */
  --display: 'Archivo', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --ui:      'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono:    'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* --- spacing ------------------------------------------------------------
     A 4px base. Panels sit on 24/32; page gutters on 32/48.                 */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px;

  /* Minimum tap target. 48px on land, 72px on a moving boat. */
  --target: 48px;
  --target-boat: 72px;

  /* --- the brand mark -----------------------------------------------------
     ONE number decides how big the bracket mark reads, on every surface. The
     tiers below are ratios of it, so rescaling the mark everywhere is an edit
     here — not a width hunted down in five HTML files. sf.js reads these via
     logo({ tier }) and writes no pixel sizes of its own.

     Why the numbers look large for what you see: the artwork does not fill its
     own viewBox. The bracket spans about 60% of the 1254 box, so a 35px lockup
     renders a mark roughly 21px wide. The box is the token; the ink is smaller.

     2026-08-12: every tier went up 35%. The mark was reading as a bullet point
     next to the wordmark rather than as a lockup with it. */
  --mark-size:       35px;                            /* card lockups   (was 26) */
  --mark-size-nav:   calc(var(--mark-size) * 0.93);   /* topbar, sidebar (was 24) */
  --mark-size-quiet: calc(var(--mark-size) * 0.70);   /* footers, gallery (was 18) */
  --mark-size-micro: calc(var(--mark-size) * 0.62);   /* activation strip (was 16) */
}

/* ============================================================================
   Reset
   ========================================================================= */

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

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

body {
  font-family: var(--ui);
  font-size: 17px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--salt);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg, video { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }
a { color: inherit; }

/* Focus that survives a redesign. Visible, square, never removed. */
.sf-focusable { cursor: pointer; }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.sf-dark :focus-visible, .sf-boat :focus-visible { outline-color: var(--cyan-bright); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ============================================================================
   Type scale — from the asset sheet, §03
   ========================================================================= */

.sf-title    { font-family: var(--display); font-weight: 800; font-size: 52px; line-height: 1.02; letter-spacing: -0.04em; }
.sf-section  { font-family: var(--display); font-weight: 800; font-size: 34px; line-height: 1.1;  letter-spacing: -0.035em; }
.sf-card-title { font-family: var(--display); font-weight: 700; font-size: 22px; line-height: 1.2; letter-spacing: -0.02em; }
.sf-body     { font-size: 17px; line-height: 1.65; }
.sf-secondary{ font-size: 14px; line-height: 1.6; color: var(--ink-60); }

/* Every field label, status chip, reference and timestamp. Never a sentence.
   These are 10px, which is the smallest type on any of these surfaces, so they
   get --ink-55 (4.6:1) rather than --ink-45 (2.9:1). A label nobody can read
   in the sun is not a label. */
.sf-label {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-55);
}
.sf-meta { font-family: var(--mono); font-size: 12px; letter-spacing: 0.02em; color: var(--ink-60); }

/* Section eyebrow: cyan, numbered. Cyan is never an action. */
.sf-eyebrow { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--cyan); }

.sf-num { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }

@media (max-width: 760px) {
  .sf-title   { font-size: 36px; }
  .sf-section { font-size: 26px; }
}

/* ============================================================================
   Surfaces
   ========================================================================= */

.sf-dark { background: var(--ink); color: var(--salt); }
.sf-dark .sf-label { color: var(--salt-55); }
.sf-dark .sf-secondary { color: var(--salt-70); }
.sf-dark .sf-eyebrow { color: var(--cyan-bright); }

/* The camera app. Near-black, not ink — it sits behind a screen in bright sun
   and pure ink reflects the sky back at the operator. */
.sf-boat { background: #0A1015; color: var(--salt); }

.sf-panel { background: #FFFFFF; border: 1px solid var(--ink-08); }
.sf-panel-pad { padding: var(--s5); }

.sf-rule { height: 1px; background: var(--ink-13); border: 0; }
.sf-dark .sf-rule { background: var(--salt-14); }

.sf-wrap { max-width: 1240px; margin: 0 auto; padding: 0 var(--s6); }
@media (max-width: 760px) { .sf-wrap { padding: 0 var(--s4); } }

/* ============================================================================
   Buttons

   One primary per view. If you need two, one of them is a secondary.
   ========================================================================= */

.sf-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: var(--target);
  padding: 17px var(--s5);
  font-family: var(--display); font-weight: 700; font-size: 17px; letter-spacing: -0.02em;
  text-align: center; text-decoration: none;
  border: 1px solid transparent;
  transition: background-color 120ms ease, color 120ms ease;
}
.sf-btn:disabled, .sf-btn[aria-disabled='true'] { cursor: default; opacity: 1; }

.sf-btn-primary { background: var(--blue); color: #fff; }
.sf-btn-primary:hover { background: var(--blue-deep); }
.sf-btn-primary:disabled { background: var(--ink-08); color: var(--ink-45); }

/* The guest gallery's buy button. Takes the OPERATOR's colour, not ours —
   the guest's relationship is with the operator; we are the line at the
   bottom. */
.sf-btn-operator { background: var(--operator); color: var(--operator-ink); }
.sf-btn-operator:hover { filter: brightness(0.92); }

.sf-btn-secondary { background: #fff; color: var(--ink); border-color: var(--ink-13); }
.sf-btn-secondary:hover { border-color: var(--ink-22); }

.sf-btn-ghost { background: transparent; color: var(--ink); border-color: var(--ink-13); }
.sf-dark .sf-btn-ghost { color: var(--salt); border-color: var(--salt-14); }

.sf-btn-ink { background: var(--ink); color: var(--salt); }
.sf-btn-block { display: flex; width: 100%; }

/* The boat target. Salt on ink, 72px minimum, no rounding, no icon.
   Four of these fit a phone held in a waterproof case in one wet hand. */
.sf-target {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; min-height: var(--target-boat);
  padding: 0 var(--s5);
  background: var(--salt); color: var(--ink);
  font-family: var(--display); font-weight: 800; font-size: 24px; letter-spacing: -0.02em;
  text-transform: uppercase;
  border: 0;
}
.sf-target[data-state='armed']  { background: var(--blue); color: #fff; }
.sf-target[data-state='idle']   { background: #17222B; color: var(--salt-45); }
.sf-target[data-state='live']   { background: var(--salt); color: var(--ink); }
.sf-target .sf-target-hint {
  font-family: var(--mono); font-size: 11px; font-weight: 400;
  letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.5;
}

/* ============================================================================
   Status chips — tint at 8%, text at full. Never a bare dot.
   ========================================================================= */

.sf-chip {
  display: inline-block;
  padding: 6px 9px;
  font-family: var(--mono); font-weight: 500; font-size: 10px;
  letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap;
  background: var(--ink-08); color: var(--ink-60);
}
.sf-chip[data-tone='paid']    { background: var(--paid-tint);    color: var(--paid); }
.sf-chip[data-tone='ready']   { background: #EBF1FB;             color: var(--blue); }
.sf-chip[data-tone='active']  { background: var(--paid-tint);    color: var(--paid); }
.sf-chip[data-tone='preview'] { background: #E6F6F9;             color: var(--cyan); }
.sf-chip[data-tone='pre']     { background: #E6F6F9;             color: var(--cyan); }
.sf-chip[data-tone='warning'] { background: var(--warning-tint); color: var(--warning); }
.sf-chip[data-tone='needs']   { background: var(--needs-tint);   color: var(--needs); }
.sf-chip[data-tone='unsold']  { background: var(--ink-08);       color: var(--ink-60); }

/* ============================================================================
   Photo placeholder + watermark

   Both are CSS so a page renders correctly with no media and no network.
   Replace .sf-photo's background with a real frame; keep the aspect box.
   ========================================================================= */

.sf-photo {
  position: relative;
  aspect-ratio: 3 / 2;
  background:
    repeating-linear-gradient(115deg,
      #2E7C8C 0px, #2E7C8C 22px,
      #2A7383 22px, #2A7383 44px,
      #327F90 44px, #327F90 62px);
  background-color: var(--deep-water);
  overflow: hidden;
}
.sf-photo img { width: 100%; height: 100%; object-fit: cover; }
.sf-photo[data-ratio='square'] { aspect-ratio: 1 / 1; }
.sf-photo[data-ratio='portrait'] { aspect-ratio: 2 / 3; }

/* -24°, white at 40%, paired lines on a 30px pitch. Sits over the preview
   without spoiling the picture the guest is imagining on a wall. */
.sf-watermark::after {
  content: '';
  position: absolute; inset: -20%;
  background: repeating-linear-gradient(-24deg,
    rgba(255,255,255,0.40) 0px, rgba(255,255,255,0.40) 1px,
    transparent 1px, transparent 4px,
    rgba(255,255,255,0.40) 4px, rgba(255,255,255,0.40) 5px,
    transparent 5px, transparent 30px);
  pointer-events: none;
}

.sf-photo-tag {
  position: absolute; left: 8px; bottom: 8px;
  padding: 4px 7px;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase;
  background: rgba(15,26,32,0.72); color: rgba(255,255,255,0.85);
}

/* ============================================================================
   Logo lockup

   Archivo 600, tracking +0.18em. SPLASH in ink, FRAME in blue.
   Beside an operator's logo: one colour, 72% opacity, never the blue.
   Below 24px: drop the accent, set the mark solid.
   ========================================================================= */

/* The gap is a ratio of the mark, not a constant, because the mark's own
   viewBox padding grows with it. Held at 0.31 the measured whitespace between
   the bracket and the S of SPLASH stays put while the mark scales — a fixed
   12px would have opened up to a visible drift at 35px. */
.sf-logo { display: inline-flex; align-items: center; gap: calc(var(--mark-size) * 0.31); text-decoration: none; }
.sf-logo-mark { width: var(--mark-size); height: var(--mark-size); flex: none; }
.sf-logo-word {
  font-family: var(--display); font-weight: 600; font-size: 17px;
  letter-spacing: 0.18em; text-transform: uppercase; white-space: nowrap;
}
.sf-logo-word b { font-weight: 600; color: var(--blue); }
.sf-dark .sf-logo-word { color: var(--salt); }
.sf-dark .sf-logo-word b { color: var(--sky); }

/* The mark's accent. The body of the mark is currentColor, so it reverses for
   free; the swash is the one element that does not, and this is where it flips.
   Asset Sheet SS01 ships the reversed variant as salt body + sky swash, which is
   exactly these two rules together. Anything that puts light type on a dark
   ground should carry .sf-dark or set --mark-accent itself. */
:root { --mark-accent: var(--blue); }
.sf-dark, .m-dark, .sf-boat { --mark-accent: var(--sky); }

/* Beside an operator's mark, we go quiet: one colour, 72%, never blue. */
.sf-logo-quiet { opacity: 0.72; }
.sf-logo-quiet .sf-logo-word b { color: inherit; }
/* Under 24px the sheet says drop the accent entirely — the crescent turns to
   mud at that size. sf.js logo() also sets --swash for the same reason. */
.sf-logo-quiet { --mark-accent: currentColor; }
.sf-logo-quiet .sf-logo-mark { color: inherit; }

/* The co-brand line. Operator first, always. */
.sf-cobrand { display: flex; align-items: center; gap: var(--s3); }
.sf-operator-badge {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: 8px 14px;
  background: var(--operator); color: var(--operator-ink);
  font-family: var(--display); font-weight: 800; font-size: 15px;
  letter-spacing: 0.02em; text-transform: uppercase;
  border-radius: 3px; /* the operator's badge is the one thing allowed a corner */
}

/* ============================================================================
   Device frames — the other exception to square corners
   ========================================================================= */

.sf-device {
  width: 100%; max-width: 380px;
  background: #0A1015;
  border-radius: 30px;
  padding: 10px;
  box-shadow: 0 24px 60px rgba(15,26,32,0.28);
}
.sf-device-screen { background: #fff; border-radius: 21px; overflow: hidden; }
.sf-device[data-tone='dark'] .sf-device-screen { background: #0A1015; }

.sf-statusbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: 8px 16px 6px;
  font-family: var(--mono); font-size: 11px; color: var(--ink-45);
}
.sf-device[data-tone='dark'] .sf-statusbar { color: var(--salt-45); }

/* ============================================================================
   Forms
   ========================================================================= */

.sf-field { display: flex; flex-direction: column; gap: 6px; }
.sf-input {
  width: 100%; min-height: var(--target);
  padding: 12px 14px;
  background: #fff; color: var(--ink);
  border: 1px solid var(--ink-13);
}
.sf-input::placeholder { color: var(--ink-45); }
.sf-input:focus { border-color: var(--blue); outline: none; box-shadow: inset 0 0 0 1px var(--blue); }
.sf-dark .sf-input { background: transparent; color: var(--salt); border: 0; border-bottom: 1px solid var(--salt-14); }
.sf-dark .sf-input::placeholder { color: var(--salt-45); }

.sf-select { appearance: none; background-image: none; padding-right: 34px; }
.sf-select-wrap { position: relative; }
.sf-select-wrap::after {
  content: ''; position: absolute; right: 14px; top: 50%; width: 7px; height: 7px;
  border-right: 1.5px solid var(--ink-60); border-bottom: 1.5px solid var(--ink-60);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}

/* ============================================================================
   Layout helpers
   ========================================================================= */

.sf-stack   { display: flex; flex-direction: column; }
.sf-row     { display: flex; align-items: center; }
.sf-between { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); }
.sf-grid    { display: grid; gap: var(--s4); }
.sf-gap-1 { gap: var(--s1); } .sf-gap-2 { gap: var(--s2); } .sf-gap-3 { gap: var(--s3); }
.sf-gap-4 { gap: var(--s4); } .sf-gap-5 { gap: var(--s5); } .sf-gap-6 { gap: var(--s6); }

/* A definition list rendered as an aligned two-column readout. Used on every
   surface that shows "what this flight carries". */
.sf-readout { display: flex; flex-direction: column; }
.sf-readout > div { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s4); padding: 7px 0; }
.sf-readout dt { color: var(--ink-60); font-size: 15px; }
.sf-readout dd { font-weight: 600; font-size: 15px; text-align: right; }
.sf-dark .sf-readout dt { color: var(--salt-70); }

.sf-hidden { display: none !important; }
.sf-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ============================================================================
   Toast — the crew phone confirms every action in words, because a tap that
   looks the same before and after is a tap the crew will make twice.
   ========================================================================= */

.sf-toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%) translateY(8px);
  max-width: min(520px, calc(100vw - 32px));
  padding: 13px 18px;
  background: var(--ink); color: var(--salt);
  font-size: 15px; line-height: 1.45;
  opacity: 0; pointer-events: none;
  transition: opacity 160ms ease, transform 160ms ease;
  z-index: 90;
}
.sf-toast[data-show='true'] { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ============================================================================
   Live indicator — cyan-bright on dark only
   ========================================================================= */

.sf-live { display: inline-flex; align-items: center; gap: 7px; }
.sf-live::before {
  content: ''; width: 7px; height: 7px; background: var(--cyan-bright);
  animation: sf-pulse 2s ease-in-out infinite;
}
@keyframes sf-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* ============================================================================
   Progress — a bar, not a spinner. A spinner tells the crew nothing about
   whether to wait or to walk away.
   ========================================================================= */

.sf-progress { height: 3px; background: var(--ink-08); overflow: hidden; }
.sf-progress > i { display: block; height: 100%; background: var(--blue); transition: width 400ms ease; }
.sf-dark .sf-progress { background: var(--salt-14); }
.sf-dark .sf-progress > i { background: var(--cyan-bright); }

/* ============================================================================
   Banner strip — the one-line "here is what is wrong" bar
   ========================================================================= */

.sf-banner {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4);
  font-size: 15px;
}
.sf-banner[data-tone='needs']   { background: var(--needs-tint);   color: var(--needs); }
.sf-banner[data-tone='warning'] { background: var(--warning-tint); color: var(--warning); }
.sf-banner[data-tone='paid']    { background: var(--paid-tint);    color: var(--paid); }
.sf-banner[data-tone='info']    { background: #EBF1FB;             color: var(--blue); }

/* ============================================================================
   Mock-mode ribbon. Present on every page while api.js is serving fixtures,
   so nobody ever demos fake numbers believing they are real.
   ========================================================================= */

.sf-mockflag {
  position: fixed; right: 0; bottom: 0; z-index: 100;
  padding: 5px 10px;
  background: var(--warning); color: #fff;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
}
