# Taegeuk — dani.korea's design system
# The machine-readable contract. Source of truth is daniko/docs/DESIGN_SYSTEM.md
# and daniko/lib/core/theme/dds_tokens.dart; this file is the published mirror.
#
# 🚨 THIS IS NOT DDS. dani.korea left the dani design system on 2026-08-05 (§0.2).
# dot-tokens.yaml is NOT edited for anything here — it is shared with dani.go and
# every other dani product, and their rendering must not move. dani.korea owns its
# own theme and its own widgets and stops consuming DDS's visual layer.

version: 1.0.0
updated: 2026-09-01
supersedes: Ledger + Dot Navy, chosen 2026-07-28
base_system: its own — no longer DDS-conformant

base:
  note: >
    §2.2. Cool, not pure black. The old base was #000000 / #F7F7F7 and both are
    replaced: pure black under full-bleed photography makes a hard seam at every
    image edge, and a cool neutral sits with flag blue where a warm one fights it.
    Light mode is NOT an inversion — borders carry depth on light, soft elevation
    carries it on dark.
  light:
    bg:          "#F7F8FA"
    surface:     "#FFFFFF"
    surface_2:   "#EEF1F5"
    border:      "#DDE2E9"
    border_soft: "#E7EBF1"
    text:        "#0E1420"
    text_2:      "#54606F"
    text_3:      "#8593A3"
  dark:
    bg:          "#0D1117"
    surface:     "#161B22"
    surface_2:   "#1D242E"
    border:      "#28313D"
    border_soft: "#1F2731"
    text:        "#F0F3F7"
    text_2:      "#A2AEBD"
    text_3:      "#6E7B8B"

accents:
  note: >
    §2.1. Two accents, taken exactly from the 태극기, each with one job. Blue is
    structure. Red is A DATE OR A DANGER and nothing else — §2.4 is the boundary.
    Every FILLED shape is the exact flag colour in BOTH modes.
  blue:
    light: "#0047A0"
    dark:  "#0047A0"
    job: Navigation, primary buttons, progress, structure
  on_blue:
    light: "#FFFFFF"
    dark:  "#FFFFFF"
    contrast: "8.6:1"
  red:
    light: "#CD2E3A"
    dark:  "#CD2E3A"
    job: A date, or a danger — and nothing else
  on_red:
    light: "#FFFFFF"
    dark:  "#FFFFFF"
    contrast: "5.2:1"
  blue_text:
    light: "#0047A0"
    dark:  "#3D7BD9"
    note: >
      🚨 THE ONLY LIFT, and it is measured. #0047A0 as thin text on #0D1117 is
      2.41:1 and unreadable; #3D7BD9 is 4.55:1. Flag red needs no lift anywhere
      (#CD2E3A on #0D1117 = 3.66:1). Do NOT "fix" this back to #0047A0. The
      precedent is Dot Navy: #000080 measured 1.31:1 on black.
  red_text:
    light: "#CD2E3A"
    dark:  "#E8737F"
    note: >
      Added 2026-08-14. Red as INK on bg, not a fill. Not a second red — `red`
      stays the unlifted flag value. #CD2E3A on #0D1117 is 3.65:1, under the body
      floor; #E8737F is 6.48:1.
  blue_soft:
    light: "#E3ECFA"
    dark:  "#0E1D38"
  red_soft:
    light: "#FDE7E9"
    dark:  "#2E1216"

confidence:
  note: >
    §2.3. verified / stale / unknown / blocking are ICON AND WORD, not hue. One
    colour is added back and only one. 🚨 Never red, in any of the three — a
    missing fact is neither a date nor a danger.
  verified:
    light: "#12855A"
    dark:  "#4ADE80"
  verified_soft:
    light: "#DFF5EB"
    dark:  "#0F2E1C"
  unknown:  Dashed 1px edge, text_3, transparent fill
  stale:    Solid hairline, text_3, and a date in DM Mono
  blocking: The flag red — a blocking conflict is by definition time-bound

red_rule:
  note: >
    §2.4. Red has TWO senses and they never share a shape.
  deadline:
    earns_it: a DATE or a TIME on it
    lives_in: the chrome — a row border, a tag, a container fill, a line of text
    shape: a border, a fill behind words, a 2px edge
  danger:
    earns_it: a threat to a person's body — injury, illness, a hazard to life
    lives_in: inside a drawn diagram, and nowhere else
    shape: the SUBJECT of the drawing — the thing that can hurt you
    opposite_in_a_drawing: a WALL, uncoloured, with hatching beyond it
  never: emphasis · branding · expense · a rule you have broken · a destructive action that is neither time-bound nor dangerous
  test: "can this injure the reader — not 'is this expensive' and not 'do people get this wrong a lot'"

typography:
  families:
    ui:     Manrope
    data:   DM Mono
    korean: IBM Plex Sans KR
  caps:
    note: >
      🚨 The three bounds Ted's rejection produced. The rejected proposal was 900
      at -1.6px tracking and 0.94 leading; the note was "font is too bold they
      look bunched up". The three values compound. Do not raise any of them.
    max_weight: 700
    min_letter_spacing: -0.3
    min_height: 1.16
  scale:
    display:     { size: 34,   weight: 700,     family: ui,   tracking: -0.3, height: 1.16 }
    display_sm:  { size: 27,   weight: 700,     family: ui,   tracking: -0.3, height: 1.16 }
    title:       { size: 25,   weight: 700,     family: ui,   tracking: -0.3, height: 1.2 }
    heading:     { size: 18,   weight: 700,     family: ui,   tracking: -0.2, height: 1.25 }
    body:        { size: 14,   weight: 400,     family: ui,   height: 1.5 }
    body_strong: { size: 14,   weight: 600,     family: ui,   height: 1.5 }
    body_sm:     { size: 12.5, weight: 400,     family: ui,   height: 1.5 }
    label:       { size: 10.5, weight: 700,     family: ui,   tracking: 1.575, height: 1.2, transform: uppercase }
    data:        { size: 12,   weight: 500,     family: data, tracking: 0.24, height: 1.55 }
    amount:      { size: 34,   weight: 700,     family: ui,   tracking: -0.3, height: 1.16 }
  data_rule: >
    🚨 Every ID, PNR, price, date and confirmation number is DM Mono with tabular
    figures. Those are read character by character, or copied. Not up for restyling.
  viewport_scale:
    phone: 1.0
    tablet: 1.2
    desktop: 1.3

spacing:
  grid: 4
  xs: 4
  sm: 8
  md: 12
  lg: 16
  xl: 20
  xxl: 24
  screen_padding: 20
  card_padding: 17
  card_gap: 9
  section_gap: 23
  control_height: 30
  tap_target_min: 44
  tap_target_emergency: 56
  max_prose_width: 680
  max_content_width: 1100

radii:
  note: >
    🚨 REVERSES DDS's "4px radii, no pills, no circles". Values are SEMANTIC — a
    card, a row and a button are three different numbers where DDS had one.
  card: 20
  row: 18
  button: 16
  photo: 16
  hero: 22
  frame: 40
  pill: 999
  divergence: >
    §4 states photo tiles as a range, "16–22". DdsRadii.photo is a single value,
    16. The code ships; the range is not expressible as a token.

borders:
  hairline: 1
  emphasis: 1.5
  strong: 2
  strong_note: "🚨 The only 2px in the system is a red urgent border."

elevation:
  note: >
    🚨 REVERSES DDS's "no shadows, ever". FRAME LEVEL ONLY — not on cards inside a
    list. Fourteen shadowed cards stacked down a screen is the generic look this
    redesign exists to escape.
  frame:
    light: "0 10px 28px rgba(15,35,70,.10)"
    dark:  "0 14px 36px rgba(0,0,0,.55)"

motion:
  note: >
    §8. 160ms, standard ease-out. No bounce and no spring.
    🚨 PARALLAX IS PERMITTED — 2026-08-07, Ted overruled it.
  fast: 100
  normal: 160
  easing: easeOutCubic
  permitted:
    parallax:        { factor: 0.45, note: a cover lags the page it sits behind, inside a ClipRect }
    staggered_entry: { lift: 18, duration: 340, interval: 70, cap: 6 }
    press:           { scale: 0.985, duration: 110 }
    photo_arrival:   { duration: 260, note: cross-fade over the DESIGNED state, never over a blank }
    hero_flight:     { duration: 120, note: shared element on the trip id }

accessibility:
  text_on_accent_min: 4.5
  large_text_min: 3.0
  painted_shape_min: 3.0
  critical_min: 7.0
  critical_note: Emergency and address-card surfaces — read under stress, at arm's length
  tap_target_min: 44
  tightest_pair:
    pair: "#CD2E3A on #1D242E — danger red on dark surface_2"
    ratio: 3.01
    note: The tightest pair in the app. Clears the 3:1 painted-shape floor by 0.01.
  open_under_aa:
    note: >
      🚨 §9.1. Three pairs in the approved palette measure under AA. All three are
      LIGHT mode; dark clears 4.5:1 on all of them. This is OPEN and it is Ted's
      call. Tightening the test to 4.5 is not a code change, it is a palette change.
    pairs:
      - { pair: "text_3 on bg — #8593A3 on #F7F8FA", ratio: 2.95, where: "eyebrows, section labels, counts" }
      - { pair: "verified on verified_soft — #12855A on #DFF5EB", ratio: 4.06, where: "the done / verified tag" }
      - { pair: "red on red_soft — #CD2E3A on #FDE7E9", ratio: 4.39, where: "the urgent tag and the urgent row's deadline line" }

brand:
  note: >
    §10. dani.go's lockup, in flag blue. Same mark, same geometry, same
    proportions — only the string and the colour differ.
    🚨 THE MARK DOES NOT FOLLOW THEME. #0047A0 in both modes: a logo that changes
    colour between modes is a different logo.
  tile: "#0047A0"
  mark: "#FFFFFF"
  mark_contrast: "8.6:1 on the tile"
  face: Space Grotesk Bold — dani.go's, NOT the UI face
  ratios:
    source: danigo/assets/images/dani_logo_dds_v3_light.svg — the artwork wins
    tile_of_nominal_height: 1.96
    radius_of_tile: 0.22
    d_of_tile: 0.658
    dot_of_tile: 0.125
    gap_of_tile: 0.28
    wordmark_of_tile: 0.867
    wordmark_tracking: "-3%"
    baseline_nudge: 0.075
  divergence: >
    Three numbers exist for the wordmark and only one is right. The SVG artwork
    sets 0.867×; dot-tokens.yaml § logo says 0.928×; dani.go's Flutter widget
    draws 1.031×. This system follows the ARTWORK.

imagery:
  note: >
    §0.4, §6.3. Photography leads. The drawn scenes are the fallback, and they
    are DESIGNED — not an empty frame and never a broken-image glyph.
  photographs:
    count: 1102
    source: 한국관광공사 포토코리아
    licence: 공공누리 제1유형
    terms: commercial use and derivatives permitted, ATTRIBUTION REQUIRED
    manifest: docs/collected/imagery/photos.json
    hosting: >
      🚨 source_url in that manifest is a DOWNLOAD QUEUE, not an image source.
      Photographs are fetched into our own storage and never hotlinked.
  drawn_scenes:
    count: 5
    names: [d-seoul, d-palace, d-food, d-mtn, d-market]
    grammar: flat shapes, the flag palette, a gradient sky, no gradient inside the subject
    plus: 🚨 a silhouette — every scene is an elevation with something holding it up
    defined_in: screens/taegeuk-sprite.js
    banned: [Icons.photo, Icons.broken_image, Icons.image]
  exception: >
    Does not apply to places the USER adds — those have no guaranteed photograph
    and are a different card, not a broken one.

survives_from_before:
  note: §0.3. Product rules, not style. None of them changed.
  - English-first naming — every user-visible name reads `English (한국어)`, and copy buttons always copy the Korean
  - Safe areas — non-negotiable, shipped broken twice already
  - Provenance on every externally-sourced surface (§6.2)
  - All text selectable, and the cross-fade transition that exists to protect it
  - "`unknown` is neutral, never red. An honest gap is not an error."

not_adopted:
  note: §12. docs/design/ keeps every proposal as reference.
  - { proposal: "A · Postcard", verdict: Layout adopted wholesale into D }
  - { proposal: "B · Compass", verdict: Not adopted, revisit: Filled tonal containers made status unmissable }
  - { proposal: "C · Passport", verdict: Readiness spine adopted into D, revisit: The per-shelf colour system and the passport stamp }
  - { proposal: "A · Ledger", verdict: Retired, revisit: Its provenance discipline survives in §6.2 }
  - { proposal: "B · Passport / C · Atlas", verdict: Retired, revisit: "C's cluster canvas, if S-31 is revisited" }
