# App Screen (Moonarc)

A phone app's screen drawn from rows instead of a screenshot: a status bar, a title, then rows of fourteen kinds (a big figure, a ring of progress, a checklist, a bar chart, a card, chips, a map with a route, a player, a chat, a list, a week of days, a button, a lock screen's clock, a notification) and a tab bar. Without its chrome it draws a widget, a lock screen or a watch face. It sizes itself to the box it sits in with container units, so inside PhoneFrame it scales with the phone, and it takes its colours from the theme and the colour block it names, so a palette recolours every screen. As it arrives the ring fills, the bars grow, the route draws and the checks tick in, once; inside a Reveal they wait for it. One accessible name for the whole screen, or hidden as decoration. Zero JavaScript.

- Import: `import AppScreen from '@moonarc/core/AppScreen'`
- Install: `npx astro add moonarc` · copy-paste: `npx shadcn@latest add https://moonarc.dev/r/app-screen.json`
- Tier A · category ui · trigger load
- Readout: `<AppScreen title="Today" rows={[{ kind: "ring", … }]} />`
- Browser support: widely (Chrome 105 · Firefox 110 · Safari 16); elsewhere: without container units the screen keeps the page's text size and its rows stack as they are
- Measured cost: 0 B JS (CSS 17.4 kB raw)
- Page: https://moonarc.dev/components/app-screen/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `rows` | `Row[]` | none | The rows, top to bottom. Each has a kind: figure { label, value, note? }, ring { value 0–1, label, text }, checks { items: { text, done?, meta? }[] }, bars { values, labels?, highlight? }, card { title, text?, tag?, block? }, chips { items, active? }, map { label?, seed? }, player { title, meta?, progress 0–1 }, chat { messages: { from: me | them, text }[] }, list { items: { title, meta?, value? }[] }, days { label?, days: a string of x (done) and . (not) }, button { text }, clock { time, date? }, notice { app, title, text?, time? }. |
| `title` | `string` | none | The screen's title, large, under the status bar. |
| `eyebrow` | `string` | none | A small line over the title: a date, a greeting. |
| `tabs` | `{ label: string; icon: Icon }[]` | none | Up to five tabs at the foot. icon is one of home, chart, list, user, search, plus, bell, book, moon, map, chat or wallet. |
| `active` | `number` | `0` | The tab marked as current. |
| `block` | `1 | 2 | 3 | 4 | 5` | none | The colour block the accent takes (the ring, the done checks, the chips, the buttons); unset, the theme's primary. A card can name its own block. |
| `time` | `string` | `'9:41'` | The clock in the status bar. |
| `label` | `string` | none | What the screen shows, for a screen reader. Unset, the screen is decoration and hidden from assistive technology. |
| `chrome` | `boolean` | `true` | The status bar. false draws a widget or a watch face: the rows edge to edge, at the size of the box. |

## Usage

```astro
<PhoneFrame width="17rem">
  <AppScreen
    eyebrow="Friday 9 October"
    title="Today"
    block={1}
    label="The app's today screen: three of four habits done and a six-day streak"
    rows={[
      { kind: 'ring', value: 0.75, text: '3 of 4', label: 'habits done' },
      { kind: 'checks', items: [{ text: 'Walk 20 minutes', done: true }, { text: 'Read 10 pages', done: true }, { text: 'No phone after 10', done: true }, { text: 'Water the plants' }] },
      { kind: 'days', label: 'This week', days: 'xxxxxx.' },
    ]}
    tabs={[{ label: 'Today', icon: 'home' }, { label: 'Stats', icon: 'chart' }, { label: 'You', icon: 'user' }]}
  />
</PhoneFrame>
```

## Reduced motion

The screen is drawn as it ends: the ring full to its value, the bars grown, the route drawn, the checks ticked.

## With ClientRouter

CSS only; a fresh page draws the screen again.

## Craft

- Every length is a share of the screen's width (container units), so the same rows read the same on a 14 rem phone and a 22 rem one, the way a real screen scales with its device.
- A screen is a picture of the app: one accessible name says what it shows, and its rows are not read one by one. Without a label it is decoration and hidden.
- The accent is one colour (the block's, or the primary) and the ground is the page's: a palette change repaints the screen with the site.
- The motion is the app's own, once: progress filling, bars growing, a route drawing, a check landing. Nothing loops beside the copy.
- Every length is a share of the screen's own width through one unit, --ma-as-u (1cqi): a widget or a watch face sets it higher around the screen and the same rows draw larger for their box.
- Inside a Reveal that has not arrived the motion is paused, delay included, so a screen far down the page fills as it is scrolled to. --ma-as-at, set on the screen or above it, delays it further: a section that shows its screens in turn sets each one's moment.
- A seed draws the map's route, so the same screen draws the same way on every build.

## Replaces

- App screenshots on a landing page
- Device mockups with static screenshots

## Source

```astro
---
/**
 * AppScreen — a phone app's screen, drawn from rows instead of a screenshot: a status bar, a title, then rows of a dozen
 * kinds (a big figure, a ring of progress, a checklist, a bar chart, a card, chips, a map with a route, a player, a
 * chat, a list, a week of days, a button, a lock screen's clock, a notification) and a tab bar. Without its chrome (no
 * status bar) it draws a widget or a watch face as well. It sizes itself to the box it sits in (container units), so
 * inside PhoneFrame it scales with the phone, and it takes its colours from the theme contract and the colour block it
 * names, so a palette recolours every screen. The ring fills, the bars grow and the checks tick once, as it arrives:
 * inside a Reveal that has not arrived yet they wait for it, and --ma-as-at delays them further (a screen shown later).
 * A picture: one accessible name for the whole screen, or hidden when it is decoration. Zero JavaScript.
 */
import type { HTMLAttributes } from 'astro/types';

type Block = 1 | 2 | 3 | 4 | 5;
type Icon = 'home' | 'chart' | 'list' | 'user' | 'search' | 'plus' | 'bell' | 'book' | 'moon' | 'map' | 'chat' | 'wallet';
type Row =
  | { kind: 'figure'; label: string; value: string; note?: string }
  | { kind: 'ring'; value: number; label: string; text: string }
  | { kind: 'checks'; items: { text: string; done?: boolean; meta?: string }[] }
  | { kind: 'bars'; values: number[]; labels?: string[]; highlight?: number }
  | { kind: 'card'; title: string; text?: string; tag?: string; block?: Block }
  | { kind: 'chips'; items: string[]; active?: number }
  | { kind: 'map'; label?: string; seed?: string }
  | { kind: 'player'; title: string; meta?: string; progress: number }
  | { kind: 'chat'; messages: { from: 'me' | 'them'; text: string }[] }
  | { kind: 'list'; items: { title: string; meta?: string; value?: string }[] }
  | { kind: 'days'; label?: string; days: string }
  | { kind: 'button'; text: string }
  | { kind: 'clock'; time: string; date?: string }
  | { kind: 'notice'; app: string; title: string; text?: string; time?: string };

interface Props extends HTMLAttributes<'div'> {
  rows: Row[];
  /** The screen's title, large, under the status bar. */
  title?: string;
  /** A small line over the title: a date, a greeting. */
  eyebrow?: string;
  /** The tab bar at the foot: up to five, the active one marked. */
  tabs?: { label: string; icon: Icon }[];
  active?: number;
  /** The colour block the screen's accent takes; unset, the theme's primary. */
  block?: Block;
  /** The clock in the status bar. */
  time?: string;
  /** What the screen shows, for a screen reader; unset, the screen is decoration and hidden. */
  label?: string;
  /** The status bar; false draws a widget or a watch face: rows only, edge to edge. */
  chrome?: boolean;
}

const { rows, title, eyebrow, tabs = [], active = 0, block, time = '9:41', label, chrome = true, class: className, ...rest } = Astro.props;
const icons: Record<Icon, string> = {
  home: 'M4 11l8-6 8 6v8H4z M10 19v-5h4v5',
  chart: 'M5 19V11 M10 19V6 M15 19v-9 M20 19v-4',
  list: 'M9 7h11 M9 12h11 M9 17h11 M4.5 7h.01 M4.5 12h.01 M4.5 17h.01',
  user: 'M12 12a4 4 0 100-8 4 4 0 000 8z M5 20c1-4 4-5 7-5s6 1 7 5',
  search: 'M11 18a7 7 0 100-14 7 7 0 000 14z M16 16l4 4',
  plus: 'M12 5v14 M5 12h14',
  bell: 'M6 16V11a6 6 0 0112 0v5l2 2H4z M10 20h4',
  book: 'M5 5h6a2 2 0 012 2v12a2 2 0 00-2-2H5z M19 5h-6a2 2 0 00-2 2v12a2 2 0 012-2h6z',
  moon: 'M18 14A7 7 0 0110 6a7 7 0 108 8z',
  map: 'M4 6l5-2 6 2 5-2v14l-5 2-6-2-5 2z M9 4v14 M15 6v14',
  chat: 'M5 5h14v10H9l-4 4z',
  wallet: 'M4 7h14a2 2 0 012 2v8H4z M4 7V5h12 M16 12h2',
};
/** A seeded route over the map: the same seed draws the same way. */
const route = (seed: string) => {
  let h = 2166136261;
  for (const c of seed) h = Math.imul(h ^ c.charCodeAt(0), 16777619);
  const r = () => ((h = Math.imul(h ^ (h >>> 15), 2246822507)) >>> 0) / 4294967296;
  const pts = Array.from({ length: 5 }, (_, i) => [8 + i * 21 + r() * 6, 12 + r() * 36]);
  return { d: `M${pts.map((p) => p.map((v) => v.toFixed(1)).join(' ')).join(' L')}`, end: pts[pts.length - 1]! };
};
const pct = (v: number) => Math.max(0, Math.min(1, v));
---

<div class:list={['ma-appscreen', className]} data-ma-block={block} data-bare={chrome ? undefined : ''} role={label ? 'img' : undefined} aria-label={label} aria-hidden={label ? undefined : 'true'} {...rest}>
  {chrome && <div class="ma-appscreen__status"><span>{time}</span><span class="ma-appscreen__glyphs"><i></i><i></i><i></i></span></div>}
  {(eyebrow || title) && (
    <div class="ma-appscreen__head">
      {eyebrow && <span class="ma-appscreen__eyebrow">{eyebrow}</span>}
      {title && <span class="ma-appscreen__title">{title}</span>}
    </div>
  )}
  <div class="ma-appscreen__rows">
    {rows.map((row, i) => {
      const style = `--ma-as-i:${i}`;
      if (row.kind === 'figure') return (
        <div class="ma-appscreen__figure" style={style}><span class="ma-appscreen__small">{row.label}</span><span class="ma-appscreen__big">{row.value}</span>{row.note && <span class="ma-appscreen__note">{row.note}</span>}</div>
      );
      if (row.kind === 'ring') return (
        <div class="ma-appscreen__ringrow" style={style}>
          <svg class="ma-appscreen__ring" viewBox="0 0 40 40"><circle cx="20" cy="20" r="16" pathLength="100" /><circle class="ma-appscreen__arc" cx="20" cy="20" r="16" pathLength="100" stroke-dasharray={`${(pct(row.value) * 100).toFixed(1)} 100`} style={`--ma-as-len:${(pct(row.value) * 100).toFixed(1)}`} /></svg>
          <span><span class="ma-appscreen__big">{row.text}</span><span class="ma-appscreen__small">{row.label}</span></span>
        </div>
      );
      if (row.kind === 'checks') return (
        <ul class="ma-appscreen__checks" style={style}>
          {row.items.map((it, j) => (
            <li data-done={it.done ? '' : undefined} style={`--ma-as-j:${j}`}><span class="ma-appscreen__box"><svg viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-7" /></svg></span><span class="ma-appscreen__text">{it.text}</span>{it.meta && <span class="ma-appscreen__small">{it.meta}</span>}</li>
          ))}
        </ul>
      );
      if (row.kind === 'bars') {
        const top = Math.max(...row.values, 1);
        return (
          <div class="ma-appscreen__bars" style={style}>
            {row.values.map((v, j) => (
              <span data-on={row.highlight === j ? '' : undefined} style={`--ma-as-h:${(v / top).toFixed(3)};--ma-as-j:${j}`}><i></i>{row.labels?.[j] && <b>{row.labels[j]}</b>}</span>
            ))}
          </div>
        );
      }
      if (row.kind === 'card') return (
        <div class="ma-appscreen__card" data-ma-block={row.block} style={style}>{row.tag && <span class="ma-appscreen__tag">{row.tag}</span>}<span class="ma-appscreen__text">{row.title}</span>{row.text && <span class="ma-appscreen__small">{row.text}</span>}</div>
      );
      if (row.kind === 'chips') return (
        <div class="ma-appscreen__chips" style={style}>{row.items.map((c, j) => <span data-on={(row.active ?? 0) === j ? '' : undefined}>{c}</span>)}</div>
      );
      if (row.kind === 'map') {
        const r = route(row.seed ?? row.label ?? 'map');
        return (
          <div class="ma-appscreen__map" style={style}>
            <svg viewBox="0 0 110 60" preserveAspectRatio="none"><path class="ma-appscreen__roads" d="M0 18H110 M0 42H110 M30 0V60 M72 0V60 M0 6L110 54" /><path class="ma-appscreen__route" d={r.d} pathLength="100" /><circle cx={r.end[0]} cy={r.end[1]} r="3" /></svg>
            {row.label && <span class="ma-appscreen__maplabel">{row.label}</span>}
          </div>
        );
      }
      if (row.kind === 'player') return (
        <div class="ma-appscreen__player" style={style}>
          <span class="ma-appscreen__play"><svg viewBox="0 0 16 16"><path d="M5 3.5l8 4.5-8 4.5z" /></svg></span>
          <span><span class="ma-appscreen__text">{row.title}</span>{row.meta && <span class="ma-appscreen__small">{row.meta}</span>}</span>
          <span class="ma-appscreen__track"><i style={`--ma-as-p:${pct(row.progress)}`}></i></span>
        </div>
      );
      if (row.kind === 'chat') return (
        <div class="ma-appscreen__chat" style={style}>{row.messages.map((m, j) => <span data-from={m.from} style={`--ma-as-j:${j}`}>{m.text}</span>)}</div>
      );
      if (row.kind === 'list') return (
        <ul class="ma-appscreen__list" style={style}>
          {row.items.map((it) => (
            <li><span class="ma-appscreen__avatar">{it.title.slice(0, 1)}</span><span><span class="ma-appscreen__text">{it.title}</span>{it.meta && <span class="ma-appscreen__small">{it.meta}</span>}</span>{it.value && <span class="ma-appscreen__value">{it.value}</span>}</li>
          ))}
        </ul>
      );
      if (row.kind === 'days') return (
        <div class="ma-appscreen__days" style={style}>
          {row.label && <span class="ma-appscreen__small">{row.label}</span>}
          <span class="ma-appscreen__week">{[...row.days].map((c, j) => <i data-on={c === 'x' ? '' : undefined} style={`--ma-as-j:${j}`}></i>)}</span>
        </div>
      );
      if (row.kind === 'clock') return (
        <div class="ma-appscreen__clock" style={style}>{row.date && <span class="ma-appscreen__date">{row.date}</span>}<span class="ma-appscreen__time">{row.time}</span></div>
      );
      if (row.kind === 'notice') return (
        <div class="ma-appscreen__notice" style={style}>
          <span class="ma-appscreen__icon">{row.app.slice(0, 1)}</span>
          <span><span class="ma-appscreen__small">{row.app}{row.time && <> · {row.time}</>}</span><span class="ma-appscreen__text">{row.title}</span>{row.text && <span class="ma-appscreen__small">{row.text}</span>}</span>
        </div>
      );
      return <span class="ma-appscreen__button" style={style}>{row.text}</span>;
    })}
  </div>
  {tabs.length > 0 && (
    <div class="ma-appscreen__tabs">
      {tabs.slice(0, 5).map((t, j) => (
        <span data-on={j === active ? '' : undefined}><svg viewBox="0 0 24 24"><path d={icons[t.icon]} /></svg>{t.label}</span>
      ))}
    </div>
  )}
</div>

<style is:global>
  @layer components {
    /* sized by the box it sits in: every length is a share of its width (cqi), so a phone twice as wide draws the same
       screen twice as large. Colours are tokens: the ground is the page's, the accent the block's or the primary */
    /* every length is a share of the screen's width, in --ma-as-u: 1cqi unless something around the screen sets more (a
       widget draws its rows larger for its size than a phone does). Read where it is used, so it is the screen's own
       width wherever it is set */
    :where(.ma-appscreen) {
      /* --ma-as-tint and --ma-as-on-tint, set around the screen, win over the block: a watch face lifts its accent */
      --ma-as-accent: var(--ma-as-tint, var(--ma-block-ground, var(--primary, var(--ma-ink))));
      --ma-as-on-accent: var(--ma-as-on-tint, var(--ma-block-text, var(--primary-foreground, var(--ma-panel))));
      --ma-as-line: color-mix(in oklab, var(--foreground, currentColor) 12%, transparent);
      --ma-as-soft: color-mix(in oklab, var(--foreground, currentColor) 6%, var(--background, transparent));
      container-type: inline-size;
      display: flex;
      flex-direction: column;
      gap: 0;
      block-size: 100%;
      min-block-size: 100%;
      overflow: hidden;
      background: var(--background, var(--ma-panel));
      color: var(--foreground, var(--ma-ink));
      font-family: var(--ma-font-body, system-ui, sans-serif);
      line-height: 1.3;
    }
    /* in PhoneFrame it fills the screen: the screen is a size container, and its content's height is only a minimum,
       which a percentage cannot resolve against */
    :where(.ma-phone__screen .ma-appscreen) { min-block-size: 100cqb; }
    /* the block, when one is named, colours the accent only: the screen stays on the page's ground */
    :where(.ma-appscreen[data-ma-block]) { background: var(--background, var(--ma-panel)); color: var(--foreground, var(--ma-ink)); }
    :where(.ma-appscreen *) { box-sizing: border-box; }
    :where(.ma-appscreen ul) { margin: 0; padding: 0; list-style: none; }
    :where(.ma-appscreen svg) { display: block; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; }
    :where(.ma-appscreen__status) { display: flex; justify-content: space-between; align-items: center; padding: calc(4 * var(--ma-as-u, 1cqi)) calc(7 * var(--ma-as-u, 1cqi)) calc(1 * var(--ma-as-u, 1cqi)); font-size: calc(3.6 * var(--ma-as-u, 1cqi)); font-weight: 650; }
    :where(.ma-appscreen__glyphs) { display: flex; gap: calc(1 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__glyphs i) { inline-size: calc(3.6 * var(--ma-as-u, 1cqi)); block-size: calc(2.2 * var(--ma-as-u, 1cqi)); border-radius: calc(0.6 * var(--ma-as-u, 1cqi)); background: currentColor; opacity: 0.85; }
    :where(.ma-appscreen__glyphs i:last-child) { inline-size: calc(5.6 * var(--ma-as-u, 1cqi)); border-radius: calc(1 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__head), :where(.ma-appscreen__rows) { font-size: calc(4.2 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__head) { display: grid; gap: calc(0.6 * var(--ma-as-u, 1cqi)); padding: calc(5 * var(--ma-as-u, 1cqi)) calc(6 * var(--ma-as-u, 1cqi)) calc(3 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__eyebrow), :where(.ma-appscreen__small) { display: block; font-size: calc(3.3 * var(--ma-as-u, 1cqi)); opacity: 0.72; }
    :where(.ma-appscreen__title) { font-family: var(--ma-font-display, inherit); font-size: calc(8 * var(--ma-as-u, 1cqi)); font-weight: 700; letter-spacing: -0.02em; line-height: 1.05; }
    :where(.ma-appscreen__rows) { display: grid; gap: calc(3.5 * var(--ma-as-u, 1cqi)); align-content: start; flex: 1; padding: calc(1 * var(--ma-as-u, 1cqi)) calc(6 * var(--ma-as-u, 1cqi)) calc(4 * var(--ma-as-u, 1cqi)); min-block-size: 0; }
    :where(.ma-appscreen__text) { display: block; font-weight: 600; }
    :where(.ma-appscreen__big) { display: block; font-family: var(--ma-font-display, inherit); font-size: calc(9 * var(--ma-as-u, 1cqi)); font-weight: 700; letter-spacing: -0.03em; line-height: 1; font-variant-numeric: tabular-nums; }
    :where(.ma-appscreen__note) { display: flex; align-items: center; gap: calc(1.4 * var(--ma-as-u, 1cqi)); margin-block-start: calc(1 * var(--ma-as-u, 1cqi)); font-size: calc(3.3 * var(--ma-as-u, 1cqi)); font-weight: 600; }
    /* a note is the text's colour beside a dot of the accent: an accent light enough to fill a ring is too light for text */
    :where(.ma-appscreen__note)::before { content: ''; flex: none; inline-size: calc(1.8 * var(--ma-as-u, 1cqi)); block-size: calc(1.8 * var(--ma-as-u, 1cqi)); border-radius: 50%; background: var(--ma-as-accent); }
    :where(.ma-appscreen__figure) { display: grid; gap: calc(1 * var(--ma-as-u, 1cqi)); padding: calc(4.5 * var(--ma-as-u, 1cqi)); border-radius: calc(5 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); }
    :where(.ma-appscreen__ringrow) { display: flex; gap: calc(4 * var(--ma-as-u, 1cqi)); align-items: center; }
    :where(.ma-appscreen__ring) { inline-size: calc(22 * var(--ma-as-u, 1cqi)); block-size: calc(22 * var(--ma-as-u, 1cqi)); flex: none; rotate: -90deg; }
    :where(.ma-appscreen__ring circle) { stroke: var(--ma-as-line); stroke-width: 5; }
    :where(.ma-appscreen__ring .ma-appscreen__arc) { stroke: var(--ma-as-accent); }
    :where(.ma-appscreen__checks) { display: grid; gap: calc(2.4 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__checks li) { display: grid; grid-template-columns: auto 1fr auto; gap: calc(3 * var(--ma-as-u, 1cqi)); align-items: center; padding: calc(3 * var(--ma-as-u, 1cqi)) calc(3.5 * var(--ma-as-u, 1cqi)); border-radius: calc(3.5 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); }
    :where(.ma-appscreen__box) { display: grid; place-items: center; inline-size: calc(6 * var(--ma-as-u, 1cqi)); block-size: calc(6 * var(--ma-as-u, 1cqi)); border-radius: 50%; box-shadow: inset 0 0 0 calc(0.6 * var(--ma-as-u, 1cqi)) var(--ma-as-line); }
    :where(.ma-appscreen__box svg) { inline-size: calc(4 * var(--ma-as-u, 1cqi)); block-size: calc(4 * var(--ma-as-u, 1cqi)); stroke-width: 2.4; opacity: 0; }
    :where(.ma-appscreen__checks li[data-done] .ma-appscreen__box) { background: var(--ma-as-accent); box-shadow: none; color: var(--ma-as-on-accent); }
    :where(.ma-appscreen__checks li[data-done] .ma-appscreen__box svg) { opacity: 1; }
    :where(.ma-appscreen__checks li[data-done] .ma-appscreen__text) { text-decoration: line-through; text-decoration-color: color-mix(in oklab, currentColor 45%, transparent); }
    :where(.ma-appscreen__bars) { display: flex; align-items: end; gap: calc(2 * var(--ma-as-u, 1cqi)); block-size: calc(30 * var(--ma-as-u, 1cqi)); padding: calc(3 * var(--ma-as-u, 1cqi)); border-radius: calc(4 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); }
    :where(.ma-appscreen__bars span) { display: grid; grid-template-rows: 1fr auto; gap: calc(1 * var(--ma-as-u, 1cqi)); flex: 1; block-size: 100%; }
    :where(.ma-appscreen__bars i) { align-self: end; block-size: calc(var(--ma-as-h) * 100%); min-block-size: calc(1.5 * var(--ma-as-u, 1cqi)); border-radius: calc(1.5 * var(--ma-as-u, 1cqi)); background: color-mix(in oklab, var(--ma-as-accent) 40%, transparent); transform-origin: bottom; }
    :where(.ma-appscreen__bars span[data-on] i) { background: var(--ma-as-accent); }
    :where(.ma-appscreen__bars b) { font-size: calc(2.8 * var(--ma-as-u, 1cqi)); font-weight: 500; text-align: center; opacity: 0.72; }
    :where(.ma-appscreen__card) { display: grid; gap: calc(1 * var(--ma-as-u, 1cqi)); padding: calc(4.5 * var(--ma-as-u, 1cqi)); border-radius: calc(5 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); }
    :where(.ma-appscreen__card[data-ma-block]) { background: var(--ma-block-ground); color: var(--ma-block-text); }
    :where(.ma-appscreen__tag) { justify-self: start; padding: calc(0.6 * var(--ma-as-u, 1cqi)) calc(2 * var(--ma-as-u, 1cqi)); border-radius: 99px; background: var(--ma-as-accent); color: var(--ma-as-on-accent); font-size: calc(2.8 * var(--ma-as-u, 1cqi)); font-weight: 650; }
    :where(.ma-appscreen__chips) { display: flex; flex-wrap: wrap; gap: calc(2 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__chips span) { padding: calc(1.4 * var(--ma-as-u, 1cqi)) calc(3.4 * var(--ma-as-u, 1cqi)); border-radius: 99px; box-shadow: inset 0 0 0 calc(0.5 * var(--ma-as-u, 1cqi)) var(--ma-as-line); font-size: calc(3.4 * var(--ma-as-u, 1cqi)); font-weight: 550; }
    :where(.ma-appscreen__chips span[data-on]) { background: var(--ma-as-accent); color: var(--ma-as-on-accent); box-shadow: none; }
    :where(.ma-appscreen__map) { position: relative; block-size: calc(40 * var(--ma-as-u, 1cqi)); overflow: hidden; border-radius: calc(5 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); }
    :where(.ma-appscreen__map svg) { inline-size: 100%; block-size: 100%; }
    :where(.ma-appscreen__roads) { stroke: var(--ma-as-line); stroke-width: 3; }
    :where(.ma-appscreen__route) { stroke: var(--ma-as-accent); stroke-width: 2.4; stroke-dasharray: 100 100; }
    :where(.ma-appscreen__map circle) { fill: var(--ma-as-accent); stroke: var(--background, #fff); stroke-width: 1.4; }
    :where(.ma-appscreen__maplabel) { position: absolute; inset-inline-start: calc(3 * var(--ma-as-u, 1cqi)); inset-block-end: calc(3 * var(--ma-as-u, 1cqi)); padding: calc(1 * var(--ma-as-u, 1cqi)) calc(2.6 * var(--ma-as-u, 1cqi)); border-radius: 99px; background: var(--background, #fff); font-size: calc(3 * var(--ma-as-u, 1cqi)); font-weight: 650; }
    :where(.ma-appscreen__player) { display: grid; grid-template-columns: auto 1fr; gap: calc(2.4 * var(--ma-as-u, 1cqi)) calc(3 * var(--ma-as-u, 1cqi)); align-items: center; padding: calc(4 * var(--ma-as-u, 1cqi)); border-radius: calc(5 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); }
    :where(.ma-appscreen__play) { display: grid; place-items: center; inline-size: calc(10 * var(--ma-as-u, 1cqi)); block-size: calc(10 * var(--ma-as-u, 1cqi)); border-radius: 50%; background: var(--ma-as-accent); color: var(--ma-as-on-accent); }
    :where(.ma-appscreen__play svg) { inline-size: calc(4.6 * var(--ma-as-u, 1cqi)); block-size: calc(4.6 * var(--ma-as-u, 1cqi)); fill: currentColor; stroke: none; }
    :where(.ma-appscreen__track) { grid-column: 1 / -1; block-size: calc(1.4 * var(--ma-as-u, 1cqi)); border-radius: calc(1 * var(--ma-as-u, 1cqi)); background: var(--ma-as-line); overflow: hidden; }
    :where(.ma-appscreen__track i) { display: block; block-size: 100%; inline-size: calc(var(--ma-as-p) * 100%); background: var(--ma-as-accent); }
    :where(.ma-appscreen__chat) { display: grid; gap: calc(2 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__chat span) { max-inline-size: 78%; padding: calc(2.4 * var(--ma-as-u, 1cqi)) calc(3.4 * var(--ma-as-u, 1cqi)); border-radius: calc(4 * var(--ma-as-u, 1cqi)) calc(4 * var(--ma-as-u, 1cqi)) calc(4 * var(--ma-as-u, 1cqi)) calc(1 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); font-size: calc(3.6 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__chat span[data-from='me']) { justify-self: end; border-radius: calc(4 * var(--ma-as-u, 1cqi)) calc(4 * var(--ma-as-u, 1cqi)) calc(1 * var(--ma-as-u, 1cqi)) calc(4 * var(--ma-as-u, 1cqi)); background: var(--ma-as-accent); color: var(--ma-as-on-accent); }
    :where(.ma-appscreen__list) { display: grid; gap: 0; }
    :where(.ma-appscreen__list li) { display: grid; grid-template-columns: auto 1fr auto; gap: calc(3 * var(--ma-as-u, 1cqi)); align-items: center; padding-block: calc(2.6 * var(--ma-as-u, 1cqi)); border-block-end: calc(0.3 * var(--ma-as-u, 1cqi)) solid var(--ma-as-line); }
    :where(.ma-appscreen__avatar) { display: grid; place-items: center; inline-size: calc(8.5 * var(--ma-as-u, 1cqi)); block-size: calc(8.5 * var(--ma-as-u, 1cqi)); border-radius: calc(2.6 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); font-weight: 700; }
    :where(.ma-appscreen__value) { font-weight: 650; font-variant-numeric: tabular-nums; }
    :where(.ma-appscreen__days) { display: grid; gap: calc(1.6 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__week) { display: grid; grid-template-columns: repeat(7, 1fr); gap: calc(1.6 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen__week i) { aspect-ratio: 1; border-radius: calc(2 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); box-shadow: inset 0 0 0 calc(0.4 * var(--ma-as-u, 1cqi)) var(--ma-as-line); }
    :where(.ma-appscreen__week i[data-on]) { background: var(--ma-as-accent); box-shadow: none; }
    :where(.ma-appscreen__button) { display: grid; place-items: center; min-block-size: calc(11 * var(--ma-as-u, 1cqi)); border-radius: 99px; background: var(--ma-as-accent); color: var(--ma-as-on-accent); font-weight: 650; }
    :where(.ma-appscreen__tabs) { display: flex; justify-content: space-around; padding: calc(2.4 * var(--ma-as-u, 1cqi)) calc(4 * var(--ma-as-u, 1cqi)) calc(6 * var(--ma-as-u, 1cqi)); border-block-start: calc(0.3 * var(--ma-as-u, 1cqi)) solid var(--ma-as-line); background: var(--ma-as-soft); }
    :where(.ma-appscreen__tabs span) { display: grid; justify-items: center; gap: calc(0.6 * var(--ma-as-u, 1cqi)); font-size: calc(2.6 * var(--ma-as-u, 1cqi)); font-weight: 550; opacity: 0.72; }
    :where(.ma-appscreen__tabs span[data-on]) { opacity: 1; font-weight: 700; }
    /* the current tab's icon takes the accent; its word stays the text's colour, which reads on any accent */
    :where(.ma-appscreen__tabs span[data-on] svg) { color: var(--ma-as-accent); }
    :where(.ma-appscreen__tabs svg) { inline-size: calc(6 * var(--ma-as-u, 1cqi)); block-size: calc(6 * var(--ma-as-u, 1cqi)); stroke-width: 1.8; }
    :where(.ma-appscreen__clock) { display: grid; justify-items: center; gap: calc(1 * var(--ma-as-u, 1cqi)); padding-block: calc(6 * var(--ma-as-u, 1cqi)) calc(4 * var(--ma-as-u, 1cqi)); text-align: center; }
    :where(.ma-appscreen__date) { font-size: calc(4 * var(--ma-as-u, 1cqi)); font-weight: 600; opacity: 0.8; }
    :where(.ma-appscreen__time) { font-family: var(--ma-font-display, inherit); font-size: calc(22 * var(--ma-as-u, 1cqi)); font-weight: 650; letter-spacing: -0.04em; line-height: 0.95; font-variant-numeric: tabular-nums; }
    :where(.ma-appscreen__notice) { display: grid; grid-template-columns: auto 1fr; gap: calc(3 * var(--ma-as-u, 1cqi)); align-items: start; padding: calc(3.5 * var(--ma-as-u, 1cqi)); border-radius: calc(5 * var(--ma-as-u, 1cqi)); background: var(--ma-as-soft); box-shadow: 0 0 0 calc(0.3 * var(--ma-as-u, 1cqi)) var(--ma-as-line); }
    :where(.ma-appscreen__icon) { display: grid; place-items: center; inline-size: calc(8.5 * var(--ma-as-u, 1cqi)); block-size: calc(8.5 * var(--ma-as-u, 1cqi)); border-radius: calc(2.4 * var(--ma-as-u, 1cqi)); background: var(--ma-as-accent); color: var(--ma-as-on-accent); font-weight: 700; }
    /* without chrome: a widget or a watch face, the rows edge to edge */
    :where(.ma-appscreen[data-bare] .ma-appscreen__rows) { padding: calc(6 * var(--ma-as-u, 1cqi)); gap: calc(4 * var(--ma-as-u, 1cqi)); }
    :where(.ma-appscreen[data-bare] .ma-appscreen__head) { padding: calc(6 * var(--ma-as-u, 1cqi)) calc(6 * var(--ma-as-u, 1cqi)) 0; }
    /* once, as the screen is drawn: the ring fills, the bars grow, the route draws, the checks and the days tick in */
    @media (prefers-reduced-motion: no-preference) {
      :where(.ma-appscreen__arc) { animation: ma-as-ring 1.1s var(--ma-ease-out, ease-out) both; animation-delay: calc(var(--ma-as-at, 0s) + 0.3s); }
      :where(.ma-appscreen__bars i) { animation: ma-as-grow 0.7s var(--ma-ease-out, ease-out) both; animation-delay: calc(var(--ma-as-at, 0s) + 0.25s + var(--ma-as-j, 0) * 60ms); }
      :where(.ma-appscreen__route) { animation: ma-as-route 1.4s var(--ma-ease-out, ease-out) both; animation-delay: calc(var(--ma-as-at, 0s) + 0.4s); }
      :where(.ma-appscreen__checks li[data-done] .ma-appscreen__box), :where(.ma-appscreen__week i[data-on]) { animation: ma-as-pop 0.4s var(--ma-ease-out, ease-out) both; animation-delay: calc(var(--ma-as-at, 0s) + 0.4s + var(--ma-as-j, 0) * 90ms); }
      :where(.ma-appscreen__track i) { animation: ma-as-track 1.2s var(--ma-ease-out, ease-out) both; animation-delay: calc(var(--ma-as-at, 0s) + 0.3s); }
      /* inside a Reveal that has not arrived, they wait for it (a paused animation's delay does not run either) */
      :where([data-ma-js] .ma-reveal:not(.ma-in) .ma-appscreen :is(.ma-appscreen__arc, .ma-appscreen__bars i, .ma-appscreen__route, .ma-appscreen__box, .ma-appscreen__week i, .ma-appscreen__track i)) { animation-play-state: paused; }
    }
  }
  @keyframes ma-as-ring { from { stroke-dasharray: 0 100; } }
  @keyframes ma-as-grow { from { scale: 1 0; } }
  @keyframes ma-as-route { from { stroke-dashoffset: 100; } }
  @keyframes ma-as-pop { from { scale: 0.6; opacity: 0; } }
  @keyframes ma-as-track { from { inline-size: 0; } }
</style>

```
