dsh-piggy 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +1348 -0
  2. package/LICENSE +32 -0
  3. package/README.md +322 -0
  4. package/THIRD-PARTY.md +31 -0
  5. package/assets/elder.svg +35 -0
  6. package/assets/pig-king-bathe.svg +38 -0
  7. package/assets/pig-king-eat.svg +45 -0
  8. package/assets/pig-king-pet.svg +45 -0
  9. package/assets/pig-king-play.svg +45 -0
  10. package/assets/pig-king-relaxed.svg +56 -0
  11. package/assets/pig-king-study.svg +45 -0
  12. package/assets/pig-king-trip.svg +45 -0
  13. package/assets/pig-king-work.svg +45 -0
  14. package/assets/pig-king.svg +56 -0
  15. package/assets/piglet.svg +30 -0
  16. package/client.js +3438 -0
  17. package/commands.js +252 -0
  18. package/cordis.patch.yml +15 -0
  19. package/core.js +11 -0
  20. package/data.js +10 -0
  21. package/index.js +68 -0
  22. package/package.json +72 -0
  23. package/packages/pet-core/package.json +21 -0
  24. package/packages/pet-core/src/core/activity.js +80 -0
  25. package/packages/pet-core/src/core/care.js +160 -0
  26. package/packages/pet-core/src/core/clock.js +122 -0
  27. package/packages/pet-core/src/core/constants.js +109 -0
  28. package/packages/pet-core/src/core/daily.js +204 -0
  29. package/packages/pet-core/src/core/diary.js +108 -0
  30. package/packages/pet-core/src/core/drops.js +82 -0
  31. package/packages/pet-core/src/core/effects.js +84 -0
  32. package/packages/pet-core/src/core/egg.js +120 -0
  33. package/packages/pet-core/src/core/evolution.js +90 -0
  34. package/packages/pet-core/src/core/growth.js +110 -0
  35. package/packages/pet-core/src/core/illness.js +235 -0
  36. package/packages/pet-core/src/core/interests.js +55 -0
  37. package/packages/pet-core/src/core/inventory.js +177 -0
  38. package/packages/pet-core/src/core/lines.js +157 -0
  39. package/packages/pet-core/src/core/migrate.js +226 -0
  40. package/packages/pet-core/src/core/profile.js +102 -0
  41. package/packages/pet-core/src/core/random.js +79 -0
  42. package/packages/pet-core/src/core/school.js +81 -0
  43. package/packages/pet-core/src/core/settlement.js +308 -0
  44. package/packages/pet-core/src/core/state.js +210 -0
  45. package/packages/pet-core/src/core/travel.js +58 -0
  46. package/packages/pet-core/src/core/upgrades.js +193 -0
  47. package/packages/pet-core/src/core/views.js +55 -0
  48. package/packages/pet-core/src/core/work.js +50 -0
  49. package/packages/pet-core/src/core.js +95 -0
  50. package/packages/pet-core/src/data/daily.js +121 -0
  51. package/packages/pet-core/src/data/drops.js +34 -0
  52. package/packages/pet-core/src/data/evolution.js +41 -0
  53. package/packages/pet-core/src/data/growth.js +72 -0
  54. package/packages/pet-core/src/data/illness.js +188 -0
  55. package/packages/pet-core/src/data/interests.js +39 -0
  56. package/packages/pet-core/src/data/jobs.js +150 -0
  57. package/packages/pet-core/src/data/life.js +88 -0
  58. package/packages/pet-core/src/data/lines.js +198 -0
  59. package/packages/pet-core/src/data/minutes.js +23 -0
  60. package/packages/pet-core/src/data/profile.js +60 -0
  61. package/packages/pet-core/src/data/school.js +99 -0
  62. package/packages/pet-core/src/data/shop.js +142 -0
  63. package/packages/pet-core/src/data/traits.js +42 -0
  64. package/packages/pet-core/src/data/travel.js +87 -0
  65. package/packages/pet-core/src/data.js +52 -0
  66. package/packages/pet-core/src/index.js +12 -0
  67. package/render.js +357 -0
  68. package/routes.js +201 -0
  69. package/scripts/build-client.mjs +100 -0
  70. package/scripts/typecheck.mjs +122 -0
  71. package/snapshot.js +368 -0
  72. package/src/client/art.js +29 -0
  73. package/src/client/constants.js +88 -0
  74. package/src/client/css-base.js +225 -0
  75. package/src/client/css-card.js +63 -0
  76. package/src/client/css-tabs.js +250 -0
  77. package/src/client/css-tiles.js +89 -0
  78. package/src/client/dom.js +33 -0
  79. package/src/client/effects.js +159 -0
  80. package/src/client/format.js +27 -0
  81. package/src/client/index.js +393 -0
  82. package/src/client/io.js +100 -0
  83. package/src/client/layout.js +103 -0
  84. package/src/client/normalize.js +352 -0
  85. package/src/client/panel.js +319 -0
  86. package/src/client/scene.js +113 -0
  87. package/src/client/storage.js +19 -0
  88. package/src/client/styles.js +13 -0
  89. package/src/client/tabs/bag.js +203 -0
  90. package/src/client/tabs/card.js +110 -0
  91. package/src/client/tabs/crown.js +59 -0
  92. package/src/client/tabs/dev.js +101 -0
  93. package/src/client/tabs/home.js +72 -0
  94. package/src/client/tabs/shop.js +78 -0
  95. package/src/client/tabs/status.js +223 -0
  96. package/src/client/tabs/study.js +128 -0
  97. package/src/client/tabs/travel.js +89 -0
  98. package/src/client/tabs/update.js +164 -0
  99. package/src/client/tabs/work.js +106 -0
  100. package/src/client/values.js +11 -0
  101. package/src/client/widgets.js +126 -0
  102. package/store/api.js +260 -0
  103. package/store/state-file.js +129 -0
  104. package/store.js +96 -0
@@ -0,0 +1,225 @@
1
+ // @ts-check
2
+ /**
3
+ * 基础样式:设计令牌、宿主容器、猪、图标栏与气泡。
4
+ *
5
+ * @module dsh-piggy/client/css-base
6
+ */
7
+
8
+
9
+ import { el } from './dom.js'
10
+
11
+ export const CSS_BASE = [
12
+ // ---------------------------------------------------------------------
13
+ // Animal Crossing design language, transcribed from
14
+ // guokaigdg/animal-island-ui docs/design-system (design-tokens.md and the
15
+ // standalone css-variables.md template).
16
+ //
17
+ // The tokens are declared on the widget root rather than :root: the host
18
+ // page must not inherit them, and they must not be clobbered by it.
19
+ //
20
+ // The rules that shape everything below:
21
+ // · warm earth-brown text on cream parchment, never pure black or grey
22
+ // · 12px minimum radius; buttons and inputs are 50px pills
23
+ // · the thick 3D bottom shadow belongs to primary buttons only
24
+ // · cards carry a border, not an elevation shadow
25
+ // · motion is 0.15-0.35s on cubic-bezier(.4,0,.2,1)
26
+ // · focus rings are yellow or teal, never blue
27
+ // ---------------------------------------------------------------------
28
+ '[data-dsh-pig]{',
29
+ '--ac-font:Nunito,"Noto Sans SC",-apple-system,"PingFang SC","Hiragino Sans GB",sans-serif;',
30
+ '--ac-primary:#19c8b9;--ac-primary-hover:#3dd4c6;--ac-primary-active:#11a89b;',
31
+ '--ac-primary-bg:#e6f9f6;',
32
+ '--ac-text:#794f27;--ac-text-body:#725d42;--ac-text-2:#9f927d;--ac-text-muted:#8a7b66;',
33
+ '--ac-text-disabled:#c4b89e;',
34
+ '--ac-bg:#f8f8f0;--ac-bg-content:rgb(247,243,223);--ac-bg-input:#fffbe7;',
35
+ '--ac-bg-disabled:#f0ece2;',
36
+ '--ac-border:#c4b89e;--ac-border-light:#e5dcc6;--ac-border-hover:#a89878;',
37
+ '--ac-radius-sm:12px;--ac-radius-card:20px;--ac-pill:50px;',
38
+ '--ac-shadow-sm:0 2px 4px 0 rgba(61,52,40,.06);',
39
+ '--ac-shadow:0 3px 10px 0 rgba(61,52,40,.1);',
40
+ '--ac-shadow-lg:0 8px 24px 0 rgba(61,52,40,.16);',
41
+ '--ac-inset:inset 0 2px 4px rgba(114,93,66,.15);',
42
+ // sidebar tokens: the library uses these for the selected menu row, which
43
+ // is exactly the role the icon bar plays here.
44
+ '--ac-active:#b7c6e5;--ac-hover:#d6dff0;',
45
+ '--ac-success:#6fba2c;--ac-warning:#f5c31c;--ac-error:#e05a5a;',
46
+ '--ac-ease:cubic-bezier(.4,0,.2,1);',
47
+ // One place to size the pig; the scene and the panel cap derive from it.
48
+ '--pig-size:56px;--pig-gap-below:12px;--scene-open:132px;--panel-width:292px;',
49
+ 'position:fixed;right:18px;bottom:18px;z-index:2147483000;',
50
+ 'font-family:var(--ac-font);font-weight:500;letter-spacing:.01em;',
51
+ '-webkit-user-select:none;user-select:none;touch-action:none;',
52
+ // The wrapper spans a column wider and taller than what it paints (the
53
+ // scene's padding, the gap above the panel). Without this it swallows
54
+ // clicks aimed at the page underneath — which once looked like "sending a
55
+ // message does nothing" while the whole stack was healthy.
56
+ 'pointer-events:none;',
57
+ // The pig is the only in-flow child, so the wrapper's box is exactly the
58
+ // pig's box and the panel can be parked anywhere around it without ever
59
+ // nudging the pig. `fitPanel` places the panel.
60
+ 'display:block}',
61
+ '[data-dsh-pig] *{box-sizing:border-box}',
62
+ '[data-dsh-pig]>*{pointer-events:auto}',
63
+ // `hidden` MUST win. The UA sheet's `[hidden]{display:none}` ties on
64
+ // specificity with a single class, so any `.dp-x{display:grid|flex}` rule
65
+ // below silently beats it and the element keeps rendering. That is exactly
66
+ // how a collapsed panel ended up showing the icon bar and the hud while
67
+ // every `el.hidden === true` assertion still passed.
68
+ '[data-dsh-pig] .dp-card[hidden],[data-dsh-pig] .dp-bar[hidden],',
69
+ '[data-dsh-pig] .dp-content[hidden],[data-dsh-pig] .dp-hud[hidden],',
70
+ '[data-dsh-pig] .dp-bubble[hidden],[data-dsh-pig] .dp-scene[hidden],',
71
+ '[data-dsh-pig] .dp-work[hidden],[data-dsh-pig] .dp-soul[hidden],',
72
+ '[data-dsh-pig] .dp-poke-hint[hidden],[data-dsh-pig] .dp-daily[hidden],',
73
+ '[data-dsh-pig] .dp-pig-img[hidden],[data-dsh-pig] .dp-pig-emoji[hidden]{display:none}',
74
+
75
+ /* ---------- the panel: cream parchment, border not shadow ---------- */
76
+ // Taken out of flow on purpose. In flow it would widen the wrapper, and a
77
+ // wider wrapper moves the pig — the exact thing this layout exists to
78
+ // prevent. Absolutely positioned, the wrapper's box stays the pig's box
79
+ // and `fitPanel` can put the panel on whichever side has room.
80
+ '.dp-card{position:absolute;right:0;bottom:calc(100% + 8px);width:var(--panel-width);',
81
+ 'border-radius:var(--ac-radius-card);overflow:hidden;',
82
+ 'display:flex;flex-direction:column;',
83
+ 'background:var(--ac-bg);border:2px solid var(--ac-border-light);',
84
+ // 面板自己钉住基准字号与字体:不钉就会继承宿主页面的 16px,
85
+ // 详情框那种「没写 font-size 的容器」就会比周围大一倍(用户反馈 #2)。
86
+ 'box-shadow:var(--ac-shadow-lg);color:var(--ac-text-body);font-family:var(--ac-font);font-size:11px}',
87
+
88
+ /* ---------- the pig: never moved, never boxed ---------- */
89
+ '.dp-scene{position:relative;height:var(--scene-open);background:none;cursor:grab;',
90
+ 'overflow:visible;display:flex;align-items:flex-end;justify-content:flex-end;',
91
+ 'padding:0 6px var(--pig-gap-below);width:max-content}',
92
+ '.dp-scene[data-dragging="true"]{cursor:grabbing}',
93
+ // Collapsed the scene is exactly the pig, so the wrapper paints nothing
94
+ // extra to click through. Open it widens to the panel so the hud and the
95
+ // speech bubble have somewhere to sit — the pig is right-aligned either
96
+ // way, so widening costs it no movement.
97
+ '[data-dsh-pig][data-open="true"] .dp-scene{width:var(--panel-width)}',
98
+ // Collapsed the scene shrinks to just the pig. An explicit height rather
99
+ // than `auto` keeps the pig's line box identical in both states, so
100
+ // opening moves it by exactly zero pixels.
101
+ '[data-dsh-pig][data-open="false"] .dp-scene{height:calc(var(--pig-size) + var(--pig-gap-below));',
102
+ 'cursor:pointer}',
103
+ '.dp-pig{line-height:1;transform-origin:50% 85%;cursor:pointer;position:relative;',
104
+ 'filter:drop-shadow(0 4px 6px rgba(61,52,40,.28));animation:dp-bob 1.8s ease-in-out infinite}',
105
+ // 装扮点位:猪身上固定的几个锚点,每个点位挂一件。
106
+ // 以后换真立绘时,只改这里的偏移/尺寸,逻辑和存档都不用动。
107
+ '.dp-dress{position:absolute;inset:0;pointer-events:none;z-index:3}',
108
+ '.dp-slot{position:absolute;line-height:1;font-size:15px;transform:translate(-50%,-50%)}',
109
+ '.dp-slot[data-slot="head"]{left:50%;top:2%}',
110
+ '.dp-slot[data-slot="face"]{left:50%;top:32%}',
111
+ '.dp-slot[data-slot="neck"]{left:50%;top:60%}',
112
+ '.dp-slot[data-slot="body"]{left:50%;top:78%;font-size:19px}',
113
+ '.dp-slot[data-slot="back"]{left:14%;top:42%;font-size:19px}',
114
+ '.dp-slot[data-slot="feet"]{left:50%;top:99%}',
115
+ '[data-dsh-pig][data-open="false"] .dp-pig{filter:drop-shadow(0 5px 9px rgba(61,52,40,.26))}',
116
+ // A petting hand rather than an arrow. Drawn inline as an SVG data URI so
117
+ // it needs no asset and can carry the palette's warm outline; the hotspot
118
+ // sits in the palm, which is where a pat actually lands. The `pointer`
119
+ // after it is the fallback for browsers that refuse a custom cursor.
120
+ '.dp-pig{cursor:url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" '
121
+ + 'width="30" height="30" viewBox="0 0 30 30"><g fill="%23F7C9B6" stroke="%23794F27" '
122
+ + 'stroke-width="1.7" stroke-linejoin="round"><rect x="10" y="13.5" width="14" height="12" '
123
+ + 'rx="4.8"/><rect x="10.6" y="6.6" width="3.6" height="10" rx="1.8"/><rect x="14.9" '
124
+ + 'y="5.1" width="3.6" height="11.5" rx="1.8"/><rect x="19.2" y="6.6" width="3.6" '
125
+ + 'height="10" rx="1.8"/><rect x="5.7" y="12.4" width="3.4" height="7.8" rx="1.7" '
126
+ + 'transform="rotate(-27 7.4 16.3)"/></g></svg>\') 16 24, pointer}',
127
+ // Transform-only keyframes: the pig is an ordinary flex item, so there is
128
+ // no translateX(-50%) centring to preserve.
129
+ '@keyframes dp-bob{0%,100%{transform:translateY(0) rotate(0deg)}50%{transform:translateY(-7px) rotate(-2.5deg)}}',
130
+ '@keyframes dp-breathe{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(1px) scale(1.09)}}',
131
+ '@keyframes dp-shake{0%,100%{transform:translateX(0) rotate(0)}20%{transform:translateX(-4px) rotate(-5deg)}60%{transform:translateX(4px) rotate(5deg)}}',
132
+ '@keyframes dp-spin{0%{transform:rotate(0)}50%{transform:rotate(180deg) scale(1.2)}100%{transform:rotate(360deg)}}',
133
+ '@keyframes dp-jump{0%{transform:translateY(0)}30%{transform:translateY(-26px) scale(1.12)}60%{transform:translateY(0) scale(.92)}100%{transform:translateY(0)}}',
134
+ '@keyframes dp-wobble{0%,100%{transform:rotate(0)}20%{transform:rotate(-14deg)}55%{transform:rotate(14deg)}}',
135
+ '@keyframes dp-cough{0%,100%{transform:translateX(0)}30%{transform:translateX(-4px) rotate(-7deg)}70%{transform:translateX(4px) rotate(6deg)}}',
136
+ '.dp-pig[data-mood="happy"]{animation-duration:1.15s}',
137
+ '.dp-pig[data-mood="sleepy"]{animation-name:dp-breathe;animation-duration:3.6s}',
138
+ '.dp-pig[data-mood="hungry"]{animation-name:dp-shake;animation-duration:2.4s}',
139
+ '.dp-pig[data-mood="dirty"]{animation-name:dp-breathe;animation-duration:2.6s;filter:sepia(.4) drop-shadow(0 4px 6px rgba(61,52,40,.28))}',
140
+ '.dp-pig[data-mood="sick"]{animation-name:dp-cough;animation-duration:2.2s;filter:hue-rotate(-28deg) saturate(.75) drop-shadow(0 4px 6px rgba(61,52,40,.28))}',
141
+ // One pose per activity, so being away reads as a thing the pig is doing.
142
+ '@keyframes dp-typing{0%,100%{transform:translateY(0) rotate(0)}25%{transform:translateY(-2px) rotate(-1.5deg)}50%{transform:translateY(0) rotate(0)}75%{transform:translateY(-2px) rotate(1.5deg)}}',
143
+ '@keyframes dp-reading{0%,100%{transform:translateY(0) rotate(0)}35%{transform:translateY(1px) rotate(-5deg)}70%{transform:translateY(1px) rotate(-2deg)}}',
144
+ '@keyframes dp-walking{0%,100%{transform:translateY(0) rotate(0)}25%{transform:translateY(-6px) rotate(-4deg)}50%{transform:translateY(0) rotate(0)}75%{transform:translateY(-6px) rotate(4deg)}}',
145
+ '.dp-pig[data-mood="working"]{animation-name:dp-typing;animation-duration:.7s}',
146
+ '.dp-pig[data-mood="studying"]{animation-name:dp-reading;animation-duration:2.4s}',
147
+ '.dp-pig[data-mood="traveling"]{animation-name:dp-walking;animation-duration:1s}',
148
+ '.dp-pig[data-mood="dead"]{animation:none;filter:grayscale(1)}',
149
+ '.dp-pig[data-react]{animation-duration:.85s;animation-iteration-count:1}',
150
+ '.dp-pig[data-react="feed"]{animation-name:dp-jump}',
151
+ '.dp-pig[data-react="bathe"]{animation-name:dp-wobble;animation-duration:1.05s}',
152
+ '.dp-pig[data-react="play"]{animation-name:dp-spin;animation-duration:.9s}',
153
+ '.dp-pig[data-react="away"]{animation-name:dp-jump;animation-duration:.9s}',
154
+ '.dp-pig[data-react="cure"]{animation-name:dp-spin;animation-duration:.9s}',
155
+ '.dp-pig[data-react="levelup"]{animation-name:dp-jump;animation-duration:.95s}',
156
+ '.dp-pig[data-react="refuse"]{animation-name:dp-shake;animation-duration:.5s}',
157
+
158
+ /* ---------- what the pig is off doing ---------- */
159
+ '[data-dsh-pig] .dp-work{display:flex;flex-direction:column;align-items:center;gap:4px;',
160
+ 'margin:0 2px 6px 0}',
161
+ '.dp-prop{font-size:26px;line-height:1;filter:drop-shadow(0 3px 5px rgba(61,52,40,.22));',
162
+ 'animation:dp-prop-bob 2.4s ease-in-out infinite}',
163
+ '[data-dsh-pig][data-away="study"] .dp-prop{animation-duration:3.4s}',
164
+ '[data-dsh-pig][data-away="trip"] .dp-prop{animation-name:dp-prop-swing;animation-duration:1.6s}',
165
+ '[data-dsh-pig][data-away="interest"] .dp-prop{animation-duration:3.4s}',
166
+ '@keyframes dp-prop-bob{0%,100%{transform:translateY(0) rotate(-3deg)}50%{transform:translateY(-3px) rotate(3deg)}}',
167
+ '@keyframes dp-prop-swing{0%,100%{transform:translateY(0) rotate(-8deg)}50%{transform:translateY(-4px) rotate(8deg)}}',
168
+ '.dp-progress{width:42px;height:7px;border-radius:var(--ac-pill);background:var(--ac-bg-disabled);',
169
+ 'box-shadow:var(--ac-inset);overflow:hidden}',
170
+ '.dp-progress i{display:block;height:100%;border-radius:var(--ac-pill);',
171
+ 'background:var(--ac-primary);transition:width .5s var(--ac-ease)}',
172
+ // The scene needs room for the prop; it grows leftward, so the pig stays put.
173
+ '[data-dsh-pig][data-away="work"] .dp-scene,[data-dsh-pig][data-away="study"] .dp-scene,',
174
+ '[data-dsh-pig][data-away="interest"] .dp-scene,',
175
+ '[data-dsh-pig][data-away="trip"] .dp-scene{width:max-content;min-width:132px}',
176
+ // 加冕后的形态有动作立绘(桌子、书、行李都画在图里):不再摆 emoji 道具,
177
+ // 动作也收小,免得把画里的东西甩来甩去(立绘与动作来自 PR #2)。
178
+ '[data-dsh-pig][data-art-actions="true"] .dp-prop{display:none}',
179
+ '.dp-pig[data-art-actions="true"][data-mood="working"]:not([data-react]){animation:dp-king-work 1.4s ease-in-out infinite}',
180
+ '.dp-pig[data-art-actions="true"][data-mood="studying"]:not([data-react]){animation:dp-king-study 2.4s ease-in-out infinite}',
181
+ '.dp-pig[data-art-actions="true"][data-mood="traveling"]:not([data-react]){animation:dp-king-walk .8s ease-in-out infinite}',
182
+ '@keyframes dp-king-work{0%,100%{transform:translateY(0)}50%{transform:translateY(1px) rotate(1deg)}}',
183
+ '@keyframes dp-king-study{0%,100%{transform:rotate(-2deg)}50%{transform:rotate(2deg)}}',
184
+ '@keyframes dp-king-walk{0%,100%{transform:translateY(0) rotate(-2deg)}50%{transform:translateY(-3px) rotate(2deg)}}',
185
+
186
+ /* ---------- hud: a cream tag beside the pig ---------- */
187
+ '.dp-hud{position:absolute;left:9px;top:7px;display:flex;flex-direction:column;gap:1px;',
188
+ 'font-size:10.5px;font-weight:600;line-height:1.45;color:var(--ac-text);',
189
+ 'background:var(--ac-bg);border:2px solid var(--ac-border-light);padding:5px 10px;',
190
+ 'border-radius:var(--ac-radius-sm);box-shadow:var(--ac-shadow-sm)}',
191
+ '.dp-hud b{font-weight:700}',
192
+
193
+ // A drawn sprite is sized by the same variable as the emoji, so growing up
194
+ // works identically either way.
195
+ '.dp-pig-img{width:var(--pig-size);height:var(--pig-size);display:block;',
196
+ '-webkit-user-drag:none;user-select:none}',
197
+ '.dp-pig-emoji{font-size:var(--pig-size);line-height:1}',
198
+
199
+ // No drawings yet — every stage is the same pig, so age reads as size plus
200
+ // a faded coat on the last one.
201
+ '[data-dsh-pig][data-faded="true"] .dp-pig-emoji{filter:grayscale(.5) opacity(.72)}',
202
+
203
+ // The box advertises itself: a slow breathing glow plus a label, so it
204
+ // does not read as scenery.
205
+ '[data-dsh-pig][data-unhatched="true"] .dp-pig{cursor:pointer;',
206
+ 'animation:dp-box-breathe 2.4s ease-in-out infinite}',
207
+ '[data-dsh-pig][data-unhatched="true"] .dp-pig-emoji{',
208
+ 'filter:drop-shadow(0 0 0 rgba(255,214,102,0)) drop-shadow(0 4px 6px rgba(61,52,40,.28))}',
209
+ '@keyframes dp-box-breathe{0%,100%{transform:translateY(0) scale(1)}',
210
+ '50%{transform:translateY(-3px) scale(1.06)}}',
211
+ '.dp-poke-hint{position:absolute;right:2px;bottom:-2px;display:flex;align-items:center;gap:3px;',
212
+ 'font-size:9.5px;font-weight:700;color:var(--ac-text);background:var(--ac-bg);',
213
+ 'border:1.5px solid var(--ac-border-light);border-radius:var(--ac-pill);padding:1px 7px;',
214
+ 'box-shadow:0 2px 0 rgba(61,52,40,.12);pointer-events:none;white-space:nowrap;z-index:3;',
215
+ 'animation:dp-hint-bob 1.6s ease-in-out infinite}',
216
+ '@keyframes dp-hint-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}',
217
+ // Each poke shakes it harder; the third one opens it instead.
218
+ '[data-dsh-pig] .dp-pig[data-mood="poke"],',
219
+ '[data-dsh-pig][data-poke] .dp-pig{animation-name:dp-poke-shake}',
220
+ '[data-dsh-pig][data-poke="2"] .dp-pig{animation-duration:.28s}',
221
+ '@keyframes dp-poke-shake{0%,100%{transform:rotate(0)}25%{transform:rotate(-7deg)}',
222
+ '50%{transform:rotate(6deg)}75%{transform:rotate(-4deg)}}',
223
+
224
+ // The shop's tiles live in css-tiles.js since B8.
225
+ ].join('')
@@ -0,0 +1,63 @@
1
+ // @ts-check
2
+ /**
3
+ * 居民卡的样式(B9)—— 照 animal-island-ui 的 Card `pattern-*`:淡色底上两层点点
4
+ * (28px / 14px 网格),1.5px 同色描边,圆角 20px;「标签:值」的值放在米白胶囊里。
5
+ * 女孩用 pattern-app-pink,男孩用 pattern-app-blue(参数取自它的 card.module.less)。
6
+ * @module dsh-piggy/client/css-card
7
+ */
8
+
9
+ export const CSS_CARD = [
10
+ '.dp-vcard{position:relative;padding:14px 14px 12px;border-radius:20px;color:var(--ac-text-body);',
11
+ '--vc-dot:rgba(196,184,158,.15);--vc-dot2:rgba(196,184,158,.1);--vc-bg:rgb(247,243,223);--vc-line:#d4c4a8;',
12
+ 'background:radial-gradient(circle,var(--vc-dot) 1.5px,transparent 1.5px),',
13
+ 'radial-gradient(circle,var(--vc-dot2) 1px,transparent 1px),var(--vc-bg);',
14
+ 'background-size:28px 28px,14px 14px;background-position:0 0,7px 7px;border:1.5px solid var(--vc-line)}',
15
+ '.dp-vcard[data-sex="girl"]{--vc-dot:rgba(248,166,178,.18);--vc-dot2:rgba(255,200,210,.12);--vc-bg:#fde4e8;--vc-line:#f8a6b2}',
16
+ '.dp-vcard[data-sex="boy"]{--vc-dot:rgba(136,157,240,.18);--vc-dot2:rgba(180,195,255,.12);--vc-bg:#e8edff;--vc-line:#889df0}',
17
+
18
+ // Top: the photo and who it is.
19
+ '.dp-vcard-top{display:flex;align-items:center;gap:12px;margin-bottom:12px}',
20
+ '.dp-vcard-avatar{flex:none;width:72px;height:72px;border-radius:18px;display:flex;align-items:center;',
21
+ 'justify-content:center;background:#fffbe7;border:2px solid var(--vc-line);box-shadow:0 3px 0 rgba(61,52,40,.12)}',
22
+ '.dp-vcard-img{width:56px;height:56px;display:block}',
23
+ '.dp-vcard-e{font-size:40px;line-height:1}',
24
+ '.dp-vcard-who{display:flex;flex-direction:column;gap:3px;min-width:0}',
25
+ '.dp-vcard-name{font-size:15px;font-weight:800;color:var(--ac-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
26
+ '.dp-vcard-sub{font-size:10.5px;font-weight:600;color:var(--ac-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
27
+
28
+ // 「标签:值」 rows.
29
+ '.dp-vcard-row{display:flex;align-items:center;gap:6px;margin-top:7px;min-width:0}',
30
+ '.dp-vcard-label{flex:none;width:44px;font-size:10.5px;font-weight:700;color:var(--ac-text)}',
31
+ '.dp-vcard-value{flex:1;min-width:0;padding:5px 11px;border-radius:var(--ac-pill);background:#faf8f2;',
32
+ 'font-size:11px;font-weight:600;color:var(--ac-text-body);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
33
+ // The motto is the pig talking: a bubble that may take two lines.
34
+ '.dp-vcard-motto-row{align-items:flex-start}',
35
+ '.dp-vcard-motto-row .dp-vcard-label{margin-top:6px}',
36
+ '.dp-vcard-value.dp-vcard-motto{border-radius:12px;white-space:normal;line-height:1.45}',
37
+ '.dp-vcard-edit{flex:none;font:inherit;font-size:12px;line-height:1;width:24px;height:24px;padding:0;cursor:pointer;',
38
+ 'border-radius:50%;border:2px solid var(--vc-line);background:#fffbe7}',
39
+ '.dp-vcard-edit:hover{background:var(--ac-hover)}',
40
+ '.dp-vcard-input{flex:1;min-width:0;padding:3px 9px;font-size:11px}',
41
+ // In-place editing: the two buttons stay as small as the pencil they replace.
42
+ '.dp-vcard-row .dp-mini{flex:none;padding:3px 9px;font-size:10px;box-shadow:none}',
43
+ '[data-dsh-pig] .dp-vcard-row .dp-mini.dp-mini-plain{background:#fffbe7;color:var(--ac-text);border:2px solid var(--vc-line);box-shadow:none}',
44
+
45
+ '.dp-vcard-foot{margin-top:12px;padding-top:9px;border-top:1.5px dashed var(--vc-line);',
46
+ 'font-size:10px;font-weight:600;color:var(--ac-text-2);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
47
+
48
+ // 加冕 App: one cream box per form, its picture on the left, conditions as small chips.
49
+ '.dp-crown{margin-bottom:10px;padding:10px 12px;border-radius:16px;background:#fffbe7;border:2px dashed #e8c66a}',
50
+ '.dp-crown.dp-crown-now{border-style:solid;background:#fdf3d0}',
51
+ '.dp-crown-top{display:flex;gap:10px;align-items:flex-start}',
52
+ '.dp-crown-pic{flex:none;width:58px;height:58px;border-radius:14px;display:flex;align-items:center;justify-content:center;',
53
+ 'background:#fff;border:2px solid #f0dca0;font-size:30px}',
54
+ '.dp-crown-img{width:50px;height:50px;display:block}',
55
+ '.dp-crown-side{flex:1;min-width:0}',
56
+ '.dp-crown-head{font-size:12px;font-weight:800;color:var(--ac-text);margin-bottom:6px}',
57
+ '.dp-crown-done{font-size:11px;font-weight:700;color:#3f8a62}',
58
+ '.dp-crown-reqs{display:flex;flex-wrap:wrap;gap:4px}',
59
+ '.dp-crown-req{padding:2px 7px;border-radius:var(--ac-pill);font-size:10px;font-weight:700;white-space:nowrap;',
60
+ 'background:#f3ece0;color:var(--ac-text-2)}',
61
+ '.dp-crown-req.dp-crown-ok{background:#dff3e8;color:#3f8a62}',
62
+ '.dp-crown .dp-btn{width:100%;margin-top:9px}',
63
+ ].join('')
@@ -0,0 +1,250 @@
1
+ // @ts-check
2
+ /**
3
+ * 面板内部样式:页签、列表、格子、装扮点位与调试页。
4
+ *
5
+ * @module dsh-piggy/client/css-tabs
6
+ */
7
+
8
+
9
+ import { button, meter } from './dom.js'
10
+
11
+ export const CSS_TABS = [
12
+ /* ---------- developer tab ---------- */
13
+ '.dp-dev-note{font-size:10px;color:var(--ac-text-2);margin:4px 0 2px;line-height:1.5}',
14
+ '.dp-dev-row{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 2px}',
15
+ '.dp-dev-btn{flex:0 0 auto;font-size:10px;padding:3px 8px}',
16
+ '.dp-on{background:var(--ac-primary);color:#fff;border-color:var(--ac-primary)}',
17
+ '[data-dsh-pig][data-dev="true"] .dp-ico[data-tab="dev"]{color:var(--ac-primary)}',
18
+
19
+ /* ---------- the soul that settles on an unclaimed grave ---------- */
20
+ '.dp-soul{position:absolute;left:50%;transform:translateX(-50%);top:-4px;font-size:22px;',
21
+ 'line-height:1;opacity:.9;pointer-events:none;z-index:1;',
22
+ 'animation:dp-haunt 3.4s ease-in-out infinite}',
23
+ '@keyframes dp-haunt{0%,100%{transform:translate(-50%,0) scale(1);opacity:.75}',
24
+ '50%{transform:translate(-50%,-9px) scale(1.08);opacity:1}}',
25
+ // A grave does not bob about like a living pig.
26
+ '.dp-pig[data-stage="grave"]{animation:none;filter:grayscale(.35) drop-shadow(0 4px 6px rgba(61,52,40,.3))}',
27
+ '.dp-pig[data-stage="box"]{animation:dp-box-wobble 3.2s ease-in-out infinite}',
28
+ '@keyframes dp-box-wobble{0%,100%{transform:rotate(0)}30%{transform:rotate(-4deg)}',
29
+ '45%{transform:rotate(3deg)}60%{transform:rotate(-2deg)}}',
30
+
31
+ // Patting squashes the pig flat. Short, so rapid clicking keeps up.
32
+ '[data-dsh-pig] .dp-pig[data-react="pet"]{animation-name:dp-squash;animation-duration:.42s}',
33
+ '@keyframes dp-squash{0%{transform:scale(1,1)}35%{transform:scale(1.16,.74) translateY(2px)}',
34
+ '60%{transform:scale(.94,1.08) translateY(-3px)}100%{transform:scale(1,1)}}',
35
+
36
+ /* ---------- speech bubble ---------- */
37
+ // `z-index` matters: the pig comes later in the DOM, so without it the pig
38
+ // paints over the bubble whenever the two boxes overlap — which is exactly
39
+ // what happened when collapsed and the scene was only as wide as the pig.
40
+ '.dp-bubble{position:absolute;right:8px;top:7px;z-index:2;max-width:162px;padding:6px 10px;',
41
+ 'border-radius:var(--ac-radius-sm);font-size:10.5px;font-weight:600;line-height:1.45;',
42
+ 'color:var(--ac-text-body);background:var(--ac-bg-input);',
43
+ 'border:2px solid var(--ac-border-light);box-shadow:var(--ac-shadow-sm)}',
44
+ // Tail drawn as a small rotated square so the 2px border stays continuous.
45
+ '.dp-bubble::after{content:"";position:absolute;left:14px;bottom:-6px;width:8px;height:8px;',
46
+ 'background:var(--ac-bg-input);border-right:2px solid var(--ac-border-light);',
47
+ 'border-bottom:2px solid var(--ac-border-light);transform:rotate(45deg)}',
48
+ // Reply buttons under a line: small pills, the mint of the primary colour
49
+ // without the 3D base, which the spec keeps for real primary buttons.
50
+ '.dp-bubble-replies{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}',
51
+ // 猪头上的日常气泡(签到 / 礼包):不用新颜色,沿用主色与卡片底色。
52
+ // 挂在场景**上方**(不是 top 边缘):折叠时场景就是猪本身,用 top:-6px
53
+ // 会让气泡叠在猪头上(用户反馈 #6)。
54
+ '.dp-daily{position:absolute;bottom:calc(100% + 7px);left:50%;width:36px;margin-left:-18px;',
55
+ 'font:inherit;font-size:15px;line-height:1;padding:3px 0;cursor:pointer;text-align:center;',
56
+ 'border:2px solid var(--ac-border);border-radius:50px;background:var(--ac-bg-input);',
57
+ 'box-shadow:0 3px 0 rgba(61,52,40,.14);animation:dp-daily-bob 2.4s var(--ac-ease) infinite}',
58
+ // 折叠时场景就剩猪本身(而且它还在上下浮动 ±7px),再多让开一点。
59
+ '[data-dsh-pig][data-open="false"] .dp-daily{bottom:calc(100% + 16px)}',
60
+ // 展开时场景有面板那么宽、那么高,挂在场景上方会压到图标栏(B8 截图里压在「商店」上):
61
+ // 改成蹲在猪左边、贴着猪身子(再高会碰到左边的名字框)。
62
+ '[data-dsh-pig][data-open="true"] .dp-daily{left:auto;margin-left:0;',
63
+ 'right:calc(6px + var(--pig-size) + 10px);bottom:calc(var(--pig-gap-below) + 4px)}',
64
+ '.dp-daily:hover{border-color:var(--ac-border-hover)}',
65
+ '.dp-daily:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}',
66
+ // 名字必须独占:叫 dp-bob 会覆盖猪的待机动画(css-base.js),
67
+ // 而那个动画的 transform 一被替掉,猪就会横跳半个身位。
68
+ // 只上下浮:横向居中改用 margin,展开时才能挪到猪旁边。
69
+ '@keyframes dp-daily-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}',
70
+ // 日记:折叠时只有首句,展开是全文。
71
+ '.dp-diary{cursor:pointer}',
72
+ '.dp-diary[data-open="true"] .dp-diary-full{display:block}',
73
+ '.dp-diary-full{margin-top:4px;line-height:1.5}',
74
+ '.dp-reply{font:inherit;font-size:10px;font-weight:700;padding:2px 9px;cursor:pointer;',
75
+ 'border-radius:var(--ac-pill);border:2px solid var(--ac-border-light);background:var(--ac-bg);',
76
+ 'color:var(--ac-text);transition:border-color .15s var(--ac-ease)}',
77
+ '.dp-reply:hover{border-color:var(--ac-border-hover)}',
78
+ '.dp-reply:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}',
79
+ // Collapsed, the scene is exactly the pig, so a bubble drawn inside it
80
+ // would sit on the pig's face. Float it above the head with the tail
81
+ // pointing down, anchored to the right edge so it can never run off the
82
+ // window. The hearts rise from behind it.
83
+ '[data-dsh-pig][data-open="false"] .dp-bubble{top:auto;bottom:calc(100% + 8px);',
84
+ 'left:auto;right:0;max-width:230px}',
85
+ '[data-dsh-pig][data-open="false"] .dp-bubble::after{left:auto;right:26px;',
86
+ 'top:100%;bottom:auto;margin:0;transform:rotate(45deg);',
87
+ 'border:0;border-right:2px solid var(--ac-border-light);',
88
+ 'border-bottom:2px solid var(--ac-border-light)}',
89
+
90
+ /* ---------- icon bar: the library sidebar, laid on its side ---------- */
91
+ '.dp-bar{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;padding:8px;',
92
+ 'background:var(--ac-bg-content);border-top:2px solid var(--ac-border-light);',
93
+ 'border-bottom:2px solid var(--ac-border-light)}',
94
+ '.dp-ico{display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;',
95
+ 'font:inherit;font-size:9.5px;font-weight:600;color:var(--ac-text-muted);background:none;',
96
+ 'border:2px solid transparent;border-radius:var(--ac-radius-sm);padding:5px 1px;',
97
+ 'transition:all .2s var(--ac-ease)}',
98
+ '.dp-ico span.dp-ico-e{font-size:18px;line-height:1}',
99
+ '.dp-ico:hover{background:var(--ac-hover)}',
100
+ '.dp-ico[data-active="true"]{background:var(--ac-active);border-color:#9db0d6;',
101
+ 'color:var(--ac-text);font-weight:700}',
102
+ '.dp-ico:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}',
103
+ '@keyframes dp-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.18)}}',
104
+ '.dp-ico[data-alert="true"] span.dp-ico-e{animation:dp-pulse 1.4s ease-in-out infinite}',
105
+
106
+ /* ---------- content ---------- */
107
+ '.dp-content{padding:12px 13px 13px;overflow-y:auto;flex:1 1 auto;min-height:0}',
108
+ '.dp-content::-webkit-scrollbar{width:8px}',
109
+ '.dp-content::-webkit-scrollbar-thumb{background:var(--ac-border-light);border-radius:4px}',
110
+ '.dp-content::-webkit-scrollbar-track{background:transparent}',
111
+ '.dp-title{display:flex;justify-content:space-between;align-items:baseline;font-size:11px;',
112
+ 'margin-bottom:8px}',
113
+ '.dp-title b{font-weight:700;color:var(--ac-text)}',
114
+ '.dp-title span{color:var(--ac-text-2);font-size:10.5px;font-weight:600}',
115
+ '.dp-row{display:flex;justify-content:space-between;font-size:11px;font-weight:600;',
116
+ 'color:var(--ac-text-body);margin:2px 0}',
117
+ '.dp-row b{font-weight:700;color:var(--ac-text)}',
118
+
119
+ /* ---------- attribute bars: pill track with an inset well ---------- */
120
+ '.dp-meter{height:9px;border-radius:var(--ac-pill);background:var(--ac-bg-disabled);',
121
+ 'box-shadow:var(--ac-inset);overflow:hidden;margin:3px 0 8px}',
122
+ '.dp-meter i{display:block;height:100%;border-radius:var(--ac-pill);',
123
+ 'background:var(--ac-warning);transition:width .35s var(--ac-ease)}',
124
+ '.dp-meter.dp-mood i{background:#f8a6b2}',
125
+ '.dp-meter.dp-clean i{background:#82d5bb}',
126
+ '.dp-meter.dp-health i{background:#8ac68a}',
127
+ '.dp-traits{display:flex;gap:10px;font-size:10.5px;font-weight:600;color:var(--ac-text-2);',
128
+ 'margin:8px 0 3px}',
129
+
130
+ /* ---------- banners ---------- */
131
+ '.dp-alert{margin:0 0 9px;padding:8px 10px;border-radius:var(--ac-radius-sm);',
132
+ 'font-size:10.5px;font-weight:600;line-height:1.55;border:2px solid}',
133
+ '.dp-alert b{font-weight:700;color:var(--ac-text)}',
134
+ '.dp-alert.dp-sick{background:#fdeeee;border-color:#f2c2c2}',
135
+ '.dp-alert.dp-work{background:#eef1fb;border-color:#c3cdf0}',
136
+ '.dp-alert.dp-dead{background:var(--ac-bg-disabled);border-color:var(--ac-border-light)}',
137
+ '.dp-alert.dp-legacy{background:#fdf7e2;border-color:#f0dfa8}',
138
+
139
+ /* ---------- buttons: secondary is a cream pill with soft elevation ---- */
140
+ '.dp-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}',
141
+ '.dp-btn{display:flex;align-items:center;justify-content:center;gap:5px;font:inherit;',
142
+ 'font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--ac-text-body);',
143
+ 'cursor:pointer;padding:8px 6px;border-radius:var(--ac-pill);',
144
+ 'border:2px solid var(--ac-border);background:var(--ac-bg-input);',
145
+ 'box-shadow:var(--ac-shadow-sm);transition:all .2s var(--ac-ease)}',
146
+ '.dp-btn:hover:not(:disabled){transform:translateY(-1px);box-shadow:var(--ac-shadow);',
147
+ 'border-color:var(--ac-border-hover)}',
148
+ '.dp-btn:active:not(:disabled){transform:translateY(2px);box-shadow:var(--ac-shadow-sm)}',
149
+ '.dp-btn:focus-visible{outline:2px solid var(--ac-primary);outline-offset:1px}',
150
+ '.dp-btn:disabled{background:var(--ac-bg-disabled);color:var(--ac-text-disabled);',
151
+ 'border-color:var(--ac-border-light);box-shadow:none;cursor:not-allowed}',
152
+ '.dp-btn-wide{grid-column:1/-1}',
153
+ '.dp-btn .dp-wait{color:var(--ac-text-2);font-size:10px;font-weight:600}',
154
+
155
+ /* ---------- segmented control ---------- */
156
+ '.dp-seg{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:9px}',
157
+ '.dp-seg button{font:inherit;font-size:10.5px;font-weight:600;color:var(--ac-text-muted);',
158
+ 'cursor:pointer;padding:6px 2px;border-radius:var(--ac-pill);',
159
+ 'border:2px solid var(--ac-border-light);background:var(--ac-bg-input);',
160
+ // One line, always: a label that wraps makes its button taller than the rest.
161
+ 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;',
162
+ 'transition:all .2s var(--ac-ease)}',
163
+ '.dp-seg button:hover{background:var(--ac-hover)}',
164
+ // The work tab has three skills, not four stages.
165
+ '.dp-seg.dp-seg-3{grid-template-columns:repeat(3,minmax(0,1fr))}',
166
+ // Work rows: two small buttons on the right, 详情 opens the checklist below.
167
+ '.dp-job-locked{opacity:.75}',
168
+ '.dp-job-detail{margin-top:-2px}',
169
+ '.dp-req{font-size:10.5px;font-weight:600;color:var(--ac-error);line-height:1.6}',
170
+ '.dp-req.dp-req-ok{color:var(--ac-success)}',
171
+ '.dp-seg button[data-active="true"]{background:var(--ac-active);border-color:#9db0d6;',
172
+ 'color:var(--ac-text);font-weight:700}',
173
+
174
+ /* ---------- list rows ---------- */
175
+ // minmax(0,1fr): a long nowrap line must ellipsize, not widen the panel.
176
+ '.dp-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}',
177
+ '.dp-list{display:flex;flex-direction:column;gap:7px}',
178
+ '.dp-shelf{margin:9px 0 1px;font-size:10px;font-weight:700;color:var(--ac-text-2);',
179
+ 'letter-spacing:.04em}',
180
+ '.dp-shelf:first-child{margin-top:0}',
181
+ '.dp-item{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:600;',
182
+ 'color:var(--ac-text-body);padding:7px 9px;border-radius:var(--ac-radius-sm);',
183
+ 'background:var(--ac-bg-content);border:2px solid var(--ac-border-light)}',
184
+ '.dp-item .dp-grow{flex:1;min-width:0}',
185
+ '.dp-item .dp-dim{color:var(--ac-text-2);font-size:10px;font-weight:500;overflow:hidden;',
186
+ 'text-overflow:ellipsis;white-space:nowrap}',
187
+ '.dp-item.dp-wanted{background:#fdf7e2;border-color:var(--ac-warning)}',
188
+ // B6 talk row: name + 改 + 免打扰, and the inline name input.
189
+ '.dp-talk{gap:6px;margin-top:8px}',
190
+ '.dp-talk>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
191
+ '.dp-mini-plain{background:var(--ac-bg-input);color:var(--ac-text);border:2px solid var(--ac-border-light);box-shadow:none}',
192
+ '.dp-mini-plain:hover:not(:disabled){background:var(--ac-hover)}',
193
+ '.dp-input{flex:1;min-width:0;font:inherit;font-size:11px;padding:3px 8px;border-radius:var(--ac-pill);',
194
+ 'border:2px solid var(--ac-border);background:var(--ac-bg-input);color:var(--ac-text)}',
195
+ '.dp-input:focus{outline:2px solid var(--ac-primary);outline-offset:1px}',
196
+
197
+ /* ---------- primary buttons: teal pill with the game 3D bottom edge --- */
198
+ '.dp-mini{font:inherit;font-size:10.5px;font-weight:700;letter-spacing:.02em;color:#fff;',
199
+ 'cursor:pointer;padding:6px 13px;border-radius:var(--ac-pill);',
200
+ 'border:2px solid var(--ac-primary-active);background:var(--ac-primary);',
201
+ 'box-shadow:0 3px 0 0 var(--ac-primary-active);transition:all .15s var(--ac-ease)}',
202
+ '.dp-mini:hover:not(:disabled){background:var(--ac-primary-hover);transform:translateY(-1px);',
203
+ 'box-shadow:0 4px 0 0 var(--ac-primary-active)}',
204
+ '.dp-mini:active:not(:disabled){transform:translateY(2px);',
205
+ 'box-shadow:0 1px 0 0 var(--ac-primary-active)}',
206
+ '.dp-mini:focus-visible{outline:2px solid var(--ac-primary);outline-offset:2px}',
207
+ '.dp-mini:disabled{background:var(--ac-bg-disabled);color:var(--ac-text-disabled);',
208
+ 'border-color:var(--ac-border-light);box-shadow:none;cursor:not-allowed}',
209
+
210
+ /* ---------- the care item picker ---------- */
211
+ '.dp-pick{margin-top:9px;padding:9px 10px;border-radius:var(--ac-radius-sm);font-size:10.5px;',
212
+ 'background:var(--ac-bg-content);border:2px solid var(--ac-border-light)}',
213
+ '.dp-pick-head{font-size:10.5px;font-weight:700;color:var(--ac-text);margin-bottom:7px}',
214
+ '.dp-cancel{display:block;width:100%;margin-top:8px;font:inherit;font-size:10.5px;',
215
+ 'font-weight:600;color:var(--ac-text-2);cursor:pointer;padding:5px;',
216
+ 'border-radius:var(--ac-pill);border:2px solid var(--ac-border-light);',
217
+ 'background:var(--ac-bg-input);transition:all .2s var(--ac-ease)}',
218
+ '.dp-cancel:hover{background:var(--ac-hover);color:var(--ac-text)}',
219
+ '.dp-count{margin-left:2px;font-size:9px;font-weight:700;color:var(--ac-text-2);',
220
+ 'background:var(--ac-bg-content);border-radius:var(--ac-pill);padding:0 5px}',
221
+ '.dp-btn[data-open-picker="true"]{background:var(--ac-active);border-color:#9db0d6}',
222
+ '.dp-seg button[data-locked="true"]{color:var(--ac-text-disabled);',
223
+ 'border-style:dashed;background:var(--ac-bg-disabled)}',
224
+ '.dp-seg button[data-locked="true"]:hover{background:var(--ac-bg-disabled)}',
225
+ '.dp-locked{margin:0 0 8px;font-size:10.5px;font-weight:600;line-height:1.5;',
226
+ 'color:var(--ac-text-body);background:#fdf7e2;border:2px solid #f0dfa8;',
227
+ 'border-radius:var(--ac-radius-sm);padding:6px 9px}',
228
+ // The per-job gate reads as a lock, not as another grey stat line: a
229
+ // threshold the pig cannot see is indistinguishable from a broken button.
230
+ '.dp-lock{font-size:10px;font-weight:700;line-height:1.5;color:#9a6b1f}',
231
+
232
+ '.dp-empty{color:var(--ac-text-2);font-size:10.5px;font-weight:500;line-height:1.65;',
233
+ 'margin-top:4px}',
234
+ '.dp-memo{margin-top:9px;padding-top:8px;border-top:2px solid var(--ac-border-light);',
235
+ 'color:var(--ac-text-muted);font-size:10px;font-weight:500;line-height:1.55;',
236
+ 'white-space:pre-wrap;word-break:break-word}',
237
+
238
+ /* ---------- particles and toast ---------- */
239
+ '.dp-fx{position:absolute;z-index:1;pointer-events:none;font-size:17px;',
240
+ 'animation:dp-rise 1.1s ease-out forwards}',
241
+ '@keyframes dp-rise{0%{opacity:0;transform:translate(var(--dx0,0),4px) scale(.5)}18%{opacity:1}',
242
+ '100%{opacity:0;transform:translate(var(--dx,0),-56px) scale(1.15)}}',
243
+ '.dp-toast{position:absolute;left:9px;right:9px;top:8px;padding:8px 11px;',
244
+ 'border-radius:var(--ac-radius-sm);font-size:10.5px;font-weight:600;line-height:1.5;',
245
+ 'color:var(--ac-text);background:var(--ac-bg-input);border:2px solid var(--ac-border);',
246
+ 'box-shadow:var(--ac-shadow);pointer-events:none;white-space:normal;',
247
+ 'animation:dp-toast 4.6s var(--ac-ease) forwards}',
248
+ '@keyframes dp-toast{0%{opacity:0;transform:translateY(-8px)}8%{opacity:1;transform:translateY(0)}',
249
+ '82%{opacity:1}100%{opacity:0;transform:translateY(-6px)}}',
250
+ ].join('')
@@ -0,0 +1,89 @@
1
+ // @ts-check
2
+ /**
3
+ * 方块页(学习 / 商店 / 背包)的样式 —— B8,照动森手机主屏:
4
+ * 3 列彩色圆角大方块、大图标、方块下面一行小字;点进去整屏换成同色浅一档的小方块。
5
+ *
6
+ * 配色取 animal-island-ui 的 app 方块色(docs/design-system/design-tokens.md),
7
+ * 声明在组件根节点上,不碰宿主页面的 :root。
8
+ * @module dsh-piggy/client/css-tiles
9
+ */
10
+
11
+ export const CSS_TILES = [
12
+ '[data-dsh-pig]{--tile-pink:#f8a6b2;--tile-purple:#b77dee;--tile-blue:#889df0;',
13
+ '--tile-yellow:#f7cd67;--tile-orange:#e59266;--tile-teal:#82d5bb;--tile-green:#8ac68a;',
14
+ '--tile-red:#fc736d;--tile-lime:#d1da49;--tile-peach:#e18c6f;--tile-brown:#9a835a}',
15
+ '.dp-tile[data-color="pink"]{--tile-c:var(--tile-pink)}',
16
+ '.dp-tile[data-color="purple"]{--tile-c:var(--tile-purple)}',
17
+ '.dp-tile[data-color="blue"]{--tile-c:var(--tile-blue)}',
18
+ '.dp-tile[data-color="yellow"]{--tile-c:var(--tile-yellow)}',
19
+ '.dp-tile[data-color="orange"]{--tile-c:var(--tile-orange)}',
20
+ '.dp-tile[data-color="teal"]{--tile-c:var(--tile-teal)}',
21
+ '.dp-tile[data-color="green"]{--tile-c:var(--tile-green)}',
22
+ '.dp-tile[data-color="red"]{--tile-c:var(--tile-red)}',
23
+ '.dp-tile[data-color="lime"]{--tile-c:var(--tile-lime)}',
24
+ '.dp-tile[data-color="peach"]{--tile-c:var(--tile-peach)}',
25
+ '.dp-tile[data-color="brown"]{--tile-c:var(--tile-brown)}',
26
+
27
+ // The grid: three columns that can never be widened by their content.
28
+ '.dp-tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px 8px;padding:4px 2px 2px}',
29
+
30
+ // A tile is a column: the coloured square, then its name, then a note.
31
+ '.dp-tile{font:inherit;display:flex;flex-direction:column;align-items:center;gap:4px;min-width:0;',
32
+ 'padding:0;margin:0;border:0;background:none;cursor:pointer;color:var(--ac-text)}',
33
+ '.dp-tile-icon{position:relative;display:flex;align-items:center;justify-content:center;',
34
+ 'width:50px;height:50px;border-radius:15px;background:var(--tile-c,var(--ac-bg-content));',
35
+ 'box-shadow:0 3px 0 rgba(61,52,40,.16);transition:transform .15s var(--ac-ease),box-shadow .15s var(--ac-ease)}',
36
+ '.dp-tile-e{font-size:24px;line-height:1;filter:drop-shadow(0 1px 1px rgba(61,52,40,.18))}',
37
+ '.dp-tile:hover:not(:disabled) .dp-tile-icon{transform:translateY(-2px);box-shadow:0 5px 0 rgba(61,52,40,.16)}',
38
+ '.dp-tile:active:not(:disabled) .dp-tile-icon{transform:translateY(2px);box-shadow:0 1px 0 rgba(61,52,40,.16)}',
39
+ '.dp-tile:focus-visible{outline:none}',
40
+ '.dp-tile:focus-visible .dp-tile-icon{outline:2px solid var(--ac-primary);outline-offset:2px}',
41
+ // One line each, never wrapping: every tile in a row stays the same height.
42
+ '.dp-tile-n,.dp-tile-note{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.25}',
43
+ '.dp-tile-n{font-size:10.5px;font-weight:700}',
44
+ '.dp-tile-note{font-size:9.5px;font-weight:600;color:var(--ac-text-2);margin-top:-2px}',
45
+ // Corner marks on the square: a count top-right, a word top-left.
46
+ '.dp-tile-badge,.dp-tile-tag{position:absolute;top:-5px;font-size:9px;font-weight:800;line-height:1;',
47
+ 'padding:3px 5px;border-radius:var(--ac-pill);white-space:nowrap;border:2px solid var(--ac-bg)}',
48
+ '.dp-tile-badge{right:-6px;background:var(--ac-primary);color:#fff}',
49
+ '.dp-tile-tag{left:-6px;background:var(--ac-warning);color:var(--ac-text)}',
50
+ // Second layer: the same colour, a shade paler and a little smaller.
51
+ // Sizes trimmed on 2026-10-01 (owner: the tiles were too big): 50px / 44px.
52
+ '.dp-tile-soft .dp-tile-icon{width:44px;height:44px;border-radius:13px;',
53
+ 'background:color-mix(in srgb,var(--tile-c) 42%,#fffbe7)}',
54
+ '.dp-tile-soft .dp-tile-e{font-size:21px}',
55
+ // Locked: greyed but still openable (a stage can be looked into before it opens).
56
+ '.dp-tile[data-locked="true"] .dp-tile-icon{filter:grayscale(.75);opacity:.6}',
57
+ '.dp-tile[data-dim="true"] .dp-tile-icon,.dp-tile:disabled .dp-tile-icon{opacity:.45;box-shadow:none}',
58
+ '.dp-tile[data-dim="true"] .dp-tile-n,.dp-tile:disabled .dp-tile-n{color:var(--ac-text-2)}',
59
+ '.dp-tile:disabled{cursor:default}',
60
+ '.dp-tile[data-active="true"] .dp-tile-icon{outline:3px solid var(--ac-active);outline-offset:2px}',
61
+
62
+ // The second layer's top row: back, title, one grey line.
63
+ '.dp-drill{display:flex;align-items:center;gap:7px;margin:0 0 10px}',
64
+ '.dp-drill-back{font:inherit;font-size:16px;font-weight:800;line-height:1;width:26px;height:26px;',
65
+ 'flex:none;cursor:pointer;color:var(--ac-text);border-radius:50%;',
66
+ 'border:2px solid var(--ac-border-light);background:var(--ac-bg-input)}',
67
+ '.dp-drill-back:hover{border-color:var(--ac-border-hover)}',
68
+ '.dp-drill-title{font-size:12px;font-weight:800;color:var(--ac-text);white-space:nowrap}',
69
+ '.dp-drill-info{flex:1;min-width:0;text-align:right;font-size:10px;font-weight:600;',
70
+ 'color:var(--ac-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
71
+ // B9: the home screen replaces the bottom icon bar. The bar still exists
72
+ // (its icons carry the alert state the home tiles read) but is not shown.
73
+ '[data-dsh-pig] .dp-card .dp-bar{display:none}',
74
+ '.dp-app-head{margin-bottom:12px}',
75
+ // Out working, the collapsed scene shrinks to pig + prop; open, it must stay
76
+ // as wide as the panel, or the name plate is squeezed onto the pig.
77
+ '[data-dsh-pig][data-open="true"][data-away] .dp-scene{width:var(--panel-width)}',
78
+ // Banners only live on the status tab now, with room to breathe below.
79
+ '[data-dsh-pig] .dp-alert{margin-bottom:14px}',
80
+ '[data-dsh-pig] .dp-alert + .dp-actions{margin-bottom:14px}',
81
+ '.dp-job-go{display:block;width:100%;margin-top:9px}',
82
+ // A picked tile's details (a diary page, a souvenir's story) sit under the grid.
83
+ '.dp-tile-card{margin-top:12px}',
84
+ // 更新 App: the release notes keep their line breaks but stay short.
85
+ '.dp-update-notes{white-space:pre-wrap;font-size:10.5px;line-height:1.5;color:var(--ac-text-2);max-height:120px;overflow:auto;margin:4px 0 6px}',
86
+ '.dp-update-back{margin-top:10px;width:100%}',
87
+ '.dp-update-now{margin-bottom:12px}',
88
+ '.dp-update-now .dp-btn,.dp-update-detail .dp-btn{width:100%;margin-top:8px}',
89
+ ].join('')