/* claudeSouls.
   Its own stylesheet rather than claudeHack's: the HUD is different (health
   pips and a stamina bar instead of a status line), the controls are different
   (a skill bar you drag from), and the palette is warmer because the one
   friendly thing in this game is a fire. */

:root {
  --bg:      #07080a;
  --panel:   #12100f;
  --panel-2: #1a1715;
  --line:    #2e2823;
  --fg:      #ddd6cc;
  --dim:     #8a8078;
  --ember:   #ff9a3c;
  --blood:   #d6412f;
  --good:    #7fd07f;
  --cool:    #79b8e8;
  --font-mono: ui-monospace, "DejaVu Sans Mono", "Cascadia Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0; padding: 0; height: 100%;
  background: var(--bg); color: var(--fg);
  font-family: var(--font-mono); font-size: 14px;
  overscroll-behavior: none; touch-action: manipulation;
  -webkit-touch-callout: none; user-select: none; -webkit-user-select: none;
}
body { overflow: hidden; }

/* One layout, everywhere.
   .
   This game is designed for a phone held in one hand: a square map, nine
   direction buttons under the thumb, a skill bar you drag from. Given a
   1920-wide window the same markup stretched into something nobody designed -
   the map grew, the buttons became a distant strip, and the two halves of the
   screen stopped being one object.
   .
   So the app keeps a phone's width on any screen and the room left over
   becomes a frame rather than more game. Below --frame nothing changes at all,
   which is the point: there is now one layout to reason about instead of two. */
/* A large phone, not a small tablet. 560 gave the hero cards two across and a
   phone gives one, which is two layouts again - the whole point was to have
   one. Change this number and both the game and the title screen follow. */
:root { --frame: 440px; }

#app {
  display: flex; flex-direction: column;
  height: 100dvh; width: 100%;
  max-width: var(--frame);
  margin-inline: auto;
  background: var(--bg);
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* The room left over. Not black: a seam you can see is a frame, and a frame
   says the narrow column is deliberate. Off on a phone, where there is no
   room left over to fill. */
@media (min-width: 441px) {
  /* The title screen keeps the same column, so the frame is behind it too. */
  #splash .splash-inner { box-shadow: 0 0 0 1px var(--line), 0 0 60px 8px rgba(0, 0, 0, .65);
                          background: var(--bg); padding: 14px 14px 20px; }
  body {
    background:
      radial-gradient(120% 80% at 50% 0%, #14100c 0%, #0a0a0c 55%, #050506 100%);
  }
  body::before {
    content: ''; position: fixed; inset: 0; pointer-events: none;
    /* A very faint diagonal grain, so the surround is not a flat void. It is
       meant to be noticed only if you look for it - same rule as the panel
       textures inside the game. */
    background-image: repeating-linear-gradient(
      45deg, rgba(255, 154, 60, .022) 0 2px, transparent 2px 7px);
  }
  #app {
    box-shadow: 0 0 0 1px var(--line), 0 0 60px 8px rgba(0, 0, 0, .65);
  }
}

/* ---------------------------------------------------------------- top bar */
#topbar {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 8px; background: var(--panel-2);
  border-bottom: 1px solid var(--line); flex: 0 0 auto; min-height: 30px;
}
#title { color: var(--ember); font-weight: 700; letter-spacing: .5px; }
#statusline { color: var(--dim); font-size: 11.5px; flex: 1 1 auto; white-space: nowrap;
              overflow: hidden; text-overflow: ellipsis; }
#statusline b { color: var(--fg); }
#statusline .lo { color: #5f5850; }
#topbtns { display: flex; gap: 4px; }
#topbtns button, .btn {
  background: var(--panel); color: var(--fg); border: 1px solid var(--line);
  border-radius: 4px; padding: 4px 8px; font: inherit; font-size: 12px;
  cursor: pointer; min-height: 28px;
}
#topbtns button:active, .btn:active { background: var(--line); }

/* --------------------------------------------------------------- messages */
/* A FIXED height, not a min/max band.
   The map canvas is the largest square that fits, so anything that changes the
   height of this bar changes the size of every tile. The aim readout is written
   here while a finger is down - and "north-east" wraps to a line that "east"
   does not - so the map was resizing and the camera shifting mid-gesture, worst
   on the diagonals. A message bar must never be able to resize the map. */
/* The scrolling log is off.
   .
   It was the only feedback for "how hard did that land", which is why it could
   not simply be hidden - a player watching health bars move for reasons they
   cannot read is worse off than one reading sentences. The numbers float off
   the bodies now (see drawFloaters), so the strip has nothing left to carry
   and its 68 pixels go back to the map - which is where the boss health bar
   is going.
   .
   The log itself is still written and still readable with Ctrl-P. This hides
   a panel, it does not stop recording. `#msgbar.on` brings it back for
   anyone debugging. */
#msgbar {
  display: none;
  flex: 0 0 auto; background: var(--panel);
  border-bottom: 1px solid var(--line);
  padding: 3px 8px; height: 68px; overflow: hidden;
  font-size: 12.5px; line-height: 1.35;
}
#msgbar.on { display: block; }

/* The aim readout is the other thing that lived up there, and it stays - as a
   band across the bottom of the map instead of a line at the top of the
   screen. It has to be findable instantly while a finger is down, which is
   exactly the case where the far edge of the screen is the wrong place. */
#aimline {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 5px 8px; pointer-events: none;
  background: linear-gradient(to top, rgba(7,8,10,.92), rgba(7,8,10,0));
  color: var(--cool); font-weight: 700; font-size: 12.5px;
  text-align: center; white-space: nowrap; overflow: hidden;
}
#aimline[hidden] { display: none; }
#msglines div { white-space: pre-wrap; word-break: break-word; }
/* The aim readout, which has to be findable instantly while a finger is down. */
#msglines .aim { color: var(--cool); font-weight: 700; }
.m-good { color: var(--good); } .m-bad { color: var(--blood); }
.m-warn { color: var(--ember); } .m-magic { color: #c9a2ff; }

/* --------------------------------------------------------------- viewport */
/* The map is square.
   Letting the canvas fill a non-square area means the horizontal field of view
   is wider than the vertical one, so an enemy to your left becomes visible
   sooner than the identical enemy above you. In a game whose whole skill is
   reading what is about to reach you, an asymmetric view is an asymmetric
   warning. The canvas is therefore the largest square that fits, centred, and
   the leftover is just background. */
#viewport {
  position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: #000;
  display: flex; align-items: center; justify-content: center;
  container-type: size;
}
/* Fallback for browsers without container query units: the old stretch-to-fill.
   Wrong aspect, but a visible map beats a zero-sized one. */
#map { display: block; outline: none; touch-action: none; width: 100%; height: 100%; }
/* The largest square that fits, said directly. `aspect-ratio` alone does not do
   it here: `height: 100%` is a definite size and wins over the ratio, which is
   how the canvas ended up 375x513 on a phone. */
@supports (width: 1cqmin) {
  #map { width: 100cqmin; height: 100cqmin; }
}

/* A red wash when you die, so a reset is never ambiguous. */
#flash { position: absolute; inset: 0; pointer-events: none; background: var(--blood); opacity: 0; }
#flash.go { animation: bleed .7s ease-out; }
@keyframes bleed { from { opacity: .55; } to { opacity: 0; } }

/* ----------------------------------------------------------------- gauges */
/* Top-left of the map, the way a Souls game does it.
   No text labels: red pips are health and a green bar is stamina, and the two
   are never going to be confused for one another. Dropping the labels is what
   makes the overlay narrow enough to sit over the map without hiding anything
   that matters. */
/* The HUD tracks the canvas, not the viewport. The map is a centred square, so
   pinning to the viewport corner would leave the bars floating in the letterbox
   - a long way from the map on a wide screen. #hudframe is the same centred
   square as the canvas, so the corner it offers is the map's own corner. */
/* The overlay is translucent, so the gauges beneath it show through as ghost
   blocks behind the text. Above it in the stack was not enough; they have to be
   out of the way entirely while a screen is open. */
#viewport:has(#overlay:not([hidden])) #hud { display: none; }

#hud {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
}
#hudframe { position: relative; width: 100%; height: 100%; }
@supports (width: 1cqmin) {
  #hudframe { width: 100cqmin; height: 100cqmin; }
}
#gauges {
  position: absolute; top: 7px; left: 7px;
  display: flex; flex-direction: column; gap: 4px;
  padding: 5px 7px; border-radius: 6px;
  background: rgba(10, 10, 13, .55);
  box-shadow: 0 1px 6px rgba(0, 0, 0, .5);
}

/* Health is pips, not a bar: "how many hits can I take" must be countable. */
/* One row, always. Sixteen pips wrapping onto a second line changes the shape
   of the bar as you take damage, which is exactly when you are reading it. */
#hpbar { display: flex; gap: 2px; flex-wrap: nowrap; }
#hpbar i {
  display: block; width: 8px; height: 9px; border-radius: 2px; flex: 0 0 auto;
  background: #2a1a18; border: 1px solid #46302a;
}
#hpbar i.on { background: var(--blood); border-color: #ff6a55; }

#stbar {
  position: relative; width: 128px; height: 9px;
  background: #17201c; border: 1px solid #2d3b33; border-radius: 3px; overflow: hidden;
}
#stbar .fill { position: absolute; inset: 0 auto 0 0; background: #4fb36a; transition: width .08s linear; }
#stbar .fill.low { background: #7a6a2a; }
/* The one line on screen that says "you can still afford to dodge". */
#stbar .mark { position: absolute; top: 0; bottom: 0; width: 2px; background: rgba(255,255,255,.55); }
/* Beats, for the one hero whose turn holds more than one. Sits just under the
   stamina bar: how much is left, and how much of this turn is left. */
#stbar .beats { position: absolute; left: 0; top: 100%; display: flex; gap: 3px; padding-top: 3px; }
#stbar .beats i { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.18);
                  box-shadow: inset 0 0 0 1px rgba(0,0,0,.6); }
#stbar .beats i.on { background: #ffd75f; }
#stbar .beats i.spare { border-radius: 2px; }
#stbar .beats i.spare.on { background: #79c0ff; }

/* --------------------------------------------------------------- controls */
#controls {
  flex: 0 0 auto; background: var(--panel-2); border-top: 1px solid var(--line);
  padding: 6px 8px calc(8px + env(safe-area-inset-bottom));
  display: flex; gap: 8px; align-items: flex-start;
}
/* Three rows instead of four, so each key can be bigger in the same footprint.
   Descend, Rest and Esc moved to the context button on the right. */
#pad { display: grid; grid-template-columns: repeat(3, 56px); gap: 5px; flex: 0 0 auto; }
#pad button {
  height: 50px; font: inherit; font-size: 19px; line-height: 1;
  background: var(--panel); color: var(--fg);
  border: 1px solid var(--line); border-radius: 6px; cursor: pointer;
}
#pad button:active { background: var(--ember); color: #000; }

/* minmax(0, 1fr), not 1fr: a bare 1fr refuses to shrink below the content's
   intrinsic width, so adding the shape icons pushed the third button off the
   right-hand edge of a phone. */
/* Every action is refused while you are still swinging, so the whole bar says
   so at once rather than each button failing on its own. */
#skillbar.recovering .skill { opacity: .35; }
#skillbar.recovering .skill.action { opacity: .5; }
#skillbar { flex: 1 1 auto; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
            gap: 4px; }
.skill {
  position: relative; height: 40px; padding: 2px 6px;
  background: var(--panel); color: var(--fg);
  border: 1px solid var(--line); border-radius: 6px;
  font: inherit; font-size: 12px; text-align: left; cursor: pointer;
  touch-action: none;           /* the drag belongs to us, not the browser */
  display: flex; flex-direction: row; align-items: center; gap: 4px;
}
/* Commitment marker: one dot per turn you will be unable to act. Deliberately
   not a number - it sits beside a stamina cost and a cooldown, both numbers,
   and a third digit would read as another quantity to add up rather than as
   "this one takes the next turn away from you". */
.skill .rec {
  position: absolute; top: 2px; right: 4px;
  display: flex; gap: 2px;
  font-size: 7px; line-height: 1;
  color: var(--blood, #c0424a);
  letter-spacing: 1px;
}
.skill .num  { position: absolute; top: 2px; left: 5px; font-size: 9px; color: var(--dim); }
.skill .nm   { font-size: 12px; padding-left: 9px; flex: 1 1 auto;
               white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The shape map on each button. Same colour language as the map itself:
   red is what gets hit, blue is where you end up, pale is you.
   Laid out in the flow rather than pinned, so it can never sit on top of the
   skill's name - the icon is there to make the button readable, and an icon
   that hides the label defeats itself. The bottom-right corner is left clear
   for the stamina cost. */
.skill .ico  { flex: 0 0 auto; height: 26px; width: auto;
               margin-right: 9px; overflow: visible; }
.skill .ic-hit  { fill: var(--blood); }
.skill .ic-step { fill: var(--cool); opacity: .55; }
.skill .ic-guard{ fill: var(--good); opacity: .8; }
.skill .ic-me   { fill: var(--fg); opacity: .9; }
.skill .cost { position: absolute; bottom: 2px; right: 6px; font-size: 10px; color: var(--good); }
.skill .cd   { position: absolute; top: 2px; right: 6px; font-size: 11px; color: var(--ember); }
/* The context button. Deliberately unlike a skill: skills are dragged, this is
   tapped, and one look should say which is which. */
.skill.action {
  flex-direction: column; align-items: center; justify-content: center; gap: 0;
  border-color: #4a4230; background: #22201a; text-align: center;
}
.skill.action .act-name { font-size: 12px; font-weight: 700; color: var(--ember); }
.skill.action .act-sub  { font-size: 9.5px; color: var(--dim); line-height: 1.1; }
.skill.action.is-rest    { border-color: #6a5320; background: #241f14; }
.skill.action.is-descend { border-color: #3f5a6a; background: #16202a; }
.skill.action.is-descend .act-name { color: var(--cool); }
.skill.action.is-blocked { border-color: #5a3a34; background: #241a18; opacity: .75; }
.skill.action.is-blocked .act-name { color: var(--blood); }
.skill.action.is-take    { border-color: #6a5a28; background: #23200f; }
.skill.action.is-take .act-name { color: #e8c25a; }
.skill.action.is-cancel  { border-color: #6a3a34; background: #2a1a18; }
.skill.action.is-cancel .act-name { color: var(--blood); }
.skill.action.is-pack { border-color: #4a4230; background: #1e1c17; }
.skill.action.is-pack .act-name { color: var(--dim); }
/* An empty slot is a hole with a name on it, so the grid never reflows when a
   weapon changes and the player can see what they are missing. */
.skill.empty { opacity: .3; border-style: dashed; }
/* A prepared thing counts down uses rather than cooldown turns, so its
   corner number is a stock, not a timer. */
.skill.prep { border-color: #3d4a52; }
.skill.prep .cd { color: var(--cool); }
.skill.empty .nm { padding-left: 9px; }
.skill.action.idle { opacity: .4; }
.skill.action.idle .act-name { color: var(--dim); }
.skill.action:disabled { cursor: default; }

.skill.armed  { border-color: var(--cool); background: #16222c; }
.skill.cooling{ opacity: .5; }
.skill.poor   { opacity: .55; }
.skill.poor .cost { color: var(--blood); }
.skill:active { background: var(--ember); color: #000; }

@media (max-width: 560px), (max-height: 560px) {
  #topbar { min-height: 26px; padding: 2px 5px; gap: 5px; }
  /* The wordmark is decoration; the floor number is information. On a phone
     the title was eating the whole status line, so it goes. */
  #title { display: none; }
  #statusline { font-size: 10.5px; }
  #statusline .roomy { display: none; }
  #msgbar { height: 52px; font-size: 12px; }
  /* Movement on the left, skills on the right - the original arrangement.
     I had stacked these to buy width for the skill icons, which moved the
     movement keys out from under the thumb to pay for a label. Wrong trade:
     the layout is muscle memory and the label is not. The buttons shrink to
     fit instead - icon and number stay, the name drops on the narrowest
     screens, and the icon is the part that says what the skill does anyway. */
  /* The direction keys get the space the fourth row used to take: 50px on a
     phone instead of 37, which is the whole point of merging those three
     buttons into one. */
  #pad { grid-template-columns: repeat(3, 50px); gap: 4px; }
  #pad button { height: 50px; font-size: 18px; }
  .skill { height: 50px; padding: 2px 4px; }
  .skill .ico { height: 22px; margin-right: 5px; }
  .skill .nm { font-size: 11px; padding-left: 7px; }
  .skill .num { font-size: 8px; }
  .skill .cost { font-size: 9px; }
  .skill.action .act-name { font-size: 11.5px; }
  #hpbar i { width: 7px; height: 8px; }
  #stbar { width: 104px; height: 8px; }

  /* Under ~400px there is no honest way to fit a shape icon and a word on a
     third of the width. The icon wins: it is the thing that says what the
     skill does, and the number still maps to the keyboard. */
  @media (max-width: 400px) {
    .skill .nm { display: none; }
    .skill .ico { margin: 0 auto; }
    /* An empty slot has no icon competing for the width, and its whole job is
       to say what is missing - so it keeps its label. */
    .skill.empty .nm, .skill.prep .nm { display: block; font-size: 10px; padding-left: 9px; }
  }
}

/* Affixes are colour-coded by where they came from, because "which part of this
   weapon is the weapon" is the question the three slots exist to answer. */
.btn.on { background: var(--ember); color: #16161a; border-color: var(--ember); }
.af { display: inline-block; font-size: 11px; padding: 0 5px; margin-right: 4px;
      border-radius: 3px; border: 1px solid; }
.af-innate  { color: #e8c25a; border-color: #6a5a28; }
.af-granted { color: var(--cool); border-color: #3f5a6a; }
.af-temp    { color: var(--good); border-color: #2f5a3a; }

/* --------------------------------------------------------------- overlays */
#overlay {
  /* Above the HUD. The gauges overlay the map, and an overlay that a health bar
     writes through is not an overlay - the pack screen had the pips printed
     across the name of the weapon you were about to swap out. */
  position: absolute; inset: 0; z-index: 10; background: rgba(7,8,10,.96);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 12px; font-size: 13px; line-height: 1.6;
}
#overlay[hidden] { display: none; }
#overlay h2 { margin: 0 0 8px; font-size: 16px; color: var(--ember); }

/* ---------------------------------------------------------------- dialogue
   A face, what they said, and what you can say back. The choices are a
   separate block below the box rather than inside it, so a long speech
   scrolls without pushing the replies off the bottom. */
.dlg { display: flex; gap: 12px; align-items: flex-start; }
.dlg-face {
  flex: 0 0 auto; width: 84px; height: 112px;
  image-rendering: pixelated;
  /* Pinned to the top, not centred. Centring made her drift down the box as
     the speech got longer, so she sat at a different height in every exchange
     - and a face that moves when the words change reads as the face reacting
     to them. She is the fixed thing; the text is what scrolls past her. */
  align-self: flex-start;
  filter: drop-shadow(0 0 10px rgba(255, 190, 120, .18));
}
.dlg-body { flex: 1 1 auto; min-width: 0; }
.dlg .say { margin: 6px 0; line-height: 1.55; }
.dlg-choices {
  display: flex; flex-direction: column; gap: 6px;
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--edge, #3a3a42);
}
.dlg-choice { text-align: left; padding: 9px 11px; }
#overlay h3 { margin: 15px 0 5px; font-size: 13.5px; color: var(--ember); }
#overlay p  { margin: 6px 0; }
#overlay ul { margin: 6px 0; padding-left: 22px; }
#overlay li { margin: 3px 0; }
#overlay code {
  color: var(--ember); background: var(--panel); border: 1px solid var(--line);
  border-radius: 3px; padding: 0 4px; font-size: 12px;
}
#overlay table { border-collapse: collapse; width: 100%; margin: 8px 0; }
#overlay td { padding: 3px 8px 3px 0; vertical-align: top;
              border-bottom: 1px solid rgba(46,40,35,.6); }
#overlay .key { color: var(--ember); white-space: nowrap; }
#overlay .foot {
  position: sticky; bottom: -12px; background: var(--panel-2);
  border-top: 1px solid var(--line); margin: 12px -12px -12px;
  padding: 9px 12px; display: flex; gap: 6px; flex-wrap: wrap;
}

/* ---------------------------------------------------------------- splash */
#splash {
  position: fixed; inset: 0; z-index: 50; background: transparent;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 16px 12px calc(24px + env(safe-area-inset-bottom));
}
#splash[hidden] { display: none; }
/* The same column as the game. It was 660, which put the hero cards in three
   across on a desktop and one across on a phone - two different screens
   describing the same four people. */
.splash-inner { max-width: var(--frame); width: 100%; }
#splash-logo { color: var(--ember); font-size: 9px; line-height: 1.15; margin: 0 0 8px;
               overflow-x: auto; white-space: pre; }
@media (max-width: 560px) { #splash-logo { font-size: 6px; } }
.tagline { color: var(--dim); margin: 0 0 16px; font-size: 12.5px; }
#splash-body h2 { font-size: 14px; color: var(--ember); margin: 18px 0 6px; }
.choice-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 6px; }
.choice {
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  padding: 10px 11px; text-align: left; color: var(--fg); font: inherit;
}
/* A card, not a control. The one thing that used to be selectable here was the
   vow, and choosing is done by walking up to somebody now, so the hand cursor
   and the selected state went with it rather than sitting here suggesting the
   title screen can still decide something. */
.choice b { display: block; color: var(--ember); font-size: 13.5px; }
.choice small { color: var(--dim); font-size: 11.5px; line-height: 1.45; display: block; margin-top: 4px; }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 14px; }
.row input[type=text] {
  background: var(--panel); border: 1px solid var(--line); color: var(--fg);
  font: inherit; padding: 8px 9px; border-radius: 5px; flex: 1 1 150px; min-width: 0;
  user-select: text; -webkit-user-select: text;
}
.big-btn {
  background: var(--ember); color: #1a0d00; border: 0; border-radius: 6px;
  padding: 11px 22px; font: inherit; font-weight: 700; cursor: pointer; font-size: 14px;
}
.note { color: var(--dim); font-size: 12px; line-height: 1.65; }
.note a { color: var(--dim); }
.note a:hover { color: var(--ember); }
.note b { color: var(--fg); }

/* A parked character: still on the cast list, visibly not ready. */
.choice.wip { opacity: .45; }
.choice.wip .tag { font-style: normal; font-size: 11px; margin-left: 6px;
                   padding: 1px 5px; border: 1px solid #6b6b6b; border-radius: 3px;
                   color: #b8b8b8; vertical-align: middle; }
