Files
LisherSong--ps5-web-file-ma…/assets/main.css
T
Songlx516 ba668ade50 release: v1.9.3M -- encrypted archives, plus the UI round that followed
First release under the fork-marker convention: VERSION_TAG carries a trailing
`M`, so /api/version, the PS5 start-up notification, the stdout banner, the UI
footer and the ELF file name all read "v1.9.3M" in one move -- and a fork build
can no longer collide with an upstream artifact of the same version, a mix-up
that already happened twice. The footer carries a tooltip spelling the marker
out.

Two bodies of work.

1. Encrypted archives (ZIP / RAR / 7z)

   - ZIP: ZipCrypto (traditional PKWARE) and WinZip AES-256, through
     minizip-ng plus a vendored crypto layer (mz_crypt_wfm.c,
     mz_strm_pkcrypt.c, mz_strm_wzaes.c).
   - RAR: RARSetPassword, wired after RAROpenArchiveEx and before the first
     RARReadHeaderEx. The ordering is load-bearing, not stylistic.
   - 7z: 7zAES including -mhe=on encrypted headers, via a virtual
     ISeekInStream that splices a pseudo-header + the real archive + the
     decrypted header, so no offset stored inside the archive has to move.

   A wrong password is reported as ZIPX_ERR_PASSWORD, and a failed attempt
   leaves no staging directory behind.

2. Reporting, and the UI round that on-device testing produced

   - A RAR whose dictionary exceeds what the build supports now gets its own
     extract_dict_too_large code instead of being mis-reported as "entry too
     large"; the message names both the required and the supported size. The
     behaviour is deliberately unchanged -- such archives are still refused,
     because admitting one means allocating the whole window up front, which
     is why rarlab's own CLI refuses them by default.
   - The upload entry is a menu again: one "Upload" button opening "Upload
     files / Upload folder". The previous main-button-plus-small-arrow made
     "upload folder" effectively undiscoverable.
   - A drag-and-drop hint sits in the footer (hidden on the console browser,
     where drag is not how anyone uploads).
   - The extract button is now always present and merely disabled until
     exactly one archive is selected, instead of appearing out of nowhere.
   - Upload-and-extract on an encrypted archive now prompts for the password
     directly. The retry table used to be keyed by PATH, and for a non-ASCII
     directory the string the page holds and the string the server reports
     are not the same bytes -- the lookup missed, so the user got a bare
     error box and had to press Extract by hand before the prompt appeared.
     It is now keyed by task id, which the server assigns and echoes back
     verbatim.
   - Error text passes through decodeFsText(), so a GBK entry name no longer
     surfaces as `â®…ç§.psd`.
   - Local names are encoded with encodeFsText() before being joined onto a
     server-side path. fs_path_value() declines to rewrite a path if ANY code
     point exceeds 0xFF, so concatenating a local name onto a server directory
     produced a mixed representation and a silently dead path.
   - The menu row highlight was losing the cascade to the generic button rule
     (identical specificity, later in the file) while inheriting the toolbar's
     3px focus ring, which overflowed a 46px row. Both rules are now scoped to
     the panel and the keyboard cue is an inset ring, so it cannot escape the
     row at any line height.
   - The footer status line is clamped to a single line; a long
     "uploading 3/12: some-name.zip" used to wrap out of the 46px footer.
   - A first failed password attempt now says the archive is encrypted,
     instead of blaming a password the user was never asked for.

Artifact
  web-file-mgr-v1.9.3M.elf
  903,448 B
  sha256 8ca47d5aaca75085b32641300cce30fadb7df7749cb6b53d04f129bcecc286b7
  e_machine 0x003e (x86-64 / PS5)

  The file size is identical to the four builds before it, and every one of
  them carries a different sha256: only .rodata moved, and by less than the
  16 KiB section alignment absorbs. Compare sections with `readelf -SW` --
  never infer "nothing changed" from the byte count.

Verification
  - host suites: 140 ZIP + 37 RAR = 177 checks, 0 failures
  - 7z suite: 27 cases, 0 failures. The `aeshe` entry that used to sit in
    KNOWN_GAPS is gone -- the -mhe=on fixture now passes both the folder
    decoder and the extraction facade
  - frontend: .build/ui_retry_test.mjs (40 checks), .build/ui_upload_menu_test.mjs
    (40 checks), .build/preview_check.mjs (12 assertions in headless Chromium
    against the real page and a fixture API). Two layout regressions and the
    highlight cascade bug were caught by the last one and by nothing else --
    reading the source, both CSS rules "look correct"
  - built twice from this tree: byte-identical (cmp clean). rsync refreshes
    every asset mtime, so this is a genuine recompile, not make short-circuiting
    on unchanged sources
  - embedded assets verified in place with .build/check-elf-gzip.py, because
    gen-asset-module.py gzips them and plain `strings` finds none of their text
  - exercised end to end on a real PS5; the checklist is
    docs/DEVICE-TEST-v1.9.3M.md

Docs
  - docs/USER-GUIDE-zh-CN.md (new, simplified Chinese user guide)
  - docs/DEVICE-TEST-v1.9.3M.md (new, on-device acceptance checklist)
  - docs/REAL-CONSOLE-PROFILE.md (new, measured console behaviour)
  - docs/archive/HANDOVER-v1.8-planning.md (superseded v1.8 design notes)
  - CHANGELOG / README (both languages) / HANDOVER updated with the artifact
    fingerprint, the section deltas and the new test counts
2026-09-24 21:07:12 +08:00

1606 lines
25 KiB
CSS

:root {
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #111316;
color: #edf0f2;
}
* {
box-sizing: border-box;
}
[hidden] {
display: none !important;
}
.init-loading {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 20000;
display: flex;
align-items: center;
justify-content: center;
background: #111316;
color: #aab4be;
font-size: 18px;
}
body {
margin: 0;
min-height: 100vh;
background: #111316;
font-size: 20px;
overflow: hidden;
}
.shell {
min-height: 100vh;
height: 100vh;
display: flex;
flex-direction: column;
}
.topbar {
position: relative;
flex: 0 0 auto;
height: auto;
min-height: 54px;
display: flex;
align-items: start;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 18px;
background: #1b2026;
border-bottom: 1px solid #2d343d;
overflow: visible;
}
.top-left,
.top-right {
height: auto;
display: flex;
align-items: center;
}
.top-left {
flex: 1 1 0;
min-width: 0;
margin-top: 15px;
}
.top-right {
flex: 0 1 auto;
min-width: 0;
margin-left: auto;
justify-content: flex-end;
flex-wrap: wrap;
padding: 12px 40px 12px 0;
text-align: right;
}
.path {
color: #aab4be;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 22px;
line-height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
flex: 1 1 auto;
min-width: 0;
}
.space-info {
height: auto;
min-height: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
flex: 1 1 auto;
max-width: none;
color: #c9d3dc;
font-size: 18px;
line-height: 30px;
white-space: nowrap;
overflow: visible;
text-align: right;
}
.space-info .space-item {
flex: 0 0 auto;
display: inline-flex;
height: auto;
min-height: 0;
min-width: 0;
margin-left: 14px;
padding: 0 2px;
border: 0;
border-radius: 3px;
background: transparent;
color: inherit;
font: inherit;
line-height: inherit;
text-align: right;
cursor: pointer;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.space-info .space-item:hover:not(:disabled),
.space-info .space-item:focus:not(:disabled) {
color: #fff;
background: rgba(255, 255, 255, 0.1);
outline: none;
}
.space-info .space-item:focus-visible {
box-shadow: 0 0 0 2px #87bfff;
}
.space-info .space-item:active {
background: rgba(255, 255, 255, 0.18);
}
.space-info .space-item:disabled {
opacity: 1;
color: inherit;
cursor: default;
pointer-events: none;
}
.space-prefix {
color: #8f9aa5;
flex: 0 0 auto;
}
.space-info .space-item.current {
color: #f2f5f7;
font-weight: 700;
}
.icon-button {
min-height: 24px;
min-width: 24px;
width: 24px;
height: 24px;
padding: 0;
}
.power-button {
position: relative;
border-color: #8f4545;
background: #402326;
}
.power-button::before {
content: "";
width: 10px;
height: 10px;
border: 2px solid #f2f5f7;
border-top-color: transparent;
border-radius: 50%;
}
.power-button::after {
content: "";
position: absolute;
top: 4px;
left: 50%;
width: 2px;
height: 11px;
border-radius: 2px;
background: #f2f5f7;
transform: translateX(-50%);
}
#exitBtn {
position: absolute;
top: 15px;
right: 18px;
margin-left: 0;
}
.toolbar,
.tool-left,
.tool-right {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.toolbar {
flex: 0 0 auto;
height: auto;
min-height: 84px;
justify-content: space-between;
padding: 15px 18px 0;
background: #15191e;
border-bottom: 1px solid #2d343d;
overflow: visible;
}
.tool-left {
min-width: 0;
flex: 1 1 auto;
}
.toolbar .tool-left,
.toolbar .tool-right {
min-height: 54px;
flex-wrap: wrap;
}
.tool-left > *,
.tool-right > * {
margin: 0 12px 15px 0;
}
.tool-right > * {
margin-right: 0;
margin-left: 12px;
}
.tool-right {
justify-content: flex-end;
flex: 1 1 auto;
}
.upload-menu {
position: relative;
display: inline-flex;
align-items: stretch;
height: 54px;
min-height: 54px;
margin-bottom: 15px;
}
.upload-menu .upload-main {
min-width: 82px;
margin: 0;
}
/* The caret is what tells the user this button opens a list. Without it the
button looks like a plain one-shot action, which is exactly how the old
file/folder split was misread. */
.upload-main::after {
content: "";
display: block;
width: 0;
height: 0;
margin-left: 10px;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 7px solid #f2f5f7;
}
.upload-menu-list {
position: absolute;
top: calc(100% + 6px);
right: 0;
z-index: 700;
display: flex;
flex-direction: column;
padding: 6px;
border: 1px solid #46515f;
border-radius: 8px;
background: #1b2128;
box-shadow: 0 12px 28px rgba(0, 0, 0, .5);
}
.upload-menu-list button {
height: 46px;
min-height: 46px;
min-width: 176px;
padding: 0 16px;
border: 0;
border-radius: 6px;
background: transparent;
justify-content: flex-start;
text-align: left;
white-space: nowrap;
}
/* Menu rows highlight by fill only. The generic button ring (3px at offset
2px) is sized for a 54px toolbar button: on a 46px row it clears the
panel's 6px padding, overlaps the neighbouring row, and its corners do not
follow the row's radius, so the highlight reads as a broken shape rather
than a selected row. The panel's own hover fill had also never applied --
the generic `button:not(.row-action):hover:not(:disabled)` rule has the
same specificity (0,3,1) and comes later in this file, so it won and hover
and focus ended up two different colours. Scoping both rules to the panel
settles the cascade, and the keyboard cue is drawn inside the row where it
cannot cross the panel edge. */
#uploadMenu button:focus {
outline: none;
}
#uploadMenu button:hover:not(:disabled),
#uploadMenu button:focus-visible {
background: #2b343e;
}
#uploadMenu button:focus-visible {
box-shadow: inset 0 0 0 2px #6fb1ff;
}
/* The status can be a long "uploading 3/12: some-very-long-name.zip". The footer
is a fixed 46px row, so a wrapped status used to spill out of it; clamping it
to one line with an ellipsis is what keeps it, the hint and the version on the
same row at every width. */
#statusText {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* A hint that has to fit next to the status line and the version, so it shrinks
with an ellipsis instead of pushing either of them around. */
.status-hint {
flex: 0 1 auto;
min-width: 0;
margin: 0 16px;
overflow: hidden;
color: #6f7a84;
font-size: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
button,
.button {
height: 54px;
min-height: 54px;
min-width: 96px;
padding: 0 20px;
border: 1px solid #46515f;
border-radius: 8px;
background: #252c34;
color: #f2f5f7;
font: inherit;
font-size: 20px;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
button:focus,
.button:focus,
.row-action:focus {
outline: 3px solid #6fb1ff;
outline-offset: 2px;
}
button:not(.row-action):hover:not(:disabled),
.button:hover:not(:disabled) {
background: #303945;
}
button:disabled,
.button.disabled {
opacity: .45;
cursor: default;
pointer-events: none;
}
.danger {
border-color: #8f4545;
background: #402326;
}
.primary {
border-color: #4f9f7b;
background: #285943;
}
button.primary:hover:not(:disabled) {
background: #347358;
}
.secondary {
border-color: #59636e;
background: #20262d;
}
.paste-action {
max-width: 360px;
}
.paste-action > * {
flex: 0 0 auto;
margin-right: 8px;
}
.paste-name {
display: inline-block;
flex: 1 1 auto;
min-width: 0;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.install-action {
min-width: 88px;
border-color: #8c6425;
background: #352b1c;
color: #f8dfae;
}
button.install-action:hover:not(:disabled) {
background: #433521;
}
.extract-action {
/* Matches the width of the other two-character verbs (copy/move/delete): the
button is on screen at all times now, so it has to line up with them
instead of being a special case that appears only on selection. */
min-width: 96px;
border-color: #2a6b66;
background: #17302e;
color: #a8e6df;
}
button.extract-action:hover:not(:disabled) {
background: #1d3f3c;
}
/* The extract button is always on screen and only greys out when the selection
cannot be extracted, so the reason has to be readable. `button:disabled`
sets `pointer-events: none`, which makes the button un-hoverable and swallows
its `title` tooltip -- the same fix the parent-directory button already uses.
Clicks stay dead either way: the disabled attribute blocks activation
regardless of hit-testing. */
.extract-action:disabled {
pointer-events: auto;
}
.paste-count {
white-space: nowrap;
}
.content {
position: relative;
flex: 1 1 auto;
min-height: 0;
overflow: auto;
}
.content.loading {
pointer-events: none;
}
.content-loading {
position: absolute;
right: 0;
left: 0;
height: 100%;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
background: rgba(4, 6, 8, .72);
}
.content-loading-text {
color: #aab4be;
font-size: 18px;
}
table {
width: 100%;
min-width: 1034px;
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
}
th,
td {
padding: 16px 18px;
border-bottom: 1px solid #252b33;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 20px;
line-height: 24px;
}
th {
position: sticky;
top: 0;
z-index: 1;
background: #171c22;
color: #aeb8c2;
font-weight: 600;
}
th.sortable {
cursor: pointer;
}
th.sortable::after {
content: "";
display: inline-block;
width: 12px;
margin-left: 8px;
color: #aeb8c2;
text-align: center;
}
th.sorted-asc::after {
content: "^";
}
th.sorted-desc::after {
content: "v";
}
.name-head {
height: 78px;
min-height: 78px;
display: flex;
align-items: center;
min-width: 0;
}
th.name-col {
padding: 0;
}
th.name-col.sortable::after {
content: none;
}
.name-heading {
min-width: 100px;
padding-left: 18px;
overflow: hidden;
text-overflow: ellipsis;
}
.name-heading::after {
content: "";
display: inline-block;
width: 12px;
margin-left: 8px;
color: #aeb8c2;
text-align: center;
}
th.name-col.sorted-asc .name-heading::after {
content: "^";
}
th.name-col.sorted-desc .name-heading::after {
content: "v";
}
.parent-nav-button {
width: 78px;
min-width: 78px;
height: 78px;
min-height: 78px;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
position: relative;
}
.parent-nav-button img {
width: 32px;
height: 32px;
display: block;
}
.parent-nav-button:hover:not(:disabled) {
background: #222932;
}
.parent-nav-button:not(:disabled):focus {
outline-offset: -5px;
}
.parent-nav-button:disabled img {
opacity: .42;
}
.parent-nav-button:disabled {
pointer-events: auto;
}
.select-col {
width: 74px;
padding: 0;
}
.name-col {
min-width: 600px;
}
td.name-col {
padding: 0;
}
.type-col {
width: 130px;
text-align: center;
}
.size-col {
width: 145px;
text-align: right;
}
th.size-col {
text-align: center;
}
.time-col {
width: 225px;
text-align: center;
}
.mode-col {
width: 100px;
text-align: center;
}
td.mode-col {
padding: 0;
}
.mode-action {
width: 100%;
min-width: 0;
height: 78px;
min-height: 78px;
padding: 0 8px;
border: 0;
border-radius: 0;
background: transparent;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.select-cell {
padding: 0;
text-align: center;
}
tr {
height: 78px;
}
thead tr {
height: 78px;
cursor: default;
}
tr.hovered,
tr.focused {
background: #171d24;
}
tr.selected {
background: #1d2d35;
}
tr[data-type="parent"] {
cursor: pointer;
}
.select-hit {
height: 78px;
min-height: 78px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
input[type="checkbox"] {
width: 34px;
height: 34px;
cursor: pointer;
}
.icon {
width: 32px;
height: 32px;
flex: 0 0 32px;
display: block;
}
.special-folder-icon {
-webkit-filter: hue-rotate(65deg);
filter: hue-rotate(65deg);
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
.row-action {
width: 100%;
height: 78px;
min-height: 78px;
padding: 16px 18px;
border: 0;
border-radius: 0;
background: transparent;
color: #edf0f2;
justify-content: flex-start;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
.row-action .icon {
margin-right: 14px;
}
.name-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.empty {
color: #aab4be;
padding: 20px 90px;
}
.status {
color: #aab4be;
flex: 0 0 auto;
height: 46px;
min-height: 46px;
padding: 0 25px;
line-height: 46px;
border-top: 1px solid #2d343d;
background: #15191e;
font-size: 18px;
display: flex;
align-items: center;
justify-content: space-between;
}
.version-text {
flex: 0 0 auto;
color: #7f8a94;
}
.text-editor-overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9000;
display: flex;
align-items: center;
justify-content: center;
padding: 48px;
background: rgba(4, 6, 8, .9);
}
.text-editor-panel {
width: 1200px;
height: calc(100vh - 96px);
min-width: 0;
max-width: calc(100vw - 96px);
display: flex;
flex-direction: column;
padding: 24px;
border: 1px solid #46515f;
border-radius: 10px;
background: #15191e;
box-shadow: 0 18px 60px rgba(0, 0, 0, .45);
}
.text-editor-path {
flex: 0 0 auto;
margin-bottom: 14px;
overflow: hidden;
color: #c9d3dc;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 20px;
white-space: nowrap;
text-overflow: ellipsis;
}
.text-editor {
width: 100%;
min-height: 0;
flex: 1 1 auto;
padding: 18px;
border: 1px solid #46515f;
border-radius: 6px;
outline: none;
resize: none;
background: #0f1215;
color: #edf0f2;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 20px;
line-height: 1.5;
}
.text-editor:focus {
border-color: #6fb1ff;
}
.text-editor-status {
min-height: 28px;
padding-top: 8px;
color: #aab4be;
font-size: 18px;
}
.text-editor-actions {
display: flex;
}
.text-editor-actions button {
flex: 1 1 0;
}
.text-editor-actions button + button {
margin-left: 14px;
}
.image-preview-stage {
width: 100%;
height: calc(100vh - 250px);
min-width: 0;
min-height: 0;
flex: 1 1 auto;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.image-preview {
display: block;
width: auto;
height: auto;
max-width: 100%;
max-height: calc(100vh - 250px);
pointer-events: none;
-webkit-user-drag: none;
user-select: none;
}
.image-preview-panel > button {
width: 100%;
margin-top: 14px;
}
.pkg-info-panel {
width: 1100px;
height: auto;
max-height: calc(100vh - 96px);
}
.pkg-info-title {
margin: 0 0 18px;
color: #edf0f2;
font-size: 26px;
font-weight: 600;
}
.pkg-info-content {
min-height: 0;
display: flex;
flex: 1 1 auto;
margin-bottom: 18px;
}
.pkg-info-image-stage {
width: 340px;
flex: 0 0 340px;
display: flex;
align-items: flex-start;
justify-content: center;
margin-right: 24px;
}
.pkg-info-image {
display: block;
width: auto;
height: auto;
max-width: 100%;
max-height: 340px;
pointer-events: none;
-webkit-user-drag: none;
user-select: none;
}
.pkg-info-fields {
min-width: 0;
min-height: 80px;
flex: 1 1 auto;
margin-bottom: 0;
overflow-x: hidden;
overflow-y: auto;
border-top: 1px solid #2d343d;
}
.pkg-info-row {
display: flex;
padding: 10px 0;
border-bottom: 1px solid #2d343d;
font-size: 18px;
}
.pkg-info-key {
width: 244px;
flex: 0 0 244px;
padding-right: 24px;
color: #8f9ba6;
word-break: break-all;
}
.pkg-info-value {
min-width: 0;
flex: 1 1 auto;
color: #d9e0e6;
word-break: break-all;
}
.permission-overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9500;
display: flex;
align-items: center;
justify-content: center;
padding: 48px;
background: rgba(4, 6, 8, .9);
}
.permission-panel {
width: 620px;
max-width: calc(100vw - 96px);
padding: 28px;
border: 1px solid #46515f;
border-radius: 10px;
background: #15191e;
box-shadow: 0 18px 60px rgba(0, 0, 0, .45);
}
.permission-title {
margin: 0;
color: #edf0f2;
font-size: 26px;
font-weight: 600;
}
.permission-path {
display: flex;
margin: 10px 0 22px;
color: #9ba7b2;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 17px;
white-space: nowrap;
}
.permission-path-names {
min-width: 0;
flex: 1 1 0;
overflow: hidden;
text-overflow: ellipsis;
}
.permission-path-count {
flex: 0 0 auto;
margin-left: 8px;
}
.permission-grid {
padding: 12px 0;
border-top: 1px solid #2d343d;
border-bottom: 1px solid #2d343d;
}
.permission-row {
display: flex;
min-height: 50px;
align-items: center;
}
.permission-row > * {
width: 96px;
flex: 0 0 96px;
text-align: center;
}
.permission-row .permission-scope {
width: auto;
min-width: 0;
flex: 1 1 auto;
color: #c9d3dc;
text-align: left;
}
.permission-head {
min-height: 28px;
color: #7f8a94;
font-size: 16px;
}
.permission-row label {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.permission-row label input {
width: 30px;
height: 30px;
margin: 0 8px 0 0;
}
.permission-octal {
display: flex;
align-items: center;
margin: 22px 0;
color: #c9d3dc;
}
.permission-octal span {
flex: 1 1 auto;
}
.permission-octal input {
width: 150px;
height: 50px;
padding: 0 14px;
border: 1px solid #46515f;
border-radius: 6px;
outline: none;
background: #0f1215;
color: #edf0f2;
font: 22px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
text-align: center;
}
.permission-octal input:focus {
border-color: #6fb1ff;
outline: 3px solid rgba(111, 177, 255, .25);
}
.permission-octal input:invalid {
border-color: #b86262;
}
.permission-recursive {
min-height: 46px;
display: flex;
align-items: center;
margin: -8px 0 18px;
color: #c9d3dc;
cursor: pointer;
}
.permission-recursive input {
flex: 0 0 auto;
margin: 0 12px 0 0;
}
.permission-actions {
display: flex;
}
.permission-actions button {
flex: 1 1 0;
}
.permission-actions button + button {
margin-left: 14px;
}
/* Shown while a file drag hovers the window. pointer-events stays off so the
drop still lands on the window listener rather than on this element, and the
z-index sits above the task overlay so the hint is visible during a running
task (the drop itself is ignored while busy). */
.drop-upload-overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 19000;
display: flex;
align-items: center;
justify-content: center;
background: rgba(8, 11, 14, .88);
box-shadow: inset 0 0 0 4px #6fb1ff;
pointer-events: none;
}
.drop-upload-message {
max-width: calc(100% - 48px);
padding: 26px 34px;
border: 1px solid #46515f;
border-radius: 8px;
background: #1b2026;
box-shadow: 0 18px 54px rgba(0, 0, 0, .5);
color: #edf0f2;
font-size: 24px;
line-height: 1.4;
text-align: center;
}
.task-overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
padding: 48px;
background: rgba(4, 6, 8, .82);
}
.task-overlay[hidden] {
display: none;
}
.task-panel {
width: 980px;
max-width: calc(100vw - 96px);
border: 1px solid #46515f;
border-radius: 10px;
background: #15191e;
padding: 30px;
box-shadow: 0 18px 60px rgba(0, 0, 0, .45);
}
.tasks {
display: block;
}
.task {
display: block;
}
.task > * + * {
margin-top: 14px;
}
.task .danger {
width: 100%;
}
.task-head {
display: flex;
align-items: flex-start;
font-size: 24px;
justify-content: space-between;
position: relative;
}
.task-head.has-amount {
justify-content: space-between;
}
.task-amount {
min-width: 160px;
white-space: nowrap;
text-align: right;
}
.task-name,
.task-current {
min-width: 0;
overflow: hidden;
white-space: nowrap;
}
.task-current-plain {
text-overflow: ellipsis;
}
.task-name {
display: flex;
min-width: 0;
text-overflow: ellipsis;
flex: 1 1 auto;
}
.task-name > * {
margin-right: 8px;
}
.task-name-op,
.task-name-state {
flex: 0 0 auto;
}
.task-name-subject {
min-width: 0;
max-width: 560px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.task-elapsed {
flex: 0 0 auto;
white-space: nowrap;
text-align: right;
margin-left: 16px;
color: #aab4be;
}
.task-meta {
display: flex;
color: #c9d3dc;
font-size: 20px;
}
.task-meta div {
flex: 1 1 0;
min-width: 0;
margin-right: 18px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.task-meta div:last-child {
margin-right: 0;
}
.task-meta div:nth-child(2) {
text-align: center;
}
.task-meta div:nth-child(3) {
text-align: right;
}
.task-current {
color: #aab4be;
font-size: 20px;
}
.progress {
position: relative;
height: 34px;
overflow: hidden;
border-radius: 7px;
background: #252c34;
}
.progress-bar {
height: 100%;
width: 0;
background: #4f9f7b;
}
.progress-text {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
color: #edf0f2;
font-size: 18px;
font-weight: 700;
}
.task.failed .progress-bar {
background: #b75a5a;
}
.task.canceled .progress-bar {
background: #8a929b;
}
@media (max-width: 900px) {
.pkg-info-panel {
width: 760px;
}
.pkg-info-content {
display: block;
}
.pkg-info-image-stage {
width: auto;
height: 260px;
margin: 0 0 18px;
}
.pkg-info-image {
max-height: 260px;
}
.pkg-info-fields {
margin-bottom: 18px;
}
}
@media (max-width: 815px) {
.top-left {
padding: 0 40px 0 0;
}
.top-right {
padding: 10px 0;
}
}
@media (max-width: 640px) {
body {
font-size: 16px;
}
.topbar {
min-height: 42px;
padding: 0 10px;
align-items: center;
flex-wrap: nowrap;
}
.top-left {
flex: 1 1 auto;
margin-top: 0;
padding-right: 34px;
}
.top-right {
display: none;
}
.path {
font-size: 16px;
line-height: 42px;
}
#exitBtn {
top: 9px;
right: 10px;
}
.toolbar {
min-height: 0;
padding: 8px 8px 0;
}
.toolbar .tool-left,
.toolbar .tool-right {
min-height: 0;
}
.tool-left,
.tool-right {
flex: 1 1 100%;
}
.tool-right {
justify-content: flex-start;
}
.tool-left > *,
.tool-right > * {
margin: 0 8px 8px 0;
}
.tool-right > * {
margin-left: 0;
}
button,
.button {
height: 40px;
min-height: 40px;
min-width: 76px;
padding: 0 12px;
border-radius: 6px;
font-size: 16px;
}
.upload-menu {
height: 40px;
min-height: 40px;
margin-bottom: 8px;
}
.upload-menu .upload-main {
min-width: 64px;
}
.upload-menu-list {
padding: 4px;
}
.upload-menu-list button {
height: 38px;
min-height: 38px;
min-width: 148px;
padding: 0 12px;
}
.status-hint {
margin: 0 10px;
font-size: 14px;
}
.paste-action {
max-width: 100%;
}
.paste-name {
max-width: 120px;
}
table {
min-width: 960px;
}
.name-col {
width: 600px;
}
th,
td {
padding: 10px 12px;
font-size: 16px;
line-height: 20px;
}
tr,
thead tr,
.name-head,
.parent-nav-button,
.select-hit,
.row-action,
.mode-action {
height: 56px;
min-height: 56px;
}
.permission-overlay {
padding: 18px;
}
.permission-panel {
max-width: calc(100vw - 36px);
padding: 18px;
}
.parent-nav-button {
width: 56px;
min-width: 56px;
}
.name-heading {
padding-left: 12px;
}
.row-action {
padding: 10px 12px;
}
input[type="checkbox"] {
width: 28px;
height: 28px;
}
.icon {
width: 26px;
height: 26px;
flex-basis: 26px;
}
.status {
height: 34px;
min-height: 34px;
padding: 0 12px;
line-height: 34px;
font-size: 14px;
}
.task-overlay {
padding: 18px;
align-items: center;
}
.task-panel {
width: 100%;
max-width: none;
padding: 18px;
border-radius: 8px;
}
.task > * + * {
margin-top: 12px;
}
.task-head {
display: block;
font-size: 20px;
}
.task-name {
display: flex;
max-width: none;
white-space: nowrap;
}
.task-name > * {
margin: 0 8px 0 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.task-name-subject {
flex: 1 1 auto;
max-width: none;
min-width: 0;
}
.task-elapsed {
margin: 8px 0 0;
text-align: left;
font-size: 18px;
}
.task-current {
font-size: 18px;
white-space: normal;
word-break: break-all;
}
.task-meta {
display: block;
font-size: 18px;
}
.task-meta div,
.task-meta div:nth-child(2),
.task-meta div:nth-child(3) {
display: block;
margin: 0 0 8px;
text-align: left;
white-space: normal;
}
.task-meta div:last-child {
margin-bottom: 0;
}
.progress {
height: 32px;
}
.task .danger {
width: 100%;
}
}