/* ==========================================================
   Font: Inter (SIL Open Font License), hosted in assets/fonts/ rather than
   loaded from Google, so pages don't wait on a second server. Two files:
   plain Latin, and accented Latin, which only downloads if a page needs it.
   "Inter Fallback" is Arial resized to Inter's proportions, so text doesn't
   jump when Inter arrives a moment after the page first shows.
   ========================================================== */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/assets/fonts/inter-latin-v20.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/assets/fonts/inter-latin-ext-v20.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Helvetica");
  size-adjust: 107.12%;
  ascent-override: 90.44%;
  descent-override: 22.52%;
  line-gap-override: 0%;
}

/* ==========================================================
   Design tokens — change these to restyle the whole site.
   Light values here; dark values in the block below, which applies
   when <html data-theme="dark"> (set by the switch, see theme.js).
   ========================================================== */
:root {
  color-scheme: light;      /* native scrollbars and form controls match */
  --bg: #ffffff;
  --tile: #f7f7f9;          /* light grey tile, same as marco.fyi */
  --text: #111111;
  --muted: #6b6b73;
  --body-text: #4a4a52;     /* longer paragraphs */
  --line: #e7e7ec;
  --outline: #d7d7dd;       /* arrow circles, outlined boxes */
  --card: #ffffff;          /* raised surfaces inside tiles */
  --on-text: #ffffff;       /* text on dark buttons (buttons use --text as background) */
  --glass: rgba(255, 255, 255, .85);   /* frosted pills: nav, captions */
  --glass-active: #eeeef1;  /* highlighted item in the nav */
  --shadow: rgba(0, 0, 0, .18);
  --hover-ring: transparent;           /* dark mode adds a faint outline on hover */
  --dark-tile-edge: transparent;       /* dark mode outlines the always-dark tiles */
  --accent: #2fd08a;        /* green, from Sam's logo */
  --font-system: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Inter", sans-serif; /* San Francisco on Apple devices */
  --ss-sidebar: #efeff2;    /* Settings-style sidebar */
  --ss-hover: rgba(0, 0, 0, .06);
  --ss-select: #0071e3;     /* Apple blue that passes 4.5:1 with white text */
  --apple-blue: #007aff;    /* Apple system colours, for Settings-style icons */
  --apple-orange: #ff9500;
  --apple-teal: #30b0c7;
  --apple-green: #34c759;
  --apple-purple: #af52de;
  --apple-pink: #ff2d55;
  --badge: #ff3b30;         /* Apple's system red: the Newsletter link's unread dot */
  --radius: 32px;           /* tile corners */
  --tile-pad: 32px;         /* default space inside a tile */
  --gap: 16px;
  --font: "Inter", "Inter Fallback", system-ui, -apple-system, sans-serif;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --badge: #ff453a;         /* system red, dark-mode version */
  --bg: #0e0e10;            /* near-black: softer on the eyes than pure black */
  --tile: #1a1a1d;
  --text: #f2f2f4;
  --muted: #9d9da6;
  --body-text: #c6c6ce;
  --line: #2e2e33;
  --outline: #3a3a41;
  --card: #26262b;
  --on-text: #0e0e10;
  --glass: rgba(34, 34, 38, .8);
  --glass-active: #34343a;
  --shadow: rgba(0, 0, 0, .5);
  --hover-ring: rgba(255, 255, 255, .14);
  --dark-tile-edge: rgba(255, 255, 255, .08);
  --ss-sidebar: #232327;
  --ss-hover: rgba(255, 255, 255, .07);
}

/* A brief colour fade when switching themes (theme.js adds this class for 0.4s) */
.theme-changing,
.theme-changing * {
  transition: background-color .35s ease, color .35s ease, border-color .35s ease !important;
}

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

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

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

/* Words for screen readers only: off screen, but still read out */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
img { display: block; max-width: 100%; }
ul { list-style: none; }

/* ==========================================================
   Floating pill navigation
   ========================================================== */
.nav {
  position: fixed;
  top: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  gap: 2px;
  padding: 4px;
  background: var(--glass);
  backdrop-filter: blur(12px);           /* frosted-glass effect */
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: 0 10px 10px -5px rgba(0, 0, 0, 0.1);
  font-family: var(--font);              /* same on every page, whatever the page's own font */
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 20px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 500;
  color: var(--muted);
  transition: color .2s, background .2s;
}
.nav-link:hover { color: var(--text); }
.nav-link.is-active { background: var(--glass-active); color: var(--text); font-weight: 600; }

/* Unread dot on the Newsletter link, added by nav-badge.js when there's an issue the
   visitor hasn't seen. It sits on the top-right corner of the word; the negative margins
   cancel out the room it takes, so the nav doesn't change width when it appears. */
.nav-link.has-news::after {
  content: '';
  align-self: flex-start;
  width: 6px; height: 6px;
  margin: -1px -7px 0 -5px;
  border-radius: 50%;
  background: var(--badge);
}

/* ==========================================================
   Light/dark switch — top left, styled to match the nav pill
   ========================================================== */
.theme-toggle {
  position: fixed;
  top: 28px;              /* 42px tall, so this centres it on the 51px nav pill (which starts at 24px) */
  left: 24px;
  z-index: 10;
  width: 42px;            /* same height as the highlighted link inside the nav */
  height: 42px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--glass);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 10px 10px -5px rgba(0, 0, 0, 0.1);
  color: var(--text);
  cursor: pointer;
  transition: transform .2s ease;
}
.theme-toggle:hover { transform: rotate(-12deg); }
.theme-toggle:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
.theme-toggle svg { width: 18px; height: 18px; }
.icon-sun,
:root[data-theme="dark"] .icon-moon { display: none; }
:root[data-theme="dark"] .icon-sun { display: block; }

/* ==========================================================
   Page + bento grid
   ========================================================== */
.page {
  max-width: 1400px;
  margin: 0 auto;
  padding: 110px 16px 40px;
}

/* 4 equal columns; rows are roughly square on a laptop screen */
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: clamp(260px, 23vw, 330px);
  grid-auto-flow: dense;   /* lets small tiles fill gaps left by big ones */
  gap: var(--gap);
}

/* Tile sizes */
.tile--intro      { grid-column: span 2; grid-row: span 2; }
.tile--newsletter,
.tile--freeform,
.tile--switcher   { grid-column: span 2; }
.tile--spotify    { grid-column: span 2; grid-row: span 2; }

/* Freeform's expand button (freeform.js) makes it 2×2 and puts the class
   is-ff-expanded on the grid. Spotify then drops to one row, right under the board,
   and Find My and Contact slide into the gap on its left. */
.tile--freeform.is-expanded { grid-row: span 2; }
@media (min-width: 1181px) {
  .grid.is-ff-expanded .tile--spotify { grid-column: 3 / span 2; grid-row: span 1; }
}
/* While the tiles move, name the ones that change place so the browser animates each */
.grid.ff-morph .tile--freeform   { view-transition-name: home-freeform; }
.grid.ff-morph .tile--photobooth { view-transition-name: home-photobooth; }
.grid.ff-morph .tile--photos     { view-transition-name: home-photos; }
.grid.ff-morph .tile--spotify    { view-transition-name: home-spotify; }
.grid.ff-morph .tile--findmy     { view-transition-name: home-findmy; }
.grid.ff-morph .tile--contact    { view-transition-name: home-contact; }
::view-transition-group(*) { animation-duration: .5s; animation-timing-function: cubic-bezier(.2, .9, .3, 1); }

/* ==========================================================
   Base tile
   ========================================================== */
.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: var(--tile-pad);
  background: var(--tile);
  border-radius: var(--radius);
  overflow: hidden;
}

/* Tiles that are links get a subtle hover */
a.tile { transition: transform .3s ease, box-shadow .3s ease; }
a.tile:hover {
  transform: translateY(-3px);
  box-shadow: 0 0 0 1px var(--hover-ring), 0 16px 32px -16px var(--shadow);
}

/* Small circular arrow in the bottom-left corner, like marco.fyi */
.arrow {
  position: absolute;
  left: 20px;
  bottom: 20px;
  width: 32px; height: 32px;
  display: grid;
  place-items: center;
  border: 1.5px solid var(--outline);
  border-radius: 50%;
  font-size: 14px;
  color: var(--muted);
  background: var(--bg);
  transition: background .2s, color .2s, border-color .2s;
}
a.tile:hover .arrow { background: var(--text); color: var(--on-text); border-color: var(--text); }

/* Shared text styles */
.label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}
.muted  { color: var(--muted); font-weight: 500; }
.small  { font-size: 14px; color: var(--muted); line-height: 1.55; }
.small strong { color: var(--text); }
.tile-heading { font-size: 24px; font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; }

.button {
  align-self: flex-start;
  margin-top: auto;
  padding: 10px 18px;
  border-radius: 999px;
  background: var(--text);
  color: var(--on-text);
  font-size: 13px;
  font-weight: 600;
}

/* ==========================================================
   Individual tiles
   ========================================================== */

/* Intro */
.tile--intro { gap: 0; }
.intro-title {
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--line);
}
.intro-logo {
  display: inline-block;
  width: 30px; height: 30px;
  margin-right: 6px;
  vertical-align: -6px;   /* nudges the logo to sit on the text line */
}
.intro-body { padding-top: 28px; display: grid; gap: 18px; }
.intro-body p { font-size: 17px; color: var(--body-text); line-height: 1.6; }

/* Portrait */
.tile--avatar { padding: 0; background: #2f3c48; }
.tile--avatar img {
  width: 100%;
  height: 100%;
  contain: size;                /* the tile sets the size; the image just fills it */
  object-fit: cover;
  object-position: 50% 30%;     /* if cropped, keep the face in view */
  image-rendering: pixelated;   /* keeps the pixel-art edges crisp when resized */
}

/* The real photo sits exactly on top of the pixel art, hidden until hover */
.tile--avatar .avatar-photo {
  position: absolute;
  inset: 0;
  image-rendering: auto;
  opacity: 0;
  filter: blur(8px);
  transform: scale(1.08);
  transition: opacity .35s ease, filter .45s ease, transform .6s cubic-bezier(.2, .7, .2, 1);
}

/* Only on devices with a real mouse, so phones keep the pixel art */
@media (hover: hover) {
  .tile--avatar:hover .avatar-photo {
    opacity: 1;
    filter: blur(0);
    transform: scale(1);
  }
}

/* ==========================================================
   Office hours tile — the Calendar "Up Next" widget from the iPhone Home Screen:
   red weekday and big date (filled in by officehours.js), then the week's slots.
   ========================================================== */
.tile--office {
  --ch-red: #d70015;               /* Apple's red, in the darker shade that passes 4.5:1 for small text */
  --ch-green: #34c759;             /* the open slot's calendar colour */
  --ch-green-text: #1b7a33;
  --ch-green-wash: rgba(52, 199, 89, .14);
  gap: 10px;
  font-family: var(--font-system);
}
:root[data-theme="dark"] .tile--office {
  --ch-red: #ff6961;
  --ch-green: #30d158;
  --ch-green-text: #4ade80;
  --ch-green-wash: rgba(48, 209, 88, .16);
}
.ch-head { display: flex; justify-content: space-between; align-items: flex-start; }
.tile--office .arrow { position: static; flex: none; }   /* top-right here, where the widget has space */
.ch-weekday {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ch-red);
}
.ch-date { font-size: 44px; font-weight: 400; line-height: 1; letter-spacing: -0.02em; }

/* Events: a coloured bar, the title, and the "time" underneath.
   Spaced with margins rather than a gap, so a folded-away event takes no space at all. */
.ch-events { display: grid; margin-top: auto; }
.ch-event {
  display: grid;
  padding: 4px 8px 4px 12px;
  border-radius: 8px;
  position: relative;
  line-height: 1.3;
}
.ch-event + .ch-event { margin-top: 4px; }
.ch-event::before {
  content: "";
  position: absolute;
  left: 3px; top: 5px; bottom: 5px;
  width: 3px;
  border-radius: 2px;
  background: var(--outline);
}
.ch-title { font-size: 13px; font-weight: 600; }
.ch-time { font-size: 12px; color: var(--muted); }
.ch-event--booked .ch-title { color: var(--muted); }   /* booked slots recede */
.ch-event--open { background: var(--ch-green-wash); }
.ch-event--open::before { background: var(--ch-green); }
.ch-event--open .ch-time { color: var(--ch-green-text); font-weight: 500; }
.ch-foot { font-size: 12px; color: var(--muted); }

/* Booking (officehours.js). The whole tile opens the day picker, so it lifts like a link tile.
   Hovering: the open slot grows and glows, the booked ones fade back, and its text says "Book it →". */
.tile--office { cursor: pointer; transition: transform .3s ease, box-shadow .3s ease; }
.tile--office:hover {
  transform: translateY(-3px);
  box-shadow: 0 0 0 1px var(--hover-ring), 0 16px 32px -16px var(--shadow);
}
.tile--office:hover .arrow { background: var(--text); color: var(--on-text); border-color: var(--text); }
.ch-event--booked {
  max-height: 3em;
  overflow: hidden;
  transition: opacity .2s, max-height .25s ease, margin .25s ease, padding .25s ease;
}
.tile--office:hover .ch-event--booked { opacity: .55; }
.ch-event--open { transition: transform .2s ease, box-shadow .2s ease; }
.tile--office:hover .ch-event--open,
.ch-event--open:has(:focus-visible) {   /* keyboard focus only, so it doesn't linger after a click */
  transform: scale(1.03);
  box-shadow: 0 0 0 1px var(--ch-green), 0 6px 18px -8px var(--ch-green);
}

/* The slot is a real button, dressed as the event it replaces */
.ch-slot {
  display: grid;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  position: relative;
}
.ch-slot:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; border-radius: 4px; }
/* "Available this week" and "Book it →" share one spot and swap on hover */
.ch-time { display: grid; }
.ch-status, .ch-cta { grid-area: 1 / 1; transition: opacity .2s, transform .2s ease; }
.ch-cta { opacity: 0; transform: translateY(4px); }
.tile--office:hover .ch-event--open:not(.is-open):not(.is-sent) .ch-status { opacity: 0; transform: translateY(-4px); }
.tile--office:hover .ch-event--open:not(.is-open):not(.is-sent) .ch-cta { opacity: 1; transform: none; }

/* The day picker slides open under the slot; the booked events fold away to make room.
   (Animating grid rows from 0fr to 1fr is how CSS animates to an unknown height.) */
.ch-days { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .25s ease; }
.ch-days-inner { overflow: hidden; display: flex; gap: 6px; }
.is-open .ch-days { grid-template-rows: 1fr; }
.is-open .ch-days-inner { padding: 8px 0 4px; }
.tile--office.is-picking .ch-event--booked { max-height: 0; margin: 0; padding-block: 0; opacity: 0; }
.ch-day {
  flex: 1;
  display: grid;
  justify-items: center;
  padding: 5px 0;
  border-radius: 8px;
  background: var(--tile);
  color: var(--ch-green-text);
  line-height: 1.2;
  transition: background .2s, color .2s;
}
.ch-day-name { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.ch-day-num { font-size: 15px; font-weight: 600; }
.ch-day:hover, .ch-day:focus-visible { background: var(--ch-green-text); color: var(--tile); }
.ch-day:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

/* Requested: a tick pops in on the right of the slot */
.ch-tick {
  position: absolute;
  right: 0; top: 50%;
  width: 20px; height: 20px;
  margin-top: -10px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--ch-green-text);
  transform: scale(0);
  transition: transform .4s cubic-bezier(.3, 1.6, .5, 1);
}
.ch-tick svg { width: 12px; height: 12px; fill: none; stroke: var(--tile); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.is-sent .ch-tick { transform: scale(1); }
.is-sent .ch-slot { padding-right: 28px; }

/* Newsletter, styled as the macOS Mail app: mailbox sidebar + inbox of past issues (newsletter.js).
   On phones the sidebar hides, like Mail on iPhone. Its traffic lights are .ss-lights. */
.tile--newsletter {
  flex-direction: row;
  gap: 0;
  padding: 0;
  font-family: var(--font-system);
}
.ml-sidebar {
  flex: 0 0 clamp(150px, 30%, 190px);
  padding: 16px 10px;
  background: var(--ss-sidebar);
  border-right: 1px solid var(--line);
}
.ml-heading { margin: 0 0 4px 8px; font-size: 11px; font-weight: 600; color: var(--muted); }
.ml-box {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 7px;
  font-size: 13px;
  color: var(--text);
}
.ml-box.is-current { background: var(--ss-hover); }
.ml-box.is-current .ml-count { color: var(--body-text); }   /* --muted is too faint on the highlight */
.ml-box svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--apple-blue); }
.ml-box .ml-star { color: var(--apple-orange); }
.ml-count { margin-left: auto; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

.ml-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ml-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px 12px 18px;
  border-bottom: 1px solid var(--line);
}
.ml-title { font-size: 15px; font-weight: 600; }
.ml-sub { font-size: 12px; color: var(--muted); }
.ml-subscribe {
  margin-left: auto;
  flex-shrink: 0;
  padding: 8px 16px;
  border-radius: 999px;
  background: var(--ss-select);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  transition: filter .2s;
}
.ml-subscribe:hover { filter: brightness(1.12); }
.ml-subscribe:focus-visible { outline: 2px solid var(--ss-select); outline-offset: 2px; }

/* The inbox: one row per issue, each a link to it on Substack; a blue dot marks unread ones. It scrolls inside the tile, like a window
   (overscroll-behavior stops the page scrolling too when the list reaches its end). */
.ml-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  margin: 0;
  padding: 6px 8px;
  list-style: none;
}
.ml-row {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  column-gap: 8px;
  padding: 9px 10px;
  position: relative;
  overflow: hidden;           /* keeps text inside while the newest issue slides in */
  border-radius: 8px;
  color: var(--text);
  transition: background .15s;
}
.ml-row:focus-visible { outline: 2px solid var(--ss-select); outline-offset: -2px; }
/* Hairline between emails, starting under the text like Mail's (a straight line, not the row's rounded edge) */
.ml-item + .ml-item .ml-row { margin-top: 1px; overflow: visible; }
.ml-item + .ml-item .ml-row::before { content: ""; position: absolute; top: -1px; left: 28px; right: 10px; height: 1px; background: var(--line); }
.ml-dot { grid-row: 1 / span 3; width: 8px; height: 8px; margin-top: 5px; border-radius: 50%; background: var(--apple-blue); }
.ml-row.is-read .ml-dot { visibility: hidden; }
.ml-from { font-size: 13px; font-weight: 600; }
.ml-date { font-size: 12px; color: var(--muted); }
.ml-subject, .ml-preview { grid-column: 2 / -1; overflow: hidden; }
.ml-subject { font-size: 13px; white-space: nowrap; text-overflow: ellipsis; }
/* Up to two lines of preview, then "…", like Mail */
.ml-preview {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
/* Pointing at an email selects it in Mail's blue (computers only) */
@media (hover: hover) {
  .ml-row:hover { background: var(--ss-select); }
  .ml-row:hover::before, .ml-item:hover + .ml-item .ml-row::before { opacity: 0; }
  .ml-row:hover > * { color: #fff; }
  .ml-row:hover .ml-dot { background: #fff; }
}

/* Entrance: the newest issue arrives at the top of the inbox the first time the tile is seen (newsletter.js).
   Done with transforms, not by growing the row, so nothing actually moves on the page (browsers
   count that as "layout shift"): the older emails start nudged up over the new one's space
   (--ml-shift, measured by newsletter.js) and glide down while it fades in. */
.tile--newsletter.ml-intro .ml-new { opacity: 0; }
.tile--newsletter.ml-intro .ml-item + .ml-item { transform: translateY(calc(-1 * var(--ml-shift, 0px))); }
.tile--newsletter.ml-intro.is-revealed .ml-new { opacity: 1; transition: opacity .4s ease .15s; }
.tile--newsletter.ml-intro.is-revealed .ml-item + .ml-item { transform: none; transition: transform .5s ease; }

/* macOS traffic lights, used by the Mail tile's sidebar */
.ss-lights { display: flex; gap: 8px; margin: 0 0 14px 8px; }
.ss-lights span { width: 12px; height: 12px; border-radius: 50%; background: #ff5f57; }
.ss-lights span:nth-child(2) { background: #febc2e; }
.ss-lights span:nth-child(3) { background: #28c840; }

/* Frosted caption pill beside the corner arrow (Photo Booth tile) */
.caption {
  position: absolute;
  left: 64px; bottom: 22px;
  padding: 6px 12px;
  background: var(--glass);
  backdrop-filter: blur(8px);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: calc(100% - 84px);
}

/* Dark tiles: a faint edge keeps them visible on the dark page */
.tile--spotify, .tile--findmy { outline: 1px solid var(--dark-tile-edge); outline-offset: -1px; }

/* ==========================================================
   Contact tile — a card from the Contacts app (iPhone / Mac):
   photo and name on top, a row of action buttons, then the email field.
   ========================================================== */
.tile--contact {
  --ct-blue: #0071e3;       /* Apple blue, darkened a touch so small text passes 4.5:1 */
  justify-content: center;
  gap: 12px;
  font-family: var(--font-system);
}
:root[data-theme="dark"] .tile--contact { --ct-blue: #409cff; }   /* lighter blue reads better on dark */

.ct-head { display: grid; justify-items: center; text-align: center; }
.ct-photo {
  width: 56px; height: 56px;
  margin-bottom: 8px;
  border-radius: 50%;
  object-fit: cover;
}
.ct-name { font-size: 19px; font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; }
.ct-note { font-size: 12.5px; color: var(--muted); line-height: 1.35; }

/* Action buttons: blue icon over a small lowercase label, like mail / call / video on iPhone */
.ct-actions { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.ct-action {
  display: grid;
  justify-items: center;
  gap: 3px;
  padding: 8px 0 6px;
  border-radius: 12px;
  background: var(--card);
  color: var(--ct-blue);
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  transition: background .2s, transform .15s;
}
.ct-action svg { width: 20px; height: 20px; }
.ct-action:hover { background: var(--glass-active); }
.ct-action:active { transform: scale(.96); }
.ct-action:focus-visible,
.ct-field-link:focus-visible,
.ct-copy:focus-visible { outline: 2px solid var(--ct-blue); outline-offset: 2px; }

/* Email field: grey label above the blue address, with a copy button on the right */
.ct-field {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 8px 7px 12px;
  border-radius: 12px;
  background: var(--card);
}
.ct-field-link { flex: 1; min-width: 0; display: grid; line-height: 1.3; }
.ct-field-label { font-size: 11px; color: var(--muted); }
.ct-field-value {
  font-size: 13.5px;
  color: var(--ct-blue);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ct-field-link:hover .ct-field-value { text-decoration: underline; }
.ct-copy {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ct-blue);
  cursor: pointer;
  transition: background .2s;
}
.ct-copy[hidden] { display: none; }
.ct-copy:hover { background: var(--glass-active); }
.ct-copy svg { width: 18px; height: 18px; grid-area: 1 / 1; transition: opacity .2s, transform .2s; }
.ct-done-icon { opacity: 0; transform: scale(.6); }
.ct-copy.is-copied .ct-copy-icon { opacity: 0; transform: scale(.6); }
.ct-copy.is-copied .ct-done-icon { opacity: 1; transform: none; }
.ct-status {   /* read out by screen readers only */
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* ==========================================================
   Spotify playlists tile — mimics the Spotify desktop app
   ========================================================== */
.tile--spotify {
  --sp-color: #b43a64;      /* set per playlist by playlists.js */
  --sp-green: #1ed760;      /* play button */
  /* Light mode: Spotify has no light theme, so this is our own take on one */
  --sp-bg: var(--tile);
  --sp-text: #111;
  --sp-text-2: #5e5e66;
  --sp-text-on-glow: #3d3d44;  /* small text on the coloured header: stronger than --sp-text-2 */
  --sp-accent: #12803b;     /* darker green so the selected playlist reads on a light background */
  --sp-glow-opacity: .32;   /* a pale tint of the playlist colour, so dark text stays readable */
  --sp-hover: rgba(0,0,0,.06);
  --sp-rule: rgba(0,0,0,.1);
  --sp-player-bg: #ebebef;   /* a step darker than the tile, like the app's bottom bar */
  --sp-player-btn: #111;
  --sp-player-btn-icon: #fff;
  --sp-track: #dcdce1;
  --sp-cover-shadow: rgba(0,0,0,.22);
  padding: 24px 24px 0;
  gap: 0;
  background: var(--sp-bg);
  color: var(--sp-text);
}
/* Dark mode: the real Spotify app look */
:root[data-theme="dark"] .tile--spotify {
  --sp-bg: #121212;
  --sp-text: #fff;
  --sp-text-2: #b3b3b3;
  --sp-text-on-glow: #fff;
  --sp-accent: #1ed760;
  --sp-glow-opacity: .85;
  --sp-hover: rgba(255,255,255,.1);
  --sp-rule: rgba(255,255,255,.1);
  --sp-player-bg: #000;
  --sp-player-btn: #fff;
  --sp-player-btn-icon: #000;
  --sp-track: #4d4d4d;
  --sp-cover-shadow: rgba(0,0,0,.5);
}

/* The header (logo, cover, title) with a colour glow behind it that fades out at its
   bottom, like Spotify's playlist header. The glow stops above the list, so the
   list's small grey text always sits on the plain background. */
.sp-head { position: relative; }
.sp-glow {
  position: absolute;
  inset: -24px -24px 0;       /* out to the tile's edges (it has 24px padding) */
  background: var(--sp-color);
  -webkit-mask-image: linear-gradient(#000 0%, transparent 100%);
  mask-image: linear-gradient(#000 0%, transparent 100%);
  opacity: var(--sp-glow-opacity);
  transition: background-color .6s ease;
  pointer-events: none;
}
.tile--spotify > * { position: relative; }
.tile--spotify > :not(.sp-list) { flex-shrink: 0; }  /* only the list may shrink */
.sp-head > :not(.sp-glow) { position: relative; }

.sp-top { display: flex; align-items: center; gap: 8px; margin-bottom: 18px; }
.sp-logo { width: 22px; height: 22px; }
.sp-top-label { font-size: 12px; font-weight: 600; letter-spacing: .02em; }

/* Hero */
.sp-hero { display: flex; align-items: flex-end; gap: 20px; margin-bottom: 18px; }
.sp-cover {
  width: clamp(100px, 9.5vw, 148px);
  aspect-ratio: 1;
  flex-shrink: 0;
  object-fit: cover;          /* crops wide images into a square, like Spotify does */
  border-radius: 6px;
  box-shadow: 0 8px 32px var(--sp-cover-shadow);
  transition: opacity .15s ease;
}
.sp-cover.is-swapping { opacity: 0; }
.sp-hero-text { min-width: 0; }
.sp-kicker { font-size: 12px; font-weight: 500; }
.sp-title {
  font-size: clamp(28px, 3.4vw, 44px);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1.05;
  margin: 6px 0 10px;
  overflow-wrap: anywhere;
}
.sp-meta { font-size: 13px; color: var(--sp-text-on-glow); }
.sp-meta strong { color: var(--sp-text); font-weight: 700; }

/* Big green play button, bottom-right of the hero */
.sp-hero .sp-play { margin-left: auto; flex-shrink: 0; }
.sp-play {
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--sp-green);
  color: #000;
  transition: transform .15s ease, background .15s ease;
}
.sp-play svg { width: 24px; height: 24px; }
.sp-play:hover { transform: scale(1.06); background: #3be477; }
.sp-open { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--sp-text-on-glow); transition: color .15s; }
.sp-open:hover { color: var(--sp-text); }

/* Track-list style playlist list */
.sp-list-head,
.sp-row {
  display: grid;
  grid-template-columns: 24px 38px 1fr auto;
  align-items: center;
  column-gap: 14px;
}
.sp-list-head {
  padding: 0 12px 8px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--sp-rule);
  font-size: 12px;
  color: var(--sp-text-2);
}
.sp-list-head span:nth-child(2) { grid-column: 2 / 4; }
/* If there are more playlists than fit, the list scrolls inside the tile */
.sp-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--sp-track) transparent;
  padding-bottom: 8px;
}
.sp-row {
  width: 100%;
  padding: 5px 12px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--sp-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .15s;
}
.sp-row:hover { background: var(--sp-hover); }
.sp-row:focus-visible { outline: 2px solid var(--sp-text); outline-offset: -2px; }
.sp-row img { width: 38px; height: 38px; object-fit: cover; border-radius: 4px; }
.sp-num { font-size: 14px; color: var(--sp-text-2); text-align: center; font-variant-numeric: tabular-nums; }
.sp-name { display: flex; flex-direction: column; font-size: 14px; font-weight: 500; min-width: 0; }
.sp-name small,
.sp-player-name small { font-size: 12px; font-weight: 400; color: var(--sp-text-2); }
.sp-count { font-size: 13px; color: var(--sp-text-2); font-variant-numeric: tabular-nums; }

/* Selected row: green title + animated equaliser bars instead of the number */
.sp-row.is-active .sp-name { color: var(--sp-accent); }
.sp-row.is-active .sp-num {
  font-size: 0;
  height: 14px;
  background:
    linear-gradient(var(--sp-accent) 0 0) 3px 100% / 3px 60% no-repeat,
    linear-gradient(var(--sp-accent) 0 0) 10px 100% / 3px 100% no-repeat,
    linear-gradient(var(--sp-accent) 0 0) 17px 100% / 3px 40% no-repeat;
  animation: sp-eq 1s ease-in-out infinite alternate;
}
@keyframes sp-eq {
  0%   { background-size: 3px 60%, 3px 100%, 3px 40%; }
  50%  { background-size: 3px 100%, 3px 35%, 3px 80%; }
  100% { background-size: 3px 30%, 3px 70%, 3px 100%; }
}

/* Mini "now playing" bar */
.sp-player {
  display: grid;
  grid-template-columns: 40px 1fr auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 12px;
  row-gap: 10px;
  margin: auto -24px 0;       /* sticks to the bottom edge of the tile */
  padding: 14px 24px 16px;
  background: var(--sp-player-bg);
}
.sp-player-cover { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; }
.sp-player-name { display: flex; flex-direction: column; font-size: 13px; font-weight: 500; min-width: 0; }
.sp-player-controls { display: flex; align-items: center; gap: 16px; color: var(--sp-text-2); }
.sp-player-controls svg { width: 16px; height: 16px; }
.sp-player-play {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--sp-player-btn);
  color: var(--sp-player-btn-icon);
}
.sp-progress {
  grid-column: 1 / -1;
  height: 4px;
  border-radius: 2px;
  background: var(--sp-track);
  overflow: hidden;
}
.sp-progress span {
  display: block;
  height: 100%;
  background: var(--sp-text);
  transform: scaleX(.35);
  transform-origin: left;
  animation: sp-progress 40s linear infinite;
}
@keyframes sp-progress { from { transform: scaleX(.08); } to { transform: scaleX(.92); } }

/* Smaller Spotify, while the Freeform board is expanded: just the playlist and the
   now-playing bar (the list comes back when the board shrinks again) */
.is-ff-expanded .sp-list-head,
.is-ff-expanded .sp-list { display: none; }
.is-ff-expanded .sp-top { margin-bottom: 12px; }
.is-ff-expanded .sp-cover { width: clamp(72px, 7vw, 104px); }
.is-ff-expanded .sp-title { font-size: clamp(24px, 2.4vw, 34px); margin: 4px 0 6px; }

/* ==========================================================
   App switcher tile — like the macOS Cmd+Tab switcher
   ========================================================== */
.tile--switcher {
  --sw-purple: #5e5ce6;
  --sw-purple-bg: #ececfd;
  --caret-x: 40px;             /* set by switcher.js */
  gap: 0;
  padding: 24px 0 24px 24px;   /* no right padding: cards and dock run off the edge */
  justify-content: center;
}

.sw-label {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}
.sw-label-icon { width: 16px; height: 16px; border-radius: 4px; }

/* Outlined box holding the link cards, with a caret pointing at the selected app */
.sw-cards {
  position: relative;
  padding: 8px;
  border: 1px solid var(--outline);
  border-right: 0;
  border-radius: 14px 0 0 14px;
  margin-bottom: 18px;
}
.sw-cards::before,
.sw-cards::after {
  content: "";
  position: absolute;
  top: 100%;
  left: var(--caret-x);
  translate: -50% 0;
  border: 9px solid transparent;
  transition: left .3s ease;
}
.sw-cards::before { border-top-color: var(--outline); }                 /* caret outline */
.sw-cards::after  { border-top-color: var(--tile); margin-top: -1px; } /* caret fill */

.sw-panel {
  display: none;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
}
.sw-panel::-webkit-scrollbar { display: none; }
.sw-panel.is-active { display: flex; animation: sw-in .35s ease; }
@keyframes sw-in { from { opacity: 0; transform: translateY(4px); } }

.sw-card {
  flex: 0 0 auto;
  min-width: 220px;
  max-width: 320px;   /* cards grow to fit their title, within these limits */
  display: flex;
  flex-direction: column;
  padding: 10px 12px;
  background: var(--card);
  border: 2px solid transparent;
  border-radius: 10px;
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
  transition: border-color .2s, background .2s;
}
/* The first card is highlighted, unless you're hovering another one */
.sw-panel:not(:hover) .sw-card:first-child,
.sw-card:hover { border-color: var(--sw-purple); background: var(--sw-purple-bg); }
.sw-card:not([href]) { cursor: default; }
.sw-card-title {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sw-card-title img { width: 16px; height: 16px; border-radius: 4px; flex-shrink: 0; }
.sw-card-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.sw-emoji { width: 16px; font-size: 15px; line-height: 1; text-align: center; flex-shrink: 0; }
.sw-card-url {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The dock of app icons */
.sw-dock {
  display: flex;
  gap: 8px;
  width: fit-content;
  max-width: calc(100% - 24px);
  padding: 8px;
  background: var(--card);
  border-radius: 20px;
  box-shadow: 0 8px 24px -8px rgba(0,0,0,.18);
}
.sw-app {
  /* A set height as well as width: Safari (so every iPhone browser) grew aspect-ratio
     buttons taller than wide, stretching the white-backed logos into tall rectangles */
  --sw-app-size: clamp(58px, 6vw, 76px);
  flex-shrink: 0;
  width: var(--sw-app-size);
  height: var(--sw-app-size);
  padding: 5px;
  border: 3px solid transparent;
  border-radius: 18px;
  background: transparent;
  cursor: pointer;
  transition: border-color .2s, background .2s, transform .2s;
}
.sw-app:hover { transform: translateY(-2px); }
:root[data-theme="dark"] .tile--switcher { --sw-purple-bg: #2e2c52; }
.sw-app.is-active { border-color: var(--sw-purple); background: var(--sw-purple-bg); }
.sw-app:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
.sw-app img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 22%;           /* app-icon corner shape */
}
/* Logos that aren't already app-icon shaped get a white rounded backing */
.sw-app--padded img {
  padding: 12%;
  background: #fff;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.08);
}

/* ==========================================================
   Photos tile — slideshow in the style of Apple Photos
   ========================================================== */
.tile--photos { padding: 0; background: var(--line); }
.ph-slides, .ph-slide { position: absolute; inset: 0; margin: 0; }
.ph-slide { opacity: 0; transition: opacity .9s ease; }
.ph-slide.is-active { opacity: 1; }
.ph-slide img {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.001);
  transition: transform 6s ease-out;
}
.ph-slide.is-active img { transform: scale(1.07); }   /* slow "Ken Burns" zoom */

/* Location caption, frosted pill bottom-left */
.ph-slide figcaption {
  position: absolute;
  left: 16px; bottom: 16px;
  max-width: calc(100% - 32px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--glass);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-size: 12px;
  font-weight: 600;
}

/* App icon, top-right */
.ph-icon {
  position: absolute;
  top: 16px; right: 16px;
  width: 56px; height: 56px;
  padding: 6px;
  border-radius: 14px;          /* iOS app icon shape */
  background: #fff;
  box-shadow: 0 6px 16px rgba(0,0,0,.18);
  pointer-events: none;
  transition: transform .3s ease;
}
.tile--photos:hover .ph-icon { transform: rotate(-6deg) scale(1.05); }
.ph-petals { isolation: isolate; }      /* petals blend with each other, not the photo */
.ph-petals rect { opacity: .78; mix-blend-mode: multiply; }

/* Page dots, top-left (opposite the app icon, leaving the bottom for the caption) */
.ph-dots {
  position: absolute;
  left: 16px; top: 16px;
  display: flex; gap: 5px;
  padding: 5px 7px;
  border-radius: 999px;
  background: rgba(0,0,0,.25);
  backdrop-filter: blur(6px);
  pointer-events: none;
}
.ph-dots span {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: rgba(255,255,255,.5);
  transition: background .3s, width .3s;
}
.ph-dots span.is-active { width: 14px; border-radius: 3px; background: #fff; }

/* Invisible full-tile button for "next photo" */
.ph-next {
  position: absolute; inset: 0;
  border: 0; background: transparent;
  cursor: pointer;
  border-radius: inherit;
}
.ph-next:focus-visible { outline: 3px solid var(--text); outline-offset: -3px; }

/* Photo details: ⓘ button bottom-right, opening a frosted panel like Apple Photos' info view */
.tile--photos:has(.ph-info-button:not([hidden])) .ph-slide figcaption {
  max-width: calc(100% - 32px - 44px);   /* leave room for the ⓘ button */
}
.ph-info-button {
  position: absolute;
  right: 16px; bottom: 16px;
  z-index: 2;
  width: 32px; height: 32px;
  display: grid; place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--glass);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--text);
  cursor: pointer;
  transition: transform .2s ease, background .2s ease;
}
.ph-info-button[hidden] { display: none; }
.ph-info-button:hover { transform: scale(1.08); }
.ph-info-button:focus-visible { outline: 3px solid var(--text); outline-offset: 2px; }
.ph-info-button svg {
  width: 20px; height: 20px;
  fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round;
}
.ph-info-button[aria-expanded="true"] { background: var(--text); color: var(--on-text); }

.ph-info {
  position: absolute;
  left: 12px; right: 12px; bottom: 60px;   /* sits just above the ⓘ button */
  z-index: 2;
  padding: 14px;
  border-radius: 20px;
  background: var(--glass);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 8px 24px var(--shadow);
  font-family: var(--font-system);
  animation: ph-info-in .25s ease-out;
}
.ph-info[hidden] { display: none; }
@keyframes ph-info-in {
  from { opacity: 0; transform: translateY(8px); }
}
.ph-info p { margin: 0; }
.ph-info [hidden] { display: none; }
.ph-info .ph-info-date { font-size: 14px; font-weight: 600; margin-bottom: 10px; }   /* outranks .ph-info p */
.ph-info-card {
  border-radius: 12px;
  background: var(--card);
  overflow: hidden;
  font-size: 12px;
}
.ph-info-card p { padding: 7px 10px; }
.ph-info-card p + p { border-top: 1px solid var(--line); }
.ph-info-device { font-weight: 600; background: var(--line); }
.ph-info-lens, .ph-info-mp { color: var(--muted); }
.tile--photos.is-showing-info .ph-slide figcaption { opacity: 0; }  /* the panel covers it */
@media (prefers-reduced-motion: reduce) { .ph-info { animation: none; } }

/* ==========================================================
   Find My tile — Sam walking around Westminster, on a light or dark map to match the theme
   ========================================================== */
.tile--findmy {
  --fm-land: #f4f1ea;                    /* light map (Apple Maps daytime) */
  --fm-glass: rgba(255, 255, 255, .82);
  --fm-button: rgba(255, 255, 255, .9);
  --fm-divider: rgba(0, 0, 0, .1);
  --fm-text: #111;
  --fm-text-2: rgba(0, 0, 0, .55);
  --fm-ring: #fff;                       /* ring around Sam's photo */
  padding: 0;
  background: var(--fm-land);
}
:root[data-theme="dark"] .tile--findmy {
  --fm-land: #344559;                    /* dark map, sampled from Find My */
  --fm-glass: rgba(30, 38, 50, .72);
  --fm-button: rgba(69, 86, 110, .85);
  --fm-divider: rgba(255, 255, 255, .15);
  --fm-text: #fff;
  --fm-text-2: rgba(255, 255, 255, .65);
  --fm-ring: #4b5563;
}
/* Show the map that matches the theme (the hidden one isn't downloaded) */
.fm-map--dark,
:root[data-theme="dark"] .fm-map--light { display: none; }
:root[data-theme="dark"] .fm-map--dark { display: block; }
.fm-map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;          /* findmy.js assumes this when placing the pin */
}

/* Sam's pin. The .fm-person box sits exactly on his location; findmy.js moves it. */
.fm-person {
  position: absolute;
  z-index: 2;                 /* always above the card and buttons, so Sam is never hidden */
  top: 0; left: 0;
  width: 0; height: 0;
  will-change: transform;
}
.fm-halo {                    /* the "roughly here" accuracy circle */
  position: absolute;
  left: -30px; top: -30px;
  width: 60px; height: 60px;
  border-radius: 50%;
  background: rgba(52, 199, 89, .22);
  animation: fm-halo 2.4s ease-in-out infinite;
}
@keyframes fm-halo { 50% { transform: scale(1.25); opacity: .6; } }
.fm-dot {
  position: absolute;
  left: -5px; top: -5px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #8e9aab;
  box-shadow: 0 0 0 2px var(--fm-land);
}
.fm-pin {                     /* photo in a grey ring, pointing down at the dot */
  position: absolute;
  left: -24px; top: -62px;
  width: 48px; height: 48px;
  padding: 3px;
  border-radius: 50%;
  background: var(--fm-ring);
  box-shadow: 0 6px 14px rgba(0, 0, 0, .35);
}
.fm-pin::after {
  content: "";
  position: absolute;
  left: 50%; top: 100%;
  translate: -50% -2px;
  border: 7px solid transparent;
  border-top: 9px solid var(--fm-ring);
}
.fm-pin img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }

/* Person card, bottom-left, like Find My's People list */
.fm-card {
  position: absolute;
  bottom: 12px; left: 12px;
  max-width: calc(100% - 94px);   /* leaves room for the buttons on the right */
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 6px 6px;
  border-radius: 14px;
  background: var(--fm-glass);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  color: var(--fm-text);
}
.fm-card img { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.fm-card-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.fm-card-text strong { font-size: 12px; font-weight: 600; }
.fm-card-text small {
  font-size: 11px;
  color: var(--fm-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Compass and zoom buttons, bottom-right (just for show) */
.fm-controls {
  position: absolute;
  right: 12px; bottom: 12px;
  display: flex;
  align-items: flex-end;
  gap: 6px;
  color: var(--fm-text);
  font-size: 13px;
  font-weight: 600;
}
.fm-compass,
.fm-zoom {
  background: var(--fm-button);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .25);
}
.fm-compass {
  width: 30px; height: 30px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border-top: 2px solid #ff453a;   /* the red "north" tick */
}
.fm-zoom {
  display: flex;
  flex-direction: column;
  width: 28px;
  border-radius: 14px;
  text-align: center;
  font-size: 16px;
  font-weight: 400;
  line-height: 26px;
}
.fm-zoom span + span { border-top: 1px solid var(--fm-divider); }

.fm-legal {
  position: absolute;
  top: 8px; right: 12px;
  font-size: 9px;
  color: var(--fm-text-2);
}
.fm-legal:hover { color: var(--fm-text); }

.footer { padding: 40px 0 16px; text-align: center; font-size: 12px; color: var(--muted); }

/* 404 page (404.html): one tile with a few ways back in */
.tile--missing { max-width: 640px; margin: 0 auto; gap: 16px; }
.missing-title { font-size: clamp(30px, 5vw, 44px); font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; text-wrap: balance; }
.missing-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.missing-links .button { display: inline-block; margin: 0; }
.button--soft { background: var(--card); color: var(--text); box-shadow: inset 0 0 0 1px var(--line); }

/* ==========================================================
   Photo Booth — a tile with the app icon; clicking it opens a macOS-style
   window. Like the real app the window is dark in both themes, so its
   colours live here.
   ========================================================== */
.tile--photobooth {
  align-items: flex-start;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: transform .3s ease, box-shadow .3s ease;
}
.tile--photobooth:hover {
  transform: translateY(-3px);
  box-shadow: 0 0 0 1px var(--hover-ring), 0 16px 32px -16px var(--shadow);
}
.tile--photobooth:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.tile--photobooth:hover .arrow { background: var(--text); color: var(--on-text); border-color: var(--text); }
.pb-app-icon {
  width: 52%;
  height: auto;                 /* the width/height in the HTML only reserve its (square) shape */
  margin: auto;                 /* centred in the space under the label */
  filter: drop-shadow(0 12px 18px rgba(0, 0, 0, .18));
  transition: transform .3s ease;
}
/* On hover the icon hops, like an app bouncing in the Dock as it opens */
.tile--photobooth:hover .pb-app-icon { animation: pb-hop .6s ease; }
@keyframes pb-hop {
  30% { transform: translateY(-14px); }
  55% { transform: translateY(0); }
  75% { transform: translateY(-5px); }
}

.pb-window {
  --pb-bg: #1c1c1e;
  --pb-chrome: #2c2c2e;
  --pb-edge: rgba(255, 255, 255, .1);
  --pb-text: #f2f2f4;
  --pb-muted: #a1a1a8;
  --pb-red: #ff3b30;
  /* As wide as fits, but never so tall that the tray drops off a short screen:
     210px is the title bar, toolbar, tray and margins; the picture is 4:3 */
  width: min(760px, calc(100vw - 32px), calc((100dvh - 210px) * 4 / 3));
  max-height: calc(100dvh - 32px);
  margin: auto;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--pb-edge);
  border-radius: 12px;
  background: var(--pb-bg);
  color: var(--pb-text);
  font-family: var(--font-system);
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, .6);
}
.pb-window[open] { display: flex; flex-direction: column; animation: pb-open .25s ease; }
.pb-window::backdrop { background: rgba(0, 0, 0, .45); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
@keyframes pb-open { from { opacity: 0; transform: scale(.96); } }

/* Title bar with traffic lights */
.pb-titlebar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 38px;
  flex-shrink: 0;
  background: var(--pb-chrome);
  border-bottom: 1px solid var(--pb-edge);
}
.pb-lights { position: absolute; left: 14px; display: flex; gap: 8px; }
.pb-light { width: 12px; height: 12px; padding: 0; border: 0; border-radius: 50%; background: #ff5f57; }
.pb-light--min { background: #febc2e; }
.pb-light--max { background: #28c840; }
.pb-light--close { cursor: pointer; }
.pb-light--close:hover { box-shadow: inset 0 0 0 3px rgba(0, 0, 0, .25); }
.pb-light--close:focus-visible { outline: 2px solid var(--pb-text); outline-offset: 2px; }
.pb-title { font-size: 13px; font-weight: 600; color: var(--pb-muted); }

/* The picture */
.pb-stage {
  position: relative;
  aspect-ratio: 4 / 3;
  min-height: 0;
  background: #000;
}
.pb-video { display: none; }   /* the camera feed; the canvas shows it instead */
.pb-canvas { width: 100%; height: 100%; object-fit: cover; }

.pb-message {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 32px;
  text-align: center;
  background: var(--pb-bg);
}
.pb-message[hidden] { display: none; }
.pb-message-title { font-size: 20px; font-weight: 600; }
.pb-message-body { max-width: 38ch; font-size: 14px; color: var(--pb-muted); }
.pb-retry,
.pb-effects-button {
  padding: 6px 14px;
  border: 1px solid var(--pb-edge);
  border-radius: 7px;
  background: #3a3a3c;
  color: var(--pb-text);
  font: 500 13px var(--font-system);
  cursor: pointer;
}
.pb-retry { margin-top: 8px; }
.pb-effects-button[aria-pressed="true"] { background: #5a5a5e; }
.pb-effects-button:disabled { opacity: .4; cursor: default; }

.pb-effect-name {
  position: absolute;
  left: 50%;
  bottom: 14px;
  translate: -50% 0;
  padding: 4px 12px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .55);
  font-size: 13px;
  font-weight: 500;
}

/* Effects: live previews side by side, over the picture */
.pb-grid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-content: center;
  gap: 6px;
  padding: 6px;
  background: #000;
}
.pb-grid[hidden] { display: none; }
.pb-grid-cell {
  position: relative;
  padding: 0;
  border: 3px solid transparent;
  border-radius: 6px;
  overflow: hidden;
  background: none;
  cursor: pointer;
}
.pb-grid-cell canvas { display: block; width: 100%; height: auto; }
.pb-grid-cell span {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 18px 8px 8px;
  background: linear-gradient(transparent, rgba(0, 0, 0, .7));
  color: var(--pb-text);
  font: 500 13px var(--font-system);
}
.pb-grid-cell.is-active { border-color: var(--pb-text); }
.pb-grid-cell:hover { border-color: var(--pb-muted); }
.pb-grid-cell:focus-visible { outline: 2px solid var(--pb-text); outline-offset: -6px; }

/* White flash when the photo is taken */
.pb-flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; }
.pb-flash.is-flashing { animation: pb-flash .6s ease-out; }
@keyframes pb-flash { from { opacity: 1; } }

/* Toolbar: red shutter in the middle, Effects on the right */
.pb-toolbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: 64px;
  flex-shrink: 0;
  padding: 0 16px;
  background: var(--pb-chrome);
  border-top: 1px solid var(--pb-edge);
}
.pb-shutter {
  grid-column: 2;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 3px solid #d0d0d4;
  border-radius: 50%;
  background: var(--pb-red);
  color: #fff;
  cursor: pointer;
  transition: transform .15s ease;
}
.pb-shutter:hover:not(:disabled) { transform: scale(1.06); }
.pb-shutter:active:not(:disabled) { transform: scale(.94); }
.pb-shutter:disabled { opacity: .4; cursor: default; }
.pb-shutter:focus-visible { outline: 2px solid var(--pb-text); outline-offset: 3px; }
.pb-shutter svg { width: 20px; height: 20px; }
.pb-shutter[hidden], .pb-countdown[hidden] { display: none; }
.pb-countdown { grid-column: 2; display: flex; gap: 18px; font-size: 24px; font-weight: 700; color: #48484a; }
.pb-countdown .is-lit { color: var(--pb-red); }
.pb-effects-button { justify-self: end; }

/* Tray of photos taken */
.pb-tray {
  display: flex;
  gap: 8px;
  height: 76px;
  flex-shrink: 0;
  padding: 10px 12px;
  overflow-x: auto;
  background: var(--pb-bg);
  border-top: 1px solid var(--pb-edge);
}
.pb-tray-empty { align-self: center; margin: 0 auto; font-size: 13px; color: var(--pb-muted); }
.pb-photo { flex-shrink: 0; height: 100%; border-radius: 4px; overflow: hidden; animation: pb-drop .4s ease; }
.pb-photo img { height: 100%; width: auto; }
.pb-photo:focus-visible { outline: 2px solid var(--pb-text); outline-offset: 2px; }
@keyframes pb-drop { from { opacity: 0; transform: translateY(-12px); } }

/* ==========================================================
   iMessage notification (imessage.js)
   A macOS-style banner that slides in from the right, in the
   top-right corner (level with the nav on wide screens). Hover shows the close button and Reply.
   ========================================================== */
.im {
  --im-bg: rgba(246, 246, 248, .78);
  --im-edge: rgba(0, 0, 0, .1);
  --im-field: rgba(0, 0, 0, .06);
  --im-button: rgba(255, 255, 255, .9);
  position: fixed;
  top: 88px;
  right: 24px;
  z-index: 30;
  width: 356px;
  max-width: calc(100vw - 24px);
  padding: 12px 14px;
  border-radius: 22px;
  background: var(--im-bg);
  backdrop-filter: blur(30px) saturate(180%);          /* the frosted glass of macOS */
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  box-shadow: 0 0 0 .5px var(--im-edge), 0 12px 32px rgba(0, 0, 0, .16), 0 2px 6px rgba(0, 0, 0, .06);
  font-family: var(--font-system);
  color: var(--text);
  touch-action: pan-y;         /* sideways drags are ours (swipe to dismiss), vertical ones still scroll */
  user-select: none;
  -webkit-user-select: none;
  cursor: default;
  /* Starts off-screen to the right; .is-in brings it on screen */
  transform: translateX(calc(100% + 40px));
  transition: transform .6s cubic-bezier(.2, 1.1, .3, 1), opacity .3s ease;  /* the slight overshoot of a spring */
}
:root[data-theme="dark"] .im {
  --im-bg: rgba(44, 44, 48, .72);
  --im-edge: rgba(255, 255, 255, .16);
  --im-field: rgba(255, 255, 255, .08);
  --im-button: rgba(80, 80, 86, .9);
}
.im[hidden] { display: none; }
.im.is-in { transform: none; }
.im.is-dragging { transition: none; }
/* Leaving: slides back out to the right, speeding up as it goes (like macOS) */
.im.is-out {
  transform: translateX(calc(100% + 40px));
  opacity: 0;
  transition: transform .4s cubic-bezier(.5, 0, .75, 0), opacity .4s ease-in;
}

.im-main { display: flex; gap: 11px; align-items: center; }

/* Sender photo with the little Messages badge in the corner */
.im-avatar { position: relative; flex-shrink: 0; width: 40px; height: 40px; }
.im-avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
.im-badge {
  position: absolute; right: -4px; bottom: -3px;
  width: 19px; height: 19px;
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, .25));
}

.im-text { flex: 1; min-width: 0; }
.im-head { display: flex; align-items: center; gap: 8px; min-height: 20px; }
.im-name { flex: 1; font-size: 13px; font-weight: 600; letter-spacing: -.01em; }
.im-time { font-size: 12px; color: var(--muted); }
.im-body {
  margin-top: 1px;
  font-size: 13px;
  line-height: 1.35;
  color: var(--body-text);
  display: -webkit-box;            /* two lines, then "…", until the reply box opens */
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.im.is-replying .im-body { -webkit-line-clamp: unset; }

/* Round × in the top-left corner, shown on hover (always shown on touch screens) */
.im-close {
  position: absolute; top: -7px; left: -7px;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--im-button);
  box-shadow: 0 0 0 .5px var(--im-edge), 0 1px 3px rgba(0, 0, 0, .15);
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transform: scale(.7);
  transition: opacity .15s ease, transform .15s ease;
}
.im-close svg { width: 10px; height: 10px; }
.im-close:hover { color: var(--text); }

/* "Reply" takes the place of "now" on hover, like the action button on a Mac notification */
.im-reply {
  display: none;
  padding: 3px 11px;
  border: 0;
  border-radius: 999px;
  background: var(--im-field);
  color: var(--text);
  font: 500 12px/1.3 var(--font-system);
  cursor: pointer;
}
.im-reply:hover { background: var(--ss-hover); filter: brightness(.95); }

.im:hover .im-close, .im:focus-within .im-close { opacity: 1; transform: none; }
.im:hover:not(.is-replying) .im-time,
.im:focus-within:not(.is-replying) .im-time { display: none; }
.im:hover:not(.is-replying) .im-reply,
.im:focus-within:not(.is-replying) .im-reply { display: block; }
@media (hover: none) {
  .im .im-close { opacity: 1; transform: none; }
  .im:not(.is-replying) .im-time { display: none; }
  .im:not(.is-replying) .im-reply { display: block; }
}

.im-close:focus-visible, .im-reply:focus-visible, .im-send:focus-visible {
  outline: 2px solid var(--ss-select); outline-offset: 2px;
}

/* The reply box: a grid row that grows from 0 to its natural height */
.im-compose {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .35s cubic-bezier(.2, .9, .3, 1);
}
.im.is-replying .im-compose { grid-template-rows: 1fr; }
.im-compose-inner {
  display: flex;
  gap: 8px;
  min-height: 0;
  overflow: hidden;
  padding-left: 51px;              /* lines up with the message text */
}
.im.is-replying .im-compose-inner { padding-top: 10px; }
.im-input {
  flex: 1;
  min-width: 0;
  padding: 6px 10px;
  border: 0;
  border-radius: 8px;
  background: var(--im-field);
  color: var(--text);
  font: 13px/1.3 var(--font-system);
  user-select: text;
  -webkit-user-select: text;
}
.im-input::placeholder { color: var(--muted); }
/* Focus ring drawn inside the box, so the grow-open wrapper can't clip it */
.im-input:focus { outline: none; box-shadow: inset 0 0 0 2px rgba(0, 122, 255, .6); }
.im-send {
  padding: 6px 14px;
  border: 0;
  border-radius: 8px;
  background: var(--apple-blue);
  color: #fff;
  font: 500 13px/1.3 var(--font-system);
  cursor: pointer;
  transition: opacity .15s ease;
}
.im-send:disabled { opacity: .4; cursor: default; }

/* Phones: full width under the switch, like an iPhone banner */
@media (max-width: 640px) {
  .im { top: 72px; right: 8px; width: auto; left: 8px; max-width: none; }
}

/* Laptops and up: the banner sits in the top-right corner, level with the nav.
   The nav is 485px wide and centred, so the room to its right is 50vw - 242px;
   minus the 24px edge margin and a 12px gap, that's 50vw - 280px. The banner
   takes that room up to its usual 356px, and shrinks to 300px at 1160px wide.
   Narrower than that it doesn't fit, so it stays below the nav (top: 88px above). */
@media (min-width: 1160px) {
  .im { top: 24px; width: min(356px, calc(50vw - 280px)); }
}

/* Reduced motion: fade in and out instead of sliding */
@media (prefers-reduced-motion: reduce) {
  .im, .im.is-out { transform: none; }
  .im:not(.is-in) { opacity: 0; }
}

/* ==========================================================
   Responsive: smaller laptops, tablet (2 columns) and phone (1 column)
   ========================================================== */
/* Smaller laptops: tiles are narrower, so tighten the spacing inside them */
@media (max-width: 1300px) {
  :root { --tile-pad: 24px; }
}

/* Smaller laptops, while it's still four columns: the intro tile has a fixed height, so shrink its text a touch to fit */
@media (min-width: 1181px) and (max-width: 1320px) {
  .intro-body { gap: 12px; }
  .intro-body p { font-size: 16px; }
}

/* Below ~1180px four columns get too narrow for the text tiles, so switch to two */
@media (max-width: 1180px) {
  .grid { grid-template-columns: repeat(2, 1fr); grid-auto-rows: minmax(280px, auto); }
  .tile--intro, .tile--spotify { grid-row: auto; }
  .tile--newsletter { height: 380px; }   /* rows grow to fit here, so fix the height and let the inbox scroll */
}

/* Narrower screens: slimmer nav links so the nav never touches the light/dark switch */
@media (max-width: 760px) {
  .nav-link { padding: 10px 14px; }
}

@media (max-width: 640px) {
  :root { --radius: 24px; }
  .page { padding-top: 88px; }
  /* One centred pill: theme.js moves the light/dark switch inside the nav, before the links */
  .nav { top: 16px; align-items: center; }
  .nav-link { padding: 8px 6px; font-size: 12px; }
  .nav + .theme-toggle { visibility: hidden; }   /* not moved in yet: hide it rather than overlap the nav */
  .nav .theme-toggle {
    position: relative;
    top: 0; left: 0;                /* undo the corner position it has on wide screens */
    width: 34px; height: 34px;      /* same height as the links */
    margin-right: 5px;              /* room for the divider */
    border: 0;
    background: none;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .nav .theme-toggle svg { width: 17px; height: 17px; }
  .nav .theme-toggle::after {       /* thin divider between the switch and the links */
    content: "";
    position: absolute;
    right: -3px; top: 9px; bottom: 9px;
    width: 1px;
    background: var(--outline);
  }
  .grid { grid-template-columns: 1fr; grid-auto-rows: minmax(260px, auto); }
  .tile--intro, .tile--newsletter, .tile--freeform,
  .tile--contact, .tile--spotify, .tile--switcher { grid-column: auto; }
  .tile--switcher { padding: 20px 0 20px 16px; }
  .sw-card { min-width: 200px; max-width: 250px; }
  .sw-dock { gap: 4px; padding: 6px; }
  .sw-app { --sw-app-size: 54px; padding: 4px; border-radius: 16px; }
  .pb-stage { aspect-ratio: 3 / 4; }                /* phones hold the camera upright */
  .pb-window { width: min(760px, calc(100vw - 32px), calc((100dvh - 210px) * 3 / 4)); }
  .pb-toolbar { padding: 0 12px; }
  .tile--spotify { padding: 20px 16px 0; }
  .ss-lights { display: none; }
  /* Newsletter: Mail on iPhone has no sidebar */
  .ml-sidebar { display: none; }
  .tile--newsletter { height: 440px; }
  .ml-toolbar { padding: 16px 16px 12px 20px; }
  .tile--photos, .tile--avatar, .tile--findmy, .tile--photobooth { aspect-ratio: 1; }
  .sp-hero { flex-wrap: wrap; gap: 16px; }
  .sp-hero-text { flex: 1; }
  .sp-cover { width: 64%; max-width: 220px; margin: 0 18% 4px; }
  .sp-list-head, .sp-row { column-gap: 10px; padding-left: 6px; padding-right: 6px; }
  .sp-player { margin: 16px -16px 0; padding: 12px 16px 14px; }
  .intro-title { font-size: 22px; }
  .intro-body p { font-size: 16px; }
}

/* Very small phones: drop the "Home" link (you're already on it) so the nav fits beside the switch */
@media (max-width: 374px) {
  .nav-link[href="/"] { display: none; }
  .nav-link { padding: 8px 5px; }
}

/* Respect people who've asked their device to reduce motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
