diff --git a/.gitignore b/.gitignore index cd4968f..0a2eb6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,80 @@ -node_modules/ -dist/ -# electron-builder output (kept separate from dist/ so builds don't swallow -# their own previous artifacts via the files "dist/**" pattern) -release/ +*~ +drafts +.acl +.meta +profile +.internal + +# Logs +logs *.log -.DS_Store +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz -# rdf2html's editable shell snapshot (scratch; the RDF is the source) -tools/conversion/shell.html +# Yarn Integrity file +.yarn-integrity -# the pivot server's PRE-COMPILED config is a required, committed artifact -# (regenerating needs an isolated build — see pivot/build-compiled-config.sh) -!pivot/dist/ -!pivot/dist/create-app.cjs +# dotenv environment variables file +.env -# Community Solid Server runtime storage state (POD_ROOT = repo root) -.internal/ +# next.js build output +.next -# The seeded personal pod instance (POD_ROOT = repo root in dev). The SOURCE is -# the committed pod-template/; dk-pod/ is the per-machine seeded copy. -/dk-pod/ -# Root owner announcement, seeded with the live origin (solid:owner → dk-pod WebID) -/.meta +# notes and drafts +drafts -# editor backups / autosave +# emacs backups *~ -\#*\# -.\#* -# machine-local Claude Code settings (permission grants, etc.) -.claude/settings.local.json +# testing with other front-end +bundles/solid-ide/* -# Personal notes + Claude session artifacts — not for the public repo -notes.md -claude/ -drafts/ +# don't overwrite user's config +config.json \ No newline at end of file diff --git a/README.md b/README.md index 002cba5..d129126 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,34 @@ -# Solid Data Kitchen - pod in a box - -This native electron app for Linux, Windows, and Mac turns your computer into a Solid pod only you can access. It mounts a local version of Pivot/CSS, a proxy server and the Data-Kitchen frontend. Data Kitchen comes pre-loaded with dozens of Solid apps and widgets as well as a wide variety of open source resources including players for the Internet Archive and Wikimedia, and access to Mastodon, Peertube, and many more. - -No setup or install needed, just download and run. Downloads for Linux, Windows, and macOS available at https://solidOS.github.io/data-kitchen/downloads.html - -## Features - -* Entire UI is saved as RDF and is editable through forms - * All menus, buttons, and interactions in the UI shell are user customizable - * No RDF or coding knowledge is required of the end user - * Forms can be auto-generated from SHACL using solid-ui forms - * Form submissions are self-validating against the SHACL -* A plugin system lets users mount plugins by drag and drop -* Plugins can share auth, store, and other features - * Currently with DK-browser, SolidOS, and Dokeili, Login once, all three are authenticated -* A dual-panel pod browser supports - * simultaneous logins to multiple WebIDs - * drag & drop to copy & move between pods -* Live visual graphs for RDF, Markdown, and Mermaid -* Listen to music while visiting any tab -* User can use home pod without logging in - it is secure behind a data-kitchen/electron gate -* User can safely browse remote pods - data-kitchen/electron firewalls external fetches - they can't reach your pod even if coming from your own machine -* Customizable native app triggers (run Claude Code or any native app with a click) -* Runs flutter apps from ANU - -## License - -MIT (c) 2019,2026, Jeff Zucker +# Data-Kitchen over CSS (WiP) + +The Data Kitchen is a stand-alone desktop app that provides the user with a SolidOS (mashlib) Databrowser capable of private no-auth access to their own file system and also authenticated access to pods on any kind of Solid server. The current impelementation is an electron app that serves the local file system via a pre-provisioned Community-Solid-Server (CSS) with the SolidOS Databrowser as front end. See [pretty picture](./assets/Data-Kitchen.png) if you like visuals. + +Please ping me (@jeff-zucker) in the chat or via issue with any problems or suggestions. + +## Installing +```text +git clone https://github.com/solid/data-kitchen.git +change into the data-kitchen folder +npm ci +``` + +## Configuring +The [kitchen.json](./kitchen.json) file will eventually be JSON-LD with a solid-ui forms-based frontend. For now it's plain JSON and you need to edit it manully. You MUST configure the rootFilePath (the place you want to view as the root of your local files) and you may configure the port, the size and position of the initial window. You can also set bookmarks (handy for testing on various servers). + +## Running +```text +change into the data-kitchen folder +npm start +``` + +## Differences from the standard Databrowser + +The banner changes color depending on wether you are viewing a private offline resource, an online resource without authentication, and an online resource with authentication. + +Note that local files follow local preferences set in /LocalKitchenUser/settings/prefs.ttl. You should not be prompted for login on local files. The top menu provides bookmarks and other resources not found in the standard databrowser. +There are many more features in the works, see [checklist of currently supported and planned features](https://github.com/SolidOS/data-kitchen/issues/31). + +## Dirty Details + +If you want to look under the hood, see [kitchen.html](./assets/kitchen.html) (which is a version of *mashlib/dist/browse.html*) and the [electron and CSS specific modifications](./configs/). + +copyright (c) 2021 Jeff Zucker, MIT license. diff --git a/assets/Data-Kitchen.png b/assets/Data-Kitchen.png new file mode 100644 index 0000000..5031602 Binary files /dev/null and b/assets/Data-Kitchen.png differ diff --git a/assets/bootstrap.min.css b/assets/bootstrap.min.css new file mode 100644 index 0000000..5b96335 --- /dev/null +++ b/assets/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:"Glyphicons Halflings";src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format("embedded-opentype"),url(../fonts/glyphicons-halflings-regular.woff2) format("woff2"),url(../fonts/glyphicons-halflings-regular.woff) format("woff"),url(../fonts/glyphicons-halflings-regular.ttf) format("truetype"),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:""}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*=col-]{padding-right:0;padding-left:0}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],.input-group-sm input[type=time],input[type=date].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,input[type=time].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],.input-group-lg input[type=time],input[type=date].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,input[type=time].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;background-image:none;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/assets/dk-chrome.css b/assets/dk-chrome.css deleted file mode 100644 index d5a95c6..0000000 --- a/assets/dk-chrome.css +++ /dev/null @@ -1,413 +0,0 @@ -/* omp shell styles — extracted from index.html (2026-05-31). - Theme / text-size defaults come from 's RDF source: sol-default - reads ui:colorScheme / ui:fontSize into the `color-scheme` / `font-size` - attributes, whose VALUES are the full UI-vocab URIs (…#DarkColorScheme, - …#MediumFont). dk-boot applies a SAVED choice (localStorage) to pre-paint; the DEFAULT (and the system fallback for - theme) is resolved by the :has() cascade below, suffix-matching ($=) the URI - local name — no script, no window flag. (solid-kitchen, the dev-write flag, - stays a plain attribute.) */ - - :root { - /* Per-library tab accents (mirror the in-panel room accents). */ - --tab-music: #f0a23a; --tab-movies: #49c8d8; --tab-news: #6fae6f; --tab-images: #b07cc6; --tab-on: #15100a; - --tab-workspaces: #8a93e0; --tab-resources: #5b9bd5; --tab-devtools: #e58a8a; - } - [data-theme="light"] { - --tab-music: #a8581a; --tab-movies: #1c7283; --tab-news: #2f7d4f; --tab-images: #7d4f9e; --tab-on: #ffffff; - --tab-workspaces: #4348a8; --tab-resources: #2f5f9e; --tab-devtools: #a83a3a; - } - - /* ── Theme DEFAULT — when there's no explicit override ── - Saved/toggled theme lives on (above + ia.css). With none - set, the default is read off (RDF-derived - from ui:colorScheme; the value is the full URI, so match its …#LightColorScheme - suffix with $=). If the attribute is absent (RDF not yet resolved, or no - value), the system preference wins. Dark is the base (ia.css :root) so only - the light case needs rules. The primitives/room values mirror ia.css's - [data-theme="light"]; keyed via :has() so it's scriptless and pre-paint. */ - :root:not([data-theme]):has(sol-default[color-scheme$="LightColorScheme"]) { - color-scheme: light; - --ia-lift: #ffffff; --ia-hover: #1b1712; --ia-sink: #000000; - --ia-base: #efe9dd; --ia-ink: #2a2620; --ia-accent: #b5651d; --ia-on-accent: #ffffff; - --tab-music: #a8581a; --tab-movies: #1c7283; --tab-news: #2f7d4f; --tab-images: #7d4f9e; --tab-on: #ffffff; - --tab-workspaces: #4348a8; --tab-resources: #2f5f9e; --tab-devtools: #a83a3a; - } - :root:not([data-theme]):has(sol-default[color-scheme$="LightColorScheme"]) .ia-player-app.media-audio { --ia-base:#f7f1e4; --ia-ink:#2c2317; --ia-accent:#a8581a; --ia-on-accent:#fff; } - :root:not([data-theme]):has(sol-default[color-scheme$="LightColorScheme"]) .ia-player-app.media-video { --ia-base:#e8eff2; --ia-ink:#152029; --ia-accent:#1c7283; --ia-on-accent:#fff; } - @media (prefers-color-scheme: light) { - :root:not([data-theme]):not(:has(sol-default[color-scheme])) { - color-scheme: light; - --ia-lift: #ffffff; --ia-hover: #1b1712; --ia-sink: #000000; - --ia-base: #efe9dd; --ia-ink: #2a2620; --ia-accent: #b5651d; --ia-on-accent: #ffffff; - --tab-music: #a8581a; --tab-movies: #1c7283; --tab-news: #2f7d4f; --tab-images: #7d4f9e; --tab-on: #ffffff; - --tab-workspaces: #4348a8; --tab-resources: #2f5f9e; --tab-devtools: #a83a3a; - } - :root:not([data-theme]):not(:has(sol-default[color-scheme])) .ia-player-app.media-audio { --ia-base:#f7f1e4; --ia-ink:#2c2317; --ia-accent:#a8581a; --ia-on-accent:#fff; } - :root:not([data-theme]):not(:has(sol-default[color-scheme])) .ia-player-app.media-video { --ia-base:#e8eff2; --ia-ink:#152029; --ia-accent:#1c7283; --ia-on-accent:#fff; } - } - - /* ── Text-size DEFAULT — when there's no explicit ──── - Read off (RDF-derived from ui:fontSize; the - value is the full URI, so match its …#SmallFont / …#LargeFont suffix with - $=), default medium. Drives the root rem size (mirrors ia.css - :root[data-fontsize]) and the per-panel --font-size (mirrors the - [data-fontsize] #panel rules further down). */ - :root:not([data-fontsize]):has(sol-default[font-size$="SmallFont"]) { font-size: 16px; } - :root:not([data-fontsize]):has(sol-default[font-size$="LargeFont"]) { font-size: 24px; } - :root:not([data-fontsize]):has(sol-default[font-size$="MediumFont"]), - :root:not([data-fontsize]):not(:has(sol-default[font-size])) { font-size: 20px; } - :root:not([data-fontsize]):has(sol-default[font-size$="SmallFont"]) :is(#panel-news, #panel-images) { --font-size: 16px; } - :root:not([data-fontsize]):has(sol-default[font-size$="LargeFont"]) :is(#panel-news, #panel-images) { --font-size: 24px; } - :root:not([data-fontsize]):has(sol-default[font-size$="MediumFont"]) :is(#panel-news, #panel-images), - :root:not([data-fontsize]):not(:has(sol-default[font-size])) :is(#panel-news, #panel-images) { --font-size: 20px; } - html, body { height: 100%; margin: 0; } - body { - display: flex; flex-direction: column; - position: relative; - background: var(--ia-bg, #121317); - font-family: var(--ia-font-body, system-ui, sans-serif); - } - /* Bridge the generic swc design tokens (consumed by sol-modal etc. mounted - on ) onto the omp --ia-* theme so the modals follow light/dark and - the app font. --ia-* already switch per data-theme, so one bridge covers both. */ - body { - --surface: var(--ia-bg-elev); --surface-2: var(--ia-bg-elev-2); - --text: var(--ia-text); --text-muted: var(--ia-text-muted); - --text-faint: var(--ia-text-muted); - --border: var(--ia-border); --border-soft: var(--ia-border); - --hover: var(--ia-bg-btn); --accent: var(--ia-accent); - --font-body: var(--ia-font-body, system-ui, sans-serif); - } - /* Controls float over the right of the bar so they share the - tab row (the bar is full-width with empty space on the right). */ - .omp-chrome-bar { - position: absolute; top: 0; right: 12px; z-index: 20; - height: 50px; - display: flex; align-items: center; gap: 8px; - background: transparent; border: none; - } - - /* bar styled as the per-room library tabs (data-tab-id from RDF). */ - #dk-tabs > .sol-tabs-bar { - align-items: flex-end; gap: 5px; padding: 8px 10px 0; - background: var(--ia-bg-elev, #181a1f); - border-bottom: 1px solid var(--ia-border, #2a2d33); - /* Reflow, don't clip: at narrow window widths the row WRAPS so every - tab and the actions row stay reachable (sol-tabs' default is a - single overflow-x:auto line, which cuts them off past the edge). */ - flex-wrap: wrap; - overflow-x: visible; overflow-y: visible; - } - #dk-tabs > .sol-tabs-bar > button { - font-family: system-ui, sans-serif; - font-size: 0.98rem; font-weight: 600; letter-spacing: 0.01em; - line-height: 1.2; - color: var(--ia-text-muted, #9aa); - background: var(--ia-bg-btn, #26282e); - border: 1px solid var(--ia-border-btn, #34373d); border-bottom: none; - border-radius: 9px 9px 0 0; padding: 8px 20px; margin-bottom: 0; - transition: color .15s ease, background .15s ease; - } - #dk-tabs > .sol-tabs-bar > button:hover { color: var(--ia-text-strong, #fff); } - #dk-tabs > .sol-tabs-bar > button[data-tab-id="panel-music"].active { background: var(--tab-music); border-color: var(--tab-music); color: var(--tab-on); } - #dk-tabs > .sol-tabs-bar > button[data-tab-id="panel-movies"].active { background: var(--tab-movies); border-color: var(--tab-movies); color: var(--tab-on); } - #dk-tabs > .sol-tabs-bar > button[data-tab-id="panel-news"].active { background: var(--tab-news); border-color: var(--tab-news); color: var(--tab-on); } - #dk-tabs > .sol-tabs-bar > button[data-tab-id="panel-images"].active { background: var(--tab-images); border-color: var(--tab-images); color: var(--tab-on); } - #dk-tabs > .sol-tabs-bar > button[data-tab-id="panel-podz"].active { background: var(--tab-workspaces); border-color: var(--tab-workspaces); color: var(--tab-on); } - #dk-tabs > .sol-tabs-bar > button[data-tab-id="panel-dev-tools"].active { background: var(--tab-devtools); border-color: var(--tab-devtools); color: var(--tab-on); } - #dk-tabs > .sol-tabs-bar > button[data-tab-id="Favourites"].active { background: var(--tab-fav, #e6b800); border-color: var(--tab-fav, #e6b800); color: var(--tab-on); } - /* A submenu tab is a launcher (sol-tabs - _buildSubmenuDropdown), not a plain -

- - - -HTML - -# renderer.js (no Node APIs) -cat > renderer.js <<'JS' -const btn = document.getElementById('launchBtn'); -const status = document.getElementById('status'); - -btn.addEventListener('click', async () => { - status.textContent = 'Launching...'; - try { - const res = await window.api.launchEmacs(); - status.textContent = res.success ? 'Emacs launched.' : 'Error: ' + (res.message || 'unknown'); - } catch (e) { - status.textContent = 'IPC error: ' + e; - } -}); -JS - -# Create a basic README -cat > README.md < build/readme.txt < -# -# The LAST argument is the target: -# /dk-pod/… or dk-pod/… or ./dk-pod/… → resolved against DK_BASE -# http(s)://… → used as-is -# DK_BASE defaults to http://localhost:8000 (the routed origin — the real path, -# router → CSS). Set DK_BASE=http://localhost:8010 to hit the CSS pod server -# directly. Everything else is passed straight through to curl, so all of curl's -# flags work (-X, -H, -d, --data-binary @file, -i, -I, -o, …). -# -# Examples: -# dk-curl /dk-pod/profile/card -# dk-curl -i /dk-pod/dk/data/data-kitchen-settings.ttl -# dk-curl -X PUT -H 'content-type: text/turtle' --data-binary @note.ttl /dk-pod/dk/scratch/note.ttl -# echo ' .' | dk-curl -X PUT -H 'content-type: text/turtle' --data-binary @- /dk-pod/x.ttl -# dk-curl -X DELETE /dk-pod/dk/scratch/note.ttl -# DK_BASE=http://localhost:8010 dk-curl /dk-pod/dk/data/data-kitchen-settings.ttl -set -euo pipefail - -tokfile="${XDG_CONFIG_HOME:-$HOME/.config}/data-kitchen/gate-token" -tok=$(cat "$tokfile" 2>/dev/null) || { - echo "dk-curl: no gate token at $tokfile" >&2 - echo " (run the dk app once so it generates one, or set DK_BASE to an ungated server)" >&2 - exit 1 -} - -base="${DK_BASE:-http://localhost:8000}" -base="${base%/}" - -if [ "$#" -eq 0 ]; then - echo "usage: dk-curl [curl options...] " >&2 - echo " e.g. dk-curl /dk-pod/profile/card" >&2 - echo " dk-curl -X PUT -H 'content-type: text/turtle' --data-binary @f.ttl /dk-pod/x.ttl" >&2 - exit 2 -fi - -# Resolve the LAST argument when it's a pod-relative path; leave full URLs, flags -# and other args untouched (so e.g. `-o /tmp/out` is never mangled). -args=("$@") -last=$(( $# - 1 )) -target="${args[$last]}" -case "$target" in - http://*|https://*) ;; # full URL — as-is - /*) target="$base$target" ;; # /dk-pod/… - ./dk-pod/*) target="$base/${target#./}" ;; # ./dk-pod/… - dk-pod/*) target="$base/$target" ;; # dk-pod/… - *) ;; # a flag or bare word — leave for curl -esac -args[$last]="$target" - -exec curl -H "x-dk-token: $tok" "${args[@]}" diff --git a/build/readme.txt b/build/readme.txt deleted file mode 100644 index 5bf78c7..0000000 --- a/build/readme.txt +++ /dev/null @@ -1,4 +0,0 @@ -Place platform icons here: - - icon.png (linux) - - icon.ico (windows) - - icon.icns (mac) diff --git a/configs/css-config.json b/configs/css-config.json new file mode 100644 index 0000000..bd6d856 --- /dev/null +++ b/configs/css-config.json @@ -0,0 +1,112 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^3.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server-metadata-extender/^3.0.0/components/context.jsonld" + ], + "import": [ + "files-scs:config/app/main/default.json", + "files-scs:config/app/init/initialize-root.json", + "files-scs:config/app/setup/disabled.json", + "files-scs:config/app/variables/default.json", + "files-scs:config/http/handler/default.json", + "files-scs:config/http/middleware/websockets.json", + "files-scs:config/http/server-factory/websockets.json", + "files-scs:config/http/static/default.json", + "files-scs:config/identity/access/public.json", + "files-scs:config/identity/email/default.json", + "files-scs:config/identity/handler/default.json", + "files-scs:config/identity/ownership/token.json", + "files-scs:config/identity/pod/static.json", + "files-scs:config/identity/registration/enabled.json", + "files-scs:config/ldp/authentication/dpop-bearer.json", + "files-scs:config/ldp/authorization/webacl.json", + "files-scs:config/ldp/handler/default.json", + "files-scs:config/ldp/metadata-parser/default.json", + "files-scs:config/ldp/metadata-writer/default.json", + "files-scs:config/ldp/modes/default.json", + "files-scs:config/storage/backend/data-accessors/file.json", + "files-scs:config/storage/key-value/resource-store.json", + "files-scs:config/storage/middleware/default.json", + "files-scs:config/util/auxiliary/acl.json", + "files-scs:config/util/identifiers/suffix.json", + + "files-scs:config/util/logging/winston.json", + "files-scs:config/util/representation-conversion/default.json", + "files-scs:config/util/resource-locker/memory.json", + "files-scs:config/util/variables/default.json" + ], + "@graph": [ + { + "comment": [ + "A filesystem-based server with Databrowser as UI.", + "Derived from config/file-no-setup.json" + ] + }, + + { + "comment": "Serve Databrowser as default representation", + "@id": "urn:solid-server:default:DefaultUiConverter", + "@type": "ConstantConverter", + "contentType": "text/html", + "filePath": "./assets/kitchen.html", + "options_container": true, + "options_document": true, + "options_minQuality": 1, + "options_disabledMediaRanges": [ + "image/*", + "application/pdf" + ] + }, + + { + "comment": "Serve Mashlib static files.", + "@id": "urn:solid-server:default:StaticAssetHandler", + "@type": "StaticAssetHandler", + "assets": [ + { + "StaticAssetHandler:_assets_key": "/mash.css", + "StaticAssetHandler:_assets_value": "./node_modules/mashlib/dist/mash.css" + }, + { + "StaticAssetHandler:_assets_key": "/mashlib.min.js", + "StaticAssetHandler:_assets_value": "./node_modules/mashlib/dist/mashlib.min.js" + }, + { + "StaticAssetHandler:_assets_key": "/mashlib.min.js.map", + "StaticAssetHandler:_assets_value": "./node_modules/mashlib/dist/mashlib.min.js.map" + } + ] + }, + { + "comment": "Serve static files. (DATA-KITCHEN)", + "@id": "urn:solid-server:default:StaticAssetHandler", + "@type": "StaticAssetHandler", + "assets": [ + { + "StaticAssetHandler:_assets_key": "/common/", + "StaticAssetHandler:_assets_value": "./assets" + } + ] + }, + + { + "comment": "A default store setup with a file system backend.", + "@id": "urn:solid-server:default:ResourceStore_Backend", + "@type": "DataAccessorBasedStore", + "identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }, + "auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" }, + "accessor": { "@id": "urn:solid-server:default:MetadataExtender" } + }, + + { + "comment": "Extend files with extra metadata.", + "@id": "urn:solid-server:default:MetadataExtender", + "@type": "MetadataExtender", + "options_ianaType": true, + "options_statSize": true, + "options_dctModified": true, + "options_statMtime": true, + "source": { "@id": "urn:solid-server:default:FileDataAccessor" } + } + ] +} diff --git a/configs/css-start.sh b/configs/css-start.sh new file mode 100755 index 0000000..9b702fc --- /dev/null +++ b/configs/css-start.sh @@ -0,0 +1 @@ +node ./node_modules/@solid/community-server/bin/server -c ./configs/css-config.json -f $1 -p $2 diff --git a/configs/css-stop.sh b/configs/css-stop.sh new file mode 100755 index 0000000..7940703 --- /dev/null +++ b/configs/css-stop.sh @@ -0,0 +1 @@ +kill $(ps aux | grep 'community-server' | awk '{print $2}') diff --git a/configs/electron-config.js b/configs/electron-config.js new file mode 100644 index 0000000..6ef3797 --- /dev/null +++ b/configs/electron-config.js @@ -0,0 +1,208 @@ +const jsonfile = require('jsonfile'); +const path = require('path') + +module.exports.getUserConfig = async function(configFile){ + try{ + cfg = await jsonfile.readFileSync( configFile ) + } + catch(e){ + console.log("Could not load config file kitchen.json: ",e); + process.exit(); + } + cfg = cfg || {} + cfg.width ||= 1024 + cfg.height ||= 768 + if(cfg.left === "center") cfg.left = null; + if(cfg.top === "center") cfg.top = null; + cfg.rootFilePath = cfg.rootFilePath || "../"; + cfg.port = cfg.port || 3000; + let parent = path.join(__dirname,"../"); + if(!cfg.rootFilePath.startsWith("/")) cfg.rootFilePath = path.join(parent,cfg.rootFilePath); + + return cfg; +} + +module.exports.mungeMenu = function(cfg,mainWindow){ + let newBM = [] + if( cfg.bookmarks ){ + for(var b=0;b { + mainWindow.webContents.send( 'gotoSubject', url ) + } + }) + } + } + let myMenu = getMenu(cfg,mainWindow) + for(var i=0;i { mainWindow.webContents.send( + 'gotoSubject',cfg.startPage + )} + }, + { label: 'File Manager', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject', 'none', 'fileManager' + ) + } + }, + { label: 'SPARQL Query', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject','none','queryForm' + ) + } + }, + { label:'Exit', + click() { closeAll() } + }, + ] + }, + // { role: 'toolsMenu' } + { + label: 'Tools', + submenu: [ + { label: 'Reload Data Kitchen', + accelerator: "CmdOrCtrl+R", + click() { + mainWindow.reload(); + mainWindow.loadURL( + cfg.startPage + ) + } + }, + { label: 'Clear Cache', + click: async () => { + // alert('clearing cache ...') + mainWindow.webContents.session.clearCache(() => { + // alert('cache is cleared') + mainWindow.webContents.session.clearStorageData() + }); + } + }, + { label: 'Install Kitchen Updates', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'https://jeff-zucker.github.io/data-kitchen.html' + ) + } + }, + { role: 'toggledevtools' }, + ] + }, + // { role: 'bookmarksMenu' } + { + label: 'Bookmarks', + submenu: [ + { label: 'My local files', + click: async () => { mainWindow.loadURL( cfg.startPage ) } + }, + { label: 'My solid.community pod', + click: async () => { mainWindow.loadURL( + 'https://jeff-zucker.solidcommunity.net/public/' + )} + }, + { label: 'My inrupt.net pod', + click: async () => { mainWindow.loadURL( + 'https://pod.inrupt.com/jeff-zucker/public/' + )} + }, + ] + }, + // { role: 'help' } + { + label: "Help", + submenu: [ + { + label: 'Welcome to the Kitchen', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject', 'assets/welcome.html' + ) + } + }, + { + enabled : false, + label: 'DataBrowser User Guide', + click: async () => { + mainWindow.webContents.send( + 'gotoSubject','https://github.com/SolidoS/userguide', 'webBrowser' ) + } + }, + { + label: 'Report Issues on this fork of the kitchen', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject','https://github.com/jeff-zucker/data-kitchen/issues', 'webBrowser' ) + } + }, + { + label: 'About Solid', + submenu: [ + { label: 'Solid : solidproject.org', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject', + 'https://solidproject.org/', + 'webBrowser' + ) + } + }, + { label: 'This Week in Solid', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject', + 'https://solidproject.org/this-week-in-solid', + 'webBrowser' + ) + } + }, + { label: 'Solid Gitter Chat', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject', + 'https://gitter.im/solid/chat', + 'webBrowser' + ) + } + }, + { label: 'Solid Forum', + enabled : false, + click: async () => { + mainWindow.webContents.send( + 'gotoSubject', + 'https://forum.solidproject.org/latest', + 'webBrowser' + ) + } + }, + ] + } + ] + } +] +} diff --git a/configs/electron-main.js b/configs/electron-main.js new file mode 100644 index 0000000..fc9a0f9 --- /dev/null +++ b/configs/electron-main.js @@ -0,0 +1,173 @@ +const {app, BrowserWindow, BrowserView, Menu} = require('electron'); +const fs = require('fs/promises') +const path = require('path') +const electronReload = require('electron-reload') +const jsonfile = require('jsonfile'); +const {getUserConfig,mungeMenu} = require('./electron-config.js'); +let mainWindow; +var cfg,css; + +let installDir = path.join(__dirname,"../"); +let tmplDir = path.join(installDir,"templates"); +let assetsDir = path.join(installDir,"assets"); + +// during development, live reload on change of any file +electronReload( installDir,{forceHardReset:true} ); + +async function getConfig(){ + let kFrom = path.join(tmplDir,'kitchen.json.tmpl') + let kTo = path.join(installDir,'kitchen.json') + try { + await fs.copyFile( kFrom, kTo ); + } + catch(e){ + console.log("Could not create kitchen.js from "+js,e); + process.exit(); + } + cfg = await getUserConfig( path.join(installDir,"kitchen.json") ) || {}; + cfg.port ||= 3000; + cfg.startPage = `http://localhost:${cfg.port}/`; + cfg.icon = path.join(installDir,"assets/favicon.ico"); + cfg.webPreferences = { + preload: path.join(installDir, 'configs/electron-preload.js'), + nativeWindowOpen: true, + nodeIntegration: false, + contextIsolation: true, + } + return cfg; +} +getConfig().then( async (cfg)=>{ + await prepRootFilePath(cfg); + spawnCSS(cfg); +}); + +async function prepRootFilePath(cfg){ + let root = cfg.rootFilePath; + let port = cfg.port; + let rootProfile = path.join(root,"profile") ; + + let aclFrom = path.join(tmplDir,"acl.tmpl"); + let aclTo = path.join(root,".acl"); + let metaFrom = path.join(tmplDir,"meta.tmpl"); + let metaTo = path.join(root,".meta"); + let jsFrom = path.join(tmplDir,'kitchen.js.tmpl') + let pFrom = path.join(tmplDir,'profile') + let jsTo = path.join(assetsDir,'kitchen.js') + let profileFrom = path.join(tmplDir,"profile/card$.ttl"); + + let settingsFrom = path.join(pFrom,"settings") ; + let settingsTo = path.join(rootProfile,"settings") ; + + // copy munged kitchen.js templates to assets + try { + let jsString = await fs.readFile( jsFrom,{encoding:'utf8'} ); + jsString = "var port ="+cfg.port+";\n"+jsString ; + await fs.writeFile( jsTo, jsString ); + } + catch(e){ + console.log("Could not create kitchen.js from "+js,e); + process.exit(); + } + // copy acl & meta templates to rootFilePath + // and kitchen.json to installDir + try { + await fs.copyFile( aclFrom, aclTo ); + await fs.copyFile( metaFrom, metaTo ); + await fs.mkdir( rootProfile ); + } + catch(e){} + try { + await fs.mkdir( settingsTo ); + } + catch(e){} + try { + if( await fs.access(path.join(rootProfile,"card$.ttl")) ) {} + } + catch(e){ + await fs.copyFile( profileFrom, rootProfile+"/card$.ttl" ); + } + try { + if( await fs.access( path.join(settingsTo,"prefs.ttl") ) ) {} + } + catch(e){ + await fs.copyFile( path.join(settingsFrom,'prefs.ttl'), path.join(settingsTo,"prefs.ttl" ) ); + await fs.copyFile( path.join(settingsFrom,'privateTypeIndex.ttl'), path.join(settingsTo,"privateTypeIndex.ttl" ) ); + await fs.copyFile( path.join(settingsFrom,'publicTypeIndex.ttl'), path.join(settingsTo,"publicTypeIndex.ttl" ) ); + } +} + + +/* ELECTRON STUFF +*/ +app.on('ready', async ()=>{ +/* + actions that would happen here, are, instead, triggered once CSS is loaded +*/ +}) +app.on('close', function (e) { + closeAll(); +}) +app.on('will-quit', function (e) { + closeAll(); +}) +process.on('uncaughtException', function (error) { + closeAll(); +}); + +function closeAll(){ + if (process.platform !== 'darwin') app.quit() +} +app.on('activate', async function () { + if (mainWindow === null) createWindow(cfg); +}) +async function createWindow (cfg,css) { + console.clear(); + console.log(`\nServing Data-Kitchen from ${cfg.rootFilePath} using port ${cfg.port}.\n`) + mainWindow = new BrowserWindow(cfg) + Menu.setApplicationMenu( + Menu.buildFromTemplate( mungeMenu(cfg,mainWindow) ) + ) + // mainWindow.removeMenu(); // NO TOP MENU + if(cfg.devTools==1) mainWindow.webContents.openDevTools() /* DEV-TOOLS */ + mainWindow.loadURL(cfg.startPage) +/* + mainwindow.webContents.session.clearStorageData([], function (data) { + console.log(data); + }) +*/ + mainWindow.on('closed', function () { mainWindow = null }) +} + +/* START CSS IN A SEPARATE PROCESS, THEN START ELECTRON + -- user starts both with npm start and doesn't need to close CSS separately +*/ +function spawnCSS(cfg){ + const cssStartPath = path.join(__dirname,"css-start.sh"); + var serverStarted=false; + const { spawn } = require("child_process"); + css = spawn(cssStartPath,[cfg.rootFilePath,cfg.port]); + css.stdout.on("data", data => { + if( !serverStarted && data.toString().match(/Starting server/) ){ + serverStarted = true; + createWindow(cfg,css); + } + console.log(`stdout: ${data}`); + }); + css.stderr.on("data", data => { + console.log(`stderr: ${data}`); + }); + css.on('error', (error) => { + // console.log(`error: ${error.message}`); + css.stdin.pause(); + css.kill(); + }); + css.on("close", code => { + css.stdin.pause(); + css.kill(); + // console.log(`error: ${code}`); + }); +} +/* END OF CSS STUFF */ + + +// ENDS! diff --git a/configs/electron-preload.js b/configs/electron-preload.js new file mode 100644 index 0000000..0b188a2 --- /dev/null +++ b/configs/electron-preload.js @@ -0,0 +1,12 @@ +const { contextBridge, ipcRenderer } = require('electron') + +contextBridge.exposeInMainWorld( + 'ipcRenderer', + { + init: (go) => { + ipcRenderer.on('gotoSubject',(event,uri)=>{ + go(uri); + }); + } + } +) diff --git a/dk.manifest.json b/dk.manifest.json deleted file mode 100644 index 74fbe33..0000000 --- a/dk.manifest.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@context": "https://jeff-zucker.github.io/component-interop/context.jsonld", - "@id": "", - "@type": "Manifest", - "name": "data-kitchen", - "objects": { - "provides": { - "webid": { "respondTo": "sol-login", "sendValue": "detail.webId" } - } - }, - "components": { - "ia-player": { - "label": "Internet Archive player", - "shape": "./shapes/music.shacl", - "help": "./plugins/ia-player/assets/ia-help.html" - }, - "omp-images": { "label": "Wikimedia images" }, - "dk-podz": { "label": "Data Kitchen Pod Browser" }, - "dk-solidos": { "label": "SolidOS data browser" }, - "dk-calendar-popout": { "label": "Calendar" }, - "dk-dokieli": { "label": "dokieli" } - }, - "stages": { - "local": { - "components": { - "ia-player": "/plugins/ia-player/dist/ia-player.esm.js" - }, - "shared-modules": {} - }, - "cdn": { - "components": { - "ia-player": "/plugins/ia-player/dist/ia-player.esm.js" - }, - "shared-modules": {} - } - } -} diff --git a/dokieli.manifest.json b/dokieli.manifest.json deleted file mode 100644 index 9118576..0000000 --- a/dokieli.manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@context": "https://jeff-zucker.github.io/component-interop/context.jsonld", - "@id": "", - "@type": "Manifest", - "name": "dokieli", - "objects": { - "consumes": { - "webid": { - "call": "adoptDokieliUser", - "from": "data-kitchen", - "module": "/dk-pod/dk/plugins/solidos/dokieli-adapter.js" - }, - "auth": { - "call": "adoptDokieliFetch", - "from": "sol-components", - "module": "/dk-pod/dk/plugins/solidos/dokieli-adapter.js" - } - } - } -} diff --git a/electron-config/README.md b/electron-config/README.md deleted file mode 100644 index f905054..0000000 --- a/electron-config/README.md +++ /dev/null @@ -1,207 +0,0 @@ -# Data-Kitchen desktop shell — native overlays & external content - -This folder is the Electron layer that wraps the Data-Kitchen web app in a -desktop window. Most of it is plumbing (config, the bundled servers, seeding a -pod). The part that surprises people — and that has no obvious home in the web -code — is how **external web content is shown using native Electron views layered -over the app**, instead of in HTML iframes. This README documents that -subsystem. - -> TL;DR: external sites (duck.ai, Bluesky, feed articles, search results) are -> NOT iframed. They render in native `WebContentsView`s painted *on top of* the -> app, positioned to line up with a region of the page. Because they're native, -> they don't obey the app's z-index/DOM, so their lifecycle has to be driven -> explicitly. Getting that lifecycle wrong is why a reader can "take over" the -> window. - -## Why native views instead of iframes - -Two reasons: - -1. **Framing-blockers.** Many sites (`bsky.app`, most large apps) send - `X-Frame-Options` / `frame-ancestors` headers that refuse to load in an - iframe. A native `WebContentsView` is a real browser tab — nothing to refuse. -2. **Security isolation.** External content must never reach this machine's local - servers (the bundled CSS pod, the no-auth CORS proxy). Native views get their - own Electron `session` (cookie jar + request filter), which lets us cancel - any request from external content to a loopback host. See **Sessions** below. - -The cost: a native view is **not** part of the page's DOM. It always paints above -the app's HTML, ignores the app's z-index, and has no automatic tie to whatever -HTML element it's visually "inside." Everything about showing/hiding/positioning -it is done by hand over IPC. - -## The window model - -``` -BaseWindow (the OS window; main.cjs) -└─ contentView (Electron's root child-view container) - ├─ appView WebContentsView ← the Data-Kitchen web app, fills the window - ├─ pane WebContentsView ← (optional) shadows an external - - diff --git a/plugins/solidos/dk-solidos.js b/plugins/solidos/dk-solidos.js deleted file mode 100644 index c26d494..0000000 --- a/plugins/solidos/dk-solidos.js +++ /dev/null @@ -1,102 +0,0 @@ -// dk-solidos — thin host for the upstream SolidOS (mashlib) panes. -// -// It embeds the isolating `sol-solidos-host.html` iframe, which runs the upstream -// against mashlib with mash.css loaded INSIDE the iframe — so SolidOS -// renders exactly as upstream intends while its global CSS cannot leak into dk's -// chrome. dk shares its authenticated fetch into the iframe via component-interop -// (`adoptAuth` → the iframe broker's adoptedFetch → getAuthFetch → the panes), so -// reads/writes follow whatever pod dk is logged into (local or remote). -// -// The pod subject to display comes from the `source` attribute (a resource URI) or -// a subclass `_landingSubject()`, defaulting to the pod root. This replaces the old -// two-pane (sol-pod sidebar + hand-rolled mashlib glue) implementation. - -const HOST_PAGE = 'dk-pod/dk/plugins/solidos/sol-solidos-host.html'; - -class DkSolidos extends HTMLElement { - static get observedAttributes() { return ['source']; } - - connectedCallback() { - if (this._wired) return; - this._wired = true; - this.style.display = 'block'; - if (!this.style.height) this.style.height = '100%'; - - const iframe = document.createElement('iframe'); - iframe.className = 'dk-solidos-frame'; - iframe.style.cssText = 'border:0;width:100%;height:100%;display:block'; - iframe.src = `${HOST_PAGE}?source=${encodeURIComponent(this._subject())}`; - this._iframe = iframe; - this.appendChild(iframe); - - iframe.addEventListener('load', () => this._shareAuth()); - this._onAuthChange = () => this._shareAuth(); - document.addEventListener('sol-login', this._onAuthChange); - document.addEventListener('sol-logout', this._onAuthChange); - - this._mountExtras(iframe); - } - - disconnectedCallback() { - document.removeEventListener('sol-login', this._onAuthChange); - document.removeEventListener('sol-logout', this._onAuthChange); - } - - attributeChangedCallback(name, oldV, newV) { - if (name === 'source' && oldV !== newV && this._iframe) { - const win = this._iframe.contentWindow; - if (win && typeof win.solSetSource === 'function') win.solSetSource(this._subject()); - } - } - - // The subject URI: an explicit `source` (absolute/rooted), else a subclass landing - // subject, else the local pod root. (The old menu wiring used `source` for a - // template path — anything that isn't a real URI falls through to the default.) - _subject() { - const src = this.getAttribute('source') || ''; - if (/^https?:\/\//.test(src)) return src; // absolute URI - if (src.startsWith('/')) return location.origin + src; // pod-rooted path → URI - // Base SolidOS opens at the local server root; subclasses (dk-dokieli) pin their own. - return this._landingSubject() || `${location.origin}/`; - } - - // Hand dk's authenticated fetch to the iframe (component-interop auth channel). - // Retries briefly because the iframe's adoptAuth is published as its modules load. - _shareAuth() { - const win = this._iframe && this._iframe.contentWindow; - if (!win) return; - const base = window.dkFetch || ((u, o) => fetch(u, o)); - // Containers (URLs whose path ends in '/') must be fetched as RDF. The local - // server content-negotiates '/' to the app's index.html (a wormhole) under - // Accept: text/html, and other folders to the Pivot HTML view — so force - // Accept: text/turtle for container requests and SolidOS gets the container, - // rendering its own folder pane. - const dkFetch = (input, init) => { - let u = ''; - try { u = typeof input === 'string' ? input : (input && (input.url || input.href)) || ''; } catch (_) {} - const path = u.split('#')[0].split('?')[0]; - if (path.endsWith('/')) { - const h = new Headers((init && init.headers) || {}); - h.set('Accept', 'text/turtle'); - return base(input, { ...(init || {}), headers: h }); - } - return base(input, init); - }; - const webId = `${location.origin}/dk-pod/profile/card#me`; - let tries = 0; - const apply = () => { - try { if (typeof win.adoptAuth === 'function') { win.adoptAuth(dkFetch, { webId }); return; } } - catch (_) { /* cross-origin/odd state — give up */ return; } - if (++tries < 40) setTimeout(apply, 50); - }; - apply(); - } - - // Subclass hooks (dk-dokieli pins a landing folder; base has neither). - _landingSubject() { return null; } - _mountExtras(/* iframe */) {} -} - -customElements.define('dk-solidos', DkSolidos); - -export { DkSolidos }; diff --git a/plugins/solidos/dokieli-adapter.js b/plugins/solidos/dokieli-adapter.js deleted file mode 100644 index 6f1cc10..0000000 --- a/plugins/solidos/dokieli-adapter.js +++ /dev/null @@ -1,84 +0,0 @@ -// dokieli-adapter.js — component-interop CONSUMER: bridge dk's owner identity AND -// dk's live authenticated fetch into the embedded dokieli editor, so dokieli -// treats you as logged-in (like SolidOS does) without its own OIDC prompt, AND -// its reads/writes follow whatever pod dk is logged into — local OR remote. -// -// The contract is declarative (see the manifests): -// • dk.manifest.json provides { webid } — from dk-owner-session's -// `sol-login` event (detail.webId = current owner/login). -// • sol-components manifest provides { auth } — the live solFetch (routes through -// AuthManager.fetchFor → the session covering each URL). -// • dokieli.manifest.json consumes { webid → adoptDokieliUser, -// auth → adoptDokieliFetch }, module: this file. -// • index.html data-objects="webid:data-kitchen auth:sol-components". -// -// dokieli (loaded from the dokie.li CDN inside a same-origin doc iframe) keeps its -// current user in `window.DO.C.User.IRI` and uses the frame's global `fetch` for -// reads/writes; its init only prompts for login when the IRI is empty. So we -// pre-seed the owner WebID (skip the prompt) AND install dk's authenticated fetch -// as that frame's `window.fetch` — the same seam SolidOS uses (installAuthFetch in -// solidos-host.html). solFetch is origin-aware: local-pod writes ride the gate, -// remote writes carry the logged-in session's auth. -// -// The dokieli doc iframe is created LATE (when you open a .html) and NESTED -// (main page → solidos-host iframe → doc iframe), all same-origin, so we sweep -// the frame tree and apply to any dokieli frame. The IRI is set only when empty so -// a genuine in-dokieli login is never clobbered; the fetch is (re)applied so it -// always tracks dk's current session. - -let ownerWebId = null; -let liveFetch = null; -let timer = null; - -function applyToWindow(win) { - let DO; - try { DO = win.DO; } catch (_) { return; } // cross-origin frame — skip - if (!DO || !DO.C || !DO.C.User) return; // not a dokieli frame yet - try { - if (!DO.C.User.IRI && ownerWebId) DO.C.User.IRI = ownerWebId; // adopt only when unset - } catch (_) { /* frozen/odd state — ignore */ } - try { - // Install dk's authed fetch as this dokieli frame's global fetch so its - // reads/writes follow the current login. liveFetch is a stable ref, so this - // assigns once (and re-asserts if dokieli swapped fetch back). - if (liveFetch && win.fetch !== liveFetch) win.fetch = liveFetch; - } catch (_) { /* frozen/odd state — ignore */ } -} - -function sweep(win) { - applyToWindow(win); - let frames; - try { frames = win.frames; } catch (_) { return; } // cross-origin — can't descend - for (let i = 0, n = frames ? frames.length : 0; i < n; i++) { - try { sweep(frames[i]); } catch (_) {} - } -} - -function startSweeping() { - sweep(window); - // dokieli iframes open later (on navigating to a .html); keep applying so each - // new one is seeded before its init reads User.IRI / makes its first fetch. - // ~150ms aims to land in the gap between dokieli publishing window.DO and its - // load-time auth init. - if (!timer) timer = setInterval(() => sweep(window), 150); -} - -export function adoptDokieliUser(webId) { - if (!webId) return; - ownerWebId = webId; - startSweeping(); -} - -export function adoptDokieliFetch(fetchFn) { - if (typeof fetchFn !== 'function') return; - // Wrap so liveFetch is a single stable reference across re-applies, and so the - // dokieli frame (a separate realm) calls our fetch with the expected signature. - liveFetch = (input, init) => fetchFn(input, init); - startSweeping(); -} - -const ci = (typeof window !== 'undefined') && (window.ComponentInterop || window.SolidWebComponents); -if (ci && typeof ci.registerConsumer === 'function') { - ci.registerConsumer('adoptDokieliUser', adoptDokieliUser); - ci.registerConsumer('adoptDokieliFetch', adoptDokieliFetch); -} diff --git a/plugins/solidos/help.html b/plugins/solidos/help.html deleted file mode 100644 index 42c3e4d..0000000 --- a/plugins/solidos/help.html +++ /dev/null @@ -1,61 +0,0 @@ - diff --git a/plugins/solidos/manifest.jsonld b/plugins/solidos/manifest.jsonld deleted file mode 100644 index 91b959e..0000000 --- a/plugins/solidos/manifest.jsonld +++ /dev/null @@ -1,54 +0,0 @@ -{ - "@context": { - "@version": 1.1, - "ui": "http://www.w3.org/ns/ui#", - "dct": "http://purl.org/dc/terms/", - "schema": "http://schema.org/", - "acl": "http://www.w3.org/ns/auth/acl#", - "Component": "ui:Component", - "Menu": "ui:Menu", - "label": "ui:label", - "name": "ui:name", - "creator": "dct:creator", - "subject": "dct:subject", - "hasPart": { - "@id": "dct:hasPart", - "@type": "@id" - }, - "requires": { - "@id": "dct:requires", - "@type": "@id" - }, - "shape": { - "@id": "dct:conformsTo", - "@type": "@id" - }, - "help": { - "@id": "schema:softwareHelp", - "@type": "@id" - }, - "mode": { - "@id": "acl:mode", - "@type": "@id" - }, - "parts": { - "@id": "ui:parts", - "@container": "@list", - "@type": "@id" - }, - "icon": "ui:icon" - }, - "@id": "", - "@type": "Component", - "label": "SolidOS", - "name": "dk-solidos", - "creator": "SolidOS Team", - "hasPart": [ - "./dk-solidos.html", - "./dk-solidos.js", - "./solidos-host.html" - ], - "help": "./help.html", - "subject": "Tech", - "icon": "https://solidproject.org/assets/img/solid-emblem.svg" -} diff --git a/plugins/solidos/sol-solidos-host.html b/plugins/solidos/sol-solidos-host.html deleted file mode 100644 index 357a4c1..0000000 --- a/plugins/solidos/sol-solidos-host.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - SolidOS host - - - - - - - - - - - - -
- - - - diff --git a/plugins/solidos/solidos-host.html b/plugins/solidos/solidos-host.html deleted file mode 100644 index d4ee909..0000000 --- a/plugins/solidos/solidos-host.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - SolidOS host - - - - - - - - - - - - - - - - - - - - -
-
- -
-
- -
-
- -
-
- - - - diff --git a/plugins/sparql-playground.ttl b/plugins/sparql-playground.ttl deleted file mode 100644 index 4fc66a6..0000000 --- a/plugins/sparql-playground.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "SPARQL Playground" ; - ui:icon "🔮" ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Try out SPARQL queries." ; - dct:publisher "AtomGraph" ; - dct:subject "Tech" . diff --git a/plugins/standard-notes.ttl b/plugins/standard-notes.ttl deleted file mode 100644 index a09a90a..0000000 --- a/plugins/standard-notes.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "Standard Notes" ; - ui:icon ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Open-source encrypted notes." ; - dct:publisher "Standard Notes" ; - dct:subject "Other Services" . diff --git a/plugins/synara-ai.ttl b/plugins/synara-ai.ttl deleted file mode 100644 index 026b290..0000000 --- a/plugins/synara-ai.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "Synara AI" ; - ui:icon ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Privacy-first AI assistant with searchable conversation logs." ; - dct:creator "PrivateDataPod team" ; - dct:subject "Tech" . diff --git a/plugins/time/manifest.jsonld b/plugins/time/manifest.jsonld deleted file mode 100644 index 349230a..0000000 --- a/plugins/time/manifest.jsonld +++ /dev/null @@ -1,52 +0,0 @@ -{ - "@context": { - "@version": 1.1, - "ui": "http://www.w3.org/ns/ui#", - "dct": "http://purl.org/dc/terms/", - "schema": "http://schema.org/", - "acl": "http://www.w3.org/ns/auth/acl#", - "Component": "ui:Component", - "Menu": "ui:Menu", - "label": "ui:label", - "name": "ui:name", - "creator": "dct:creator", - "subject": "dct:subject", - "hasPart": { - "@id": "dct:hasPart", - "@type": "@id" - }, - "requires": { - "@id": "dct:requires", - "@type": "@id" - }, - "shape": { - "@id": "dct:conformsTo", - "@type": "@id" - }, - "help": { - "@id": "schema:softwareHelp", - "@type": "@id" - }, - "mode": { - "@id": "acl:mode", - "@type": "@id" - }, - "parts": { - "@id": "ui:parts", - "@container": "@list", - "@type": "@id" - }, - "icon": "ui:icon" - }, - "@id": "", - "@type": "Component", - "label": "Clock", - "name": "sol-time", - "creator": "Jeff Zucker", - "requires": [ - "./time-settings.ttl" - ], - "shape": "../../node_modules/sol-components/shapes/time-settings.shacl", - "subject": "Information", - "icon": "🕐" -} diff --git a/plugins/time/time-settings.ttl b/plugins/time/time-settings.ttl deleted file mode 100644 index b9d2adf..0000000 --- a/plugins/time/time-settings.ttl +++ /dev/null @@ -1,10 +0,0 @@ -@prefix schema: . -@prefix wd: . -@prefix foaf: . -@prefix : <#> . - -<> a wd:Q1193846 ; - foaf:primaryTopic <#Settings> . - -<#Settings> - schema:timezone "Asia/Kolkata" . diff --git a/plugins/tired-bike.ttl b/plugins/tired-bike.ttl deleted file mode 100644 index 2c5046a..0000000 --- a/plugins/tired-bike.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "Tired Bike" ; - ui:icon ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Community platform for bicycle touring." ; - dct:creator "Open Hospitality Network" ; - dct:subject "Community" . diff --git a/plugins/todopod.ttl b/plugins/todopod.ttl deleted file mode 100644 index 9c82376..0000000 --- a/plugins/todopod.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "TodoPod" ; - ui:icon ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Task lists in encrypted personal storage." ; - dct:creator "Togaware" ; - dct:subject "Productivity" . diff --git a/plugins/tuta.ttl b/plugins/tuta.ttl deleted file mode 100644 index ea6b03f..0000000 --- a/plugins/tuta.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "Tuta" ; - ui:icon ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Open-source encrypted email." ; - dct:publisher "Tuta" ; - dct:subject "Other Services" . diff --git a/plugins/umai.ttl b/plugins/umai.ttl deleted file mode 100644 index 04abe49..0000000 --- a/plugins/umai.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "Umai" ; - ui:icon ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Offline-first recipe manager." ; - dct:creator "Noel De Martin" ; - dct:subject "Information" . diff --git a/plugins/vitalstats.ttl b/plugins/vitalstats.ttl deleted file mode 100644 index 3bba57e..0000000 --- a/plugins/vitalstats.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . - -<> - a ui:Link ; - ui:label "VitalStats" ; - ui:icon ; - ui:href ; - ui:region ui:Tab ; - rdfs:comment "Log blood pressure, weight and other vital signs." ; - dct:publisher "PrivateDataPod" ; - dct:subject "Health" . diff --git a/plugins/weather.ttl b/plugins/weather.ttl deleted file mode 100644 index 2be5592..0000000 --- a/plugins/weather.ttl +++ /dev/null @@ -1,15 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . -@prefix schema: . - -<> - a ui:Component ; - ui:label "Weather" ; - ui:icon "🌤" ; - ui:name "sol-weather" ; - rdfs:comment "Weather widget (location and units in its settings file)." ; - dct:creator "Jeff Zucker" ; - dct:subject "Information" ; - ui:attribute - [ schema:name "source" ; schema:value "./plugins/weather/weather-settings.ttl#Settings" ] . diff --git a/plugins/weather/manifest.jsonld b/plugins/weather/manifest.jsonld deleted file mode 100644 index 9cd20e4..0000000 --- a/plugins/weather/manifest.jsonld +++ /dev/null @@ -1,52 +0,0 @@ -{ - "@context": { - "@version": 1.1, - "ui": "http://www.w3.org/ns/ui#", - "dct": "http://purl.org/dc/terms/", - "schema": "http://schema.org/", - "acl": "http://www.w3.org/ns/auth/acl#", - "Component": "ui:Component", - "Menu": "ui:Menu", - "label": "ui:label", - "name": "ui:name", - "creator": "dct:creator", - "subject": "dct:subject", - "hasPart": { - "@id": "dct:hasPart", - "@type": "@id" - }, - "requires": { - "@id": "dct:requires", - "@type": "@id" - }, - "shape": { - "@id": "dct:conformsTo", - "@type": "@id" - }, - "help": { - "@id": "schema:softwareHelp", - "@type": "@id" - }, - "mode": { - "@id": "acl:mode", - "@type": "@id" - }, - "parts": { - "@id": "ui:parts", - "@container": "@list", - "@type": "@id" - }, - "icon": "ui:icon" - }, - "@id": "", - "@type": "Component", - "label": "Weather", - "name": "sol-weather", - "creator": "Jeff Zucker", - "requires": [ - "./weather-settings.ttl" - ], - "shape": "../../node_modules/sol-components/shapes/weather-settings.shacl", - "subject": "Information", - "icon": "🌤" -} diff --git a/plugins/weather/weather-settings.ttl b/plugins/weather/weather-settings.ttl deleted file mode 100644 index 5b9b68e..0000000 --- a/plugins/weather/weather-settings.ttl +++ /dev/null @@ -1,17 +0,0 @@ -@prefix geo: . -@prefix schema: . -@prefix time: . -@prefix ui: . -@prefix wd: . -@prefix foaf: . -@prefix : <#> . - -<> a wd:Q1193846 ; - foaf:primaryTopic <#Settings> . - -<#Settings> - geo:lat 45.52 ; - geo:long -122.68 ; - schema:addressLocality "Portland, OR" ; - ui:temperatureUnit ui:Both ; - time:hours 12 . diff --git a/plugins/workspaces.ttl b/plugins/workspaces.ttl deleted file mode 100644 index dd0552a..0000000 --- a/plugins/workspaces.ttl +++ /dev/null @@ -1,16 +0,0 @@ -@prefix dct: . -@prefix ui: . -@prefix rdfs: . -@prefix schema: . - -<> - a ui:Component ; - ui:label "Data Kitchen Pod Browser" ; - ui:icon "🗂" ; - ui:name "dk-podz" ; - rdfs:comment "Browse and manage your Solid pods and workspaces." ; - dct:creator "Jeff Zucker" ; - dct:subject "Pods" ; - ui:attribute - [ schema:name "source" ; schema:value "./plugins/podz/dk-podz.html" ] , - [ schema:name "defer" ; schema:value "" ] . diff --git a/pod-template/.acl b/pod-template/.acl deleted file mode 100644 index aac4cf5..0000000 --- a/pod-template/.acl +++ /dev/null @@ -1,25 +0,0 @@ -# Root ACL resource for the agent account -@prefix acl: . -@prefix foaf: . - -# The homepage is readable by the public -<#public> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo <./>; - acl:mode - acl:Read, acl:Write, acl:Control. - -# The owner has full access to every resource in their pod. -# Other agents have no access rights, -# unless specifically authorized in other .acl resources. -<#owner> - a acl:Authorization; - acl:agent ; - # Set the access to the root storage folder itself - acl:accessTo <./>; - # All resources will inherit this authorization, by default - acl:default <./>; - # The owner has all of the access modes allowed - acl:mode - acl:Read, acl:Write, acl:Control. diff --git a/pod-template/.meta b/pod-template/.meta deleted file mode 100644 index 4285dab..0000000 --- a/pod-template/.meta +++ /dev/null @@ -1 +0,0 @@ - a . diff --git a/pod-template/README$.markdown b/pod-template/README$.markdown deleted file mode 100644 index 7440847..0000000 --- a/pod-template/README$.markdown +++ /dev/null @@ -1,22 +0,0 @@ -# Welcome to your pod - -## A place to store your data -Your pod is a **secure storage space** for your documents and data. -
-You can choose to share those with other people and apps. - -As the owner of this pod, -identified by http://localhost:3000/data-kitchen-user/profile/card#me, -you have access to all of your documents. - -## Working with your pod -The easiest way to interact with pods -is through Solid apps. -
-For example, -you can open your pod in [Databrowser](https://solidos.github.io/mashlib/dist/browse.html?uri=). - -## Learn more -The [Solid website](https://solidproject.org/) -and the people on its [forum](https://forum.solidproject.org/) -will be glad to help you on your journey. diff --git a/pod-template/README.acl b/pod-template/README.acl deleted file mode 100644 index c868de5..0000000 --- a/pod-template/README.acl +++ /dev/null @@ -1,14 +0,0 @@ -@prefix acl: . -@prefix foaf: . - -<#public> - a acl:Authorization; - acl:accessTo <./README>; - acl:agentClass foaf:Agent; - acl:mode acl:Read. - -<#owner> - a acl:Authorization; - acl:accessTo <./README>; - acl:agent ; - acl:mode acl:Read, acl:Write, acl:Control. diff --git a/pod-template/inbox/.acl b/pod-template/inbox/.acl deleted file mode 100644 index ca39d8a..0000000 --- a/pod-template/inbox/.acl +++ /dev/null @@ -1,19 +0,0 @@ -# ACL resource for the profile Inbox - -@prefix acl: . -@prefix foaf: . - -<#owner> - a acl:Authorization; - acl:agent ; - acl:accessTo <./>; - acl:default <./>; - acl:mode - acl:Read, acl:Write, acl:Control. - -# Appendable by authenticated, but NOT public-readable -<#authenticated> - a acl:Authorization; - acl:agentClass acl:AuthenticatedAgent; - acl:accessTo <./>; - acl:mode acl:Append. diff --git a/pod-template/profile/.acl b/pod-template/profile/.acl deleted file mode 100644 index 102acaf..0000000 --- a/pod-template/profile/.acl +++ /dev/null @@ -1,16 +0,0 @@ -@prefix : <#>. -@prefix acl: . -@prefix foaf: . - -:ControlReadWrite - a acl:Authorization; - acl:accessTo <./>; - acl:agent ; - acl:default <./>; - acl:mode acl:Control, acl:Read, acl:Write. -:Read - a acl:Authorization; - acl:accessTo <./>; - acl:agentClass foaf:Agent; - acl:default <./>; - acl:mode acl:Control, acl:Read, acl:Write. diff --git a/pod-template/profile/card$.ttl b/pod-template/profile/card$.ttl deleted file mode 100644 index 1085ff1..0000000 --- a/pod-template/profile/card$.ttl +++ /dev/null @@ -1,17 +0,0 @@ -@prefix foaf: . -@prefix solid: . -@prefix space: . -@prefix ldp: . -<> - a foaf:PersonalProfileDocument; - foaf:maker ; - foaf:primaryTopic . - - - space:storage <../>; - ldp:inbox <../inbox/>; - space:preferencesFile <../settings/prefs.ttl>; - solid:privateTypeIndex <../settings/privateTypeIndex.ttl>; - solid:publicTypeIndex <../settings/publicTypeIndex.ttl>; - solid:oidcIssuer ; - a foaf:Person. diff --git a/pod-template/profile/card.acl b/pod-template/profile/card.acl deleted file mode 100644 index 28f6b67..0000000 --- a/pod-template/profile/card.acl +++ /dev/null @@ -1,26 +0,0 @@ -@prefix acl: . -@prefix foaf: . - -<#viewer> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read. - -<#poster> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Append. - -<#editor> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read, acl:Write. - -<#owner> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read, acl:Write, acl:Append, acl:Control. diff --git a/pod-template/public/.acl b/pod-template/public/.acl deleted file mode 100644 index a3cc37c..0000000 --- a/pod-template/public/.acl +++ /dev/null @@ -1,18 +0,0 @@ -# Root ACL resource for the agent account -@prefix acl: . -@prefix foaf: . - -<#public> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo <./>; - acl:default <./>; - acl:mode - acl:Read, acl:Write, acl:Control. -<#owner> - a acl:Authorization; - acl:agent ; - acl:accessTo <./>; - acl:default <./>; - acl:mode - acl:Read, acl:Write, acl:Control. diff --git a/pod-template/public/logo.svg b/pod-template/public/logo.svg deleted file mode 100644 index a9b20e4..0000000 --- a/pod-template/public/logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/pod-template/public/logo.svg.acl b/pod-template/public/logo.svg.acl deleted file mode 100644 index 504bfcb..0000000 --- a/pod-template/public/logo.svg.acl +++ /dev/null @@ -1,26 +0,0 @@ -@prefix acl: . -@prefix foaf: . - -<#viewer> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read. - -<#poster> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Append. - -<#editor> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read, acl:Write. - -<#owner> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read, acl:Write, acl:Append, acl:Control. diff --git a/pod-template/public/test-tracker/index.ttl b/pod-template/public/test-tracker/index.ttl deleted file mode 100644 index b758981..0000000 --- a/pod-template/public/test-tracker/index.ttl +++ /dev/null @@ -1,7 +0,0 @@ -<#this> <../../profile/card#me>; - a ; - "2026-01-05T01:05:56Z"^^; - ; - ; - ; - . diff --git a/pod-template/public/test-tracker/index.ttl.acl b/pod-template/public/test-tracker/index.ttl.acl deleted file mode 100644 index bd0b387..0000000 --- a/pod-template/public/test-tracker/index.ttl.acl +++ /dev/null @@ -1,20 +0,0 @@ -@prefix acl: . -@prefix foaf: . - -<#viewer> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read. - -<#poster> - a acl:Authorization; - acl:agentClass acl:AuthenticatedAgent; - acl:accessTo ; - acl:mode acl:Append. - -<#editor> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo ; - acl:mode acl:Read, acl:Write. diff --git a/pod-template/public/test-tracker/state.ttl b/pod-template/public/test-tracker/state.ttl deleted file mode 100644 index 0aa5d20..0000000 --- a/pod-template/public/test-tracker/state.ttl +++ /dev/null @@ -1,5 +0,0 @@ - <>. -<#Iss1767575194231> ; - "create a new tracker"; - "2026-01-05T01:06:34Z"^^; - a . diff --git a/pod-template/robots.txt b/pod-template/robots.txt deleted file mode 100644 index 7b5fefd..0000000 --- a/pod-template/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -User-agent: * -# Allow all crawling (subject to ACLs as usual, of course) -Disallow: diff --git a/pod-template/robots.txt.acl b/pod-template/robots.txt.acl deleted file mode 100644 index 8869af2..0000000 --- a/pod-template/robots.txt.acl +++ /dev/null @@ -1,18 +0,0 @@ -# ACL for the default robots.txt resource -# Individual users will be able to override it as they wish -# Public-readable - -@prefix acl: . -@prefix foaf: . - -<#owner> - a acl:Authorization; - acl:agent ; - acl:accessTo ; - acl:mode acl:Read, acl:Write, acl:Control. - -<#public> - a acl:Authorization; - acl:agentClass foaf:Agent; # everyone - acl:accessTo ; - acl:mode acl:Read. diff --git a/pod-template/settings/.acl b/pod-template/settings/.acl deleted file mode 100644 index 41c0830..0000000 --- a/pod-template/settings/.acl +++ /dev/null @@ -1,17 +0,0 @@ -@prefix acl: . -@prefix foaf: . - -<#owner> - a acl:Authorization; - acl:agent ; - acl:accessTo <./>; - acl:default <./>; - acl:mode - acl:Read, acl:Write, acl:Control. - -:Read - a acl:Authorization; - acl:accessTo <./>; - acl:agentClass foaf:Agent; - acl:default <./>; - acl:mode acl:Control, acl:Read, acl:Write. diff --git a/pod-template/settings/prefs.ttl b/pod-template/settings/prefs.ttl deleted file mode 100644 index cbca4de..0000000 --- a/pod-template/settings/prefs.ttl +++ /dev/null @@ -1,15 +0,0 @@ -@prefix dct: . -@prefix pim: . -@prefix foaf: . -@prefix solid: . - -<> - a pim:ConfigurationFile; - - dct:title "Preferences file" . - - - - - solid:publicTypeIndex ; - solid:privateTypeIndex . diff --git a/pod-template/settings/publicTypeIndex.ttl.acl b/pod-template/settings/publicTypeIndex.ttl.acl deleted file mode 100644 index 484b097..0000000 --- a/pod-template/settings/publicTypeIndex.ttl.acl +++ /dev/null @@ -1,16 +0,0 @@ -@prefix acl: . -@prefix foaf: . - -# The Public Type Index is readable by the public. -<#public> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo <./publicTypeIndex.ttl>; - acl:mode acl:Read. - -# The owner has full access to the profile -<#owner> - a acl:Authorization; - acl:agent ; - acl:accessTo <./publicTypeIndex.ttl>; - acl:mode acl:Read, acl:Write, acl:Control. diff --git a/profile/card$.ttl b/profile/card$.ttl new file mode 100644 index 0000000..3369587 --- /dev/null +++ b/profile/card$.ttl @@ -0,0 +1,25 @@ +@prefix solid: . +@prefix foaf: . +@prefix pim: . +@prefix schema: . +@prefix ldp: . +@prefix me: <#me>. + +<> + a foaf:PersonalProfileDocument ; + foaf:maker me: ; + foaf:primaryTopic me: . + +<#me> + a foaf:Person ; + a schema:Person ; + + foaf:name "Local Kitchen User" ; + + solid:account ; # link to the account uri + pim:storage ; # root storage + + pim:preferencesFile <./settings/prefs.ttl> ; # private settings/preferences + solid:publicTypeIndex <./settings/publicTypeIndex.ttl> ; + solid:privateTypeIndex <./settings/privateTypeIndex.ttl> . + diff --git a/proxy/index.cjs b/proxy/index.cjs deleted file mode 100644 index 60c0692..0000000 --- a/proxy/index.cjs +++ /dev/null @@ -1,84 +0,0 @@ -// CORS proxy for the Electron home build. Fetches an external resource -// server-side and returns it with permissive CORS, so the app can read -// cross-origin content the browser would otherwise block. -// -// Dependency-free: Node's built-in http server + the global fetch (Node 18+), -// no express/cors — so it runs with a plain `node index.js`, no install. -// Started/stopped with the app (see ../electron-config/servers.cjs). - -const http = require('node:http'); -const { makeGate } = require('../electron-config/gate.cjs'); - -const port = Number(process.env.DK_PROXY_PORT) || 8001; -const appPort = Number(process.env.DK_PUBLIC_PORT) || 8000; - -// Token gate (see gate.cjs); no-op when DK_GATE_TOKEN is absent (standalone dev). -// The app's own origin is allowed by Origin/Referer too: app pages in a blessed -// browser call the proxy with plain fetch(), which doesn't attach cookies -// cross-port — and a hostile page can't forge its Origin. -const gate = makeGate(process.env.DK_GATE_TOKEN, { - allowOrigins: [`http://localhost:${appPort}`, `http://127.0.0.1:${appPort}`], -}); - -// Permissive CORS on every response (replaces the express `cors()` middleware). -const CORS = { - 'access-control-allow-origin': '*', - 'access-control-allow-methods': 'GET,OPTIONS', - 'access-control-allow-headers': '*', -}; - -const server = http.createServer(async (req, res) => { - if (gate(req, res)) return; - if (req.method === 'OPTIONS') { res.writeHead(204, CORS); return res.end(); } - - const url = new URL(req.url, `http://localhost:${port}`); - if (url.pathname !== '/proxy') { res.writeHead(404, CORS); return res.end('not found'); } - - const uri = url.searchParams.get('uri') || url.searchParams.get('url'); - if (!uri) { res.writeHead(400, CORS); return res.end('missing uri'); } - console.log('fetching ', uri); - - try { - const response = await fetch(uri); - const type = response.headers.get('content-type') || ''; - - if (type.includes('text/html')) { - // A site that refuses framing (X-Frame-Options, or a restrictive CSP - // frame-ancestors) re-routes its client app on this proxy URL and - // renders its own 404 inside the in-pane reader iframe. For those we - // strip the page's scripts so the server-rendered article survives; - // framing-friendly pages keep their JS intact. - const xfo = (response.headers.get('x-frame-options') || '').toLowerCase(); - const csp = (response.headers.get('content-security-policy') || '').toLowerCase(); - const fa = (csp.match(/frame-ancestors([^;]*)/) || [, ''])[1]; - const refusesFraming = - xfo.includes('deny') || xfo.includes('sameorigin') || (fa && !fa.includes('*')); - - // Inject so the article's relative assets (css/img) resolve - // against its real origin when shown in the in-pane reader iframe. - let html = await response.text(); - const base = ``; - html = /]*>/i.test(html) ? html.replace(/]*)>/i, `${base}`) : base + html; - - if (refusesFraming) { - html = html - .replace(/]*>[\s\S]*?<\/script>/gi, '') // inline + external - .replace(/]*\/>/gi, ''); // self-closing - console.log(' refuses framing → stripped scripts'); - } - res.writeHead(200, { ...CORS, 'content-type': 'text/html; charset=utf-8' }); - return res.end(html); - } - - const buf = Buffer.from(await response.arrayBuffer()); - res.writeHead(response.status || 200, { ...CORS, ...(type ? { 'content-type': type } : {}) }); - return res.end(buf); - } catch (error) { - console.log(error); - res.writeHead(500, CORS); - res.end(String(error)); - } -}); - -// Loopback only — the proxy is for this machine's app, never the LAN. -server.listen(port, '127.0.0.1', () => console.log(`Proxy listening on port ${port}`)); diff --git a/router/index.cjs b/router/index.cjs deleted file mode 100644 index eff7ff7..0000000 --- a/router/index.cjs +++ /dev/null @@ -1,101 +0,0 @@ -// dk single-origin routing front server. -// -// dk is a self-hosting, user-redesignable app: the editable app DEFINITION (the -// HTML shells, RDF config, favourites, content) lives in the user's writable pod -// root, while the read-only ENGINE (component libraries, vendor bundles, compiled -// plugin dist, dk's own bundle) ships inside the executable. The definition files -// reference the engine by plain relative paths (e.g. node_modules/…, dist/…), so -// both must appear under ONE origin for those paths to resolve. -// -// This server is that single origin (default :8000). It routes per request: -// - engine path prefixes -> served statically from the executable dir (read-only) -// - everything else -> reverse-proxied to the pivot CSS (the pod root), -// which listens on an internal loopback port -// CSS keeps Solid semantics (LDP/PATCH/POST) on the pod; the engine is plain files. -// -// Loopback-bound and gated (see ../electron-config/gate.cjs) like the other dk -// servers. Standalone (no DK_GATE_TOKEN) it runs open, for dev. - -'use strict'; - -const http = require('node:http'); -const fs = require('node:fs'); -const path = require('node:path'); -const { makeGate } = require('../electron-config/gate.cjs'); - -const publicPort = Number(process.env.DK_PUBLIC_PORT) || 8000; -const cssPort = Number(process.env.DK_CSS_INTERNAL_PORT) || 8010; -const engineDir = path.resolve(process.env.DK_ENGINE_DIR || path.join(__dirname, '..')); - -// Path prefixes whose files are ENGINE (shipped, read-only) — served from the -// executable dir, never the pod. Everything else is pod data via CSS. -const ENGINE_PREFIXES = ['/node_modules/', '/dist/', '/src/', '/assets/']; -// Plugin dist is engine; the rest of a plugin (config/assets) is pod-editable. -const ENGINE_PLUGIN = /^\/plugins\/[^/]+\/dist\//; - -function isEnginePath(p) { - return ENGINE_PREFIXES.some((pre) => p.startsWith(pre)) || ENGINE_PLUGIN.test(p); -} - -const gate = makeGate(process.env.DK_GATE_TOKEN, { - allowOrigins: [`http://localhost:${publicPort}`, `http://127.0.0.1:${publicPort}`], -}); - -const MIME = { - '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', - '.cjs': 'text/javascript', '.css': 'text/css', '.json': 'application/json', - '.map': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', - '.jpg': 'image/jpeg', '.gif': 'image/gif', '.ico': 'image/x-icon', - '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', - '.wasm': 'application/wasm', '.ttl': 'text/turtle', '.txt': 'text/plain', -}; - -function serveEngine(req, res, pathname) { - // Resolve under engineDir and refuse anything that escapes it (traversal). - const rel = decodeURIComponent(pathname).replace(/^\/+/, ''); - const full = path.resolve(engineDir, rel); - if (full !== engineDir && !full.startsWith(engineDir + path.sep)) { - res.writeHead(403); return res.end('forbidden'); - } - fs.stat(full, (err, st) => { - if (err || !st.isFile()) { res.writeHead(404); return res.end('not found'); } - res.writeHead(200, { 'content-type': MIME[path.extname(full).toLowerCase()] || 'application/octet-stream' }); - if (req.method === 'HEAD') return res.end(); - fs.createReadStream(full).pipe(res); - }); -} - -function proxyToCss(req, res) { - const up = http.request( - { host: '127.0.0.1', port: cssPort, method: req.method, path: req.url, headers: req.headers }, - (r) => { res.writeHead(r.statusCode, r.headers); r.pipe(res); }, - ); - up.on('error', (e) => { res.writeHead(502); res.end(`pod server unreachable: ${e.message}`); }); - req.pipe(up); -} - -const server = http.createServer((req, res) => { - if (gate(req, res)) return; - const pathname = new URL(req.url, 'http://localhost').pathname; - if ((req.method === 'GET' || req.method === 'HEAD') && isEnginePath(pathname)) { - return serveEngine(req, res, pathname); - } - proxyToCss(req, res); -}); - -// CSS uses websockets for change notifications; forward upgrades to the pod. -server.on('upgrade', (req, socket, head) => { - if (!gate.upgradeOk(req)) return socket.destroy(); - const up = http.request({ host: '127.0.0.1', port: cssPort, method: req.method, path: req.url, headers: req.headers }); - up.on('upgrade', (r, upSocket, upHead) => { - socket.write(`HTTP/1.1 101 ${r.statusMessage}\r\n` + - Object.entries(r.headers).map(([k, v]) => `${k}: ${v}`).join('\r\n') + '\r\n\r\n'); - if (upHead && upHead.length) upSocket.unshift(upHead); - upSocket.pipe(socket).pipe(upSocket); - }); - up.on('error', () => socket.destroy()); - up.end(); -}); - -server.listen(publicPort, '127.0.0.1', () => - console.log(`router listening on http://localhost:${publicPort}/ — engine from ${engineDir}, pod via :${cssPort}`)); diff --git a/src/dk-auth-indicator.js b/src/dk-auth-indicator.js deleted file mode 100644 index 73347da..0000000 --- a/src/dk-auth-indicator.js +++ /dev/null @@ -1,43 +0,0 @@ -// Repaints the chrome's Settings gear as the logged-in indicator: -// green tint + WebID surfaced as the gear's hover title. Listens on -// document for sol-login / sol-logout (both bubble + composed, so -// they reach us from any on the page including ones -// inside shadow trees like sol-pod). -// -// Coupled deliberately to the Settings sol-button (not sol-login's -// own UI) so the chrome can keep the login element itself hidden -// (display:none) except during an active sol-auth-needed flow. - -const GEAR_AUTHED_CLASS = 'dk-chrome-authed'; - -function settingsButton() { - for (const sb of document.querySelectorAll('sol-button')) { - if (sb.getAttribute('name') === 'Settings') return sb; - } - return null; -} - -function paintAuthed(webId) { - const btn = settingsButton(); - if (!btn) return; - btn.classList.add(GEAR_AUTHED_CLASS); - if (webId) btn.setAttribute('title', webId); -} - -function paintUnauthed() { - const btn = settingsButton(); - if (!btn) return; - btn.classList.remove(GEAR_AUTHED_CLASS); - // Restore the original tooltip declared in markup; the chrome - // sol-button is authored with title="Settings". - btn.setAttribute('title', 'Settings'); -} - -document.addEventListener('sol-login', (e) => { - const webId = e.detail?.webId || ''; - paintAuthed(webId); -}); - -document.addEventListener('sol-logout', () => { - paintUnauthed(); -}); diff --git a/src/dk-auth-router.js b/src/dk-auth-router.js deleted file mode 100644 index 5c6a570..0000000 --- a/src/dk-auth-router.js +++ /dev/null @@ -1,52 +0,0 @@ -// dk-auth-router parses the URL fragment for `auth=` and exposes -// `window.dkActiveAuthTag` + `window.dkFetch(url, init?)`. Components -// that do ad-hoc authenticated fetches at dk level can call dkFetch; -// it routes via the shared AuthManager so the active tag's session is -// used. When no tag is set, AuthManager.fetchFor walks all sessions in -// registration order and uses the first that covers the origin. - -function parseHashTag() { - const h = location.hash || ''; - // Match `#auth=foo` or `#…&auth=foo`. The fragment is opaque to the - // browser, so we just scan for the pattern. - const m = h.match(/(?:^|[#&])auth=([^&]+)/); - return m ? decodeURIComponent(m[1]) : null; -} - -function findAuthManager() { - // dk no longer mounts a sol-login of its own — login UIs live inside - // embedded apps (podz mounts sol-login inside its sol-pod shadow - // root, future apps follow the same pattern). The page-wide singleton - // is reachable through the swc bundle's AuthManager.shared accessor - // regardless of whether any sol-login element is currently in the - // DOM. Fall back to DOM probing so dev surfaces that DO mount a - // sol-login still work if the bundle hasn't loaded yet. - return window.SolidWebComponents?.AuthManager?.shared - ?? document.querySelector('sol-login')?.auth - ?? null; -} - -function updateActiveTag() { - const tag = parseHashTag(); - window.dkActiveAuthTag = tag; // may be null — fetchFor handles that - document.dispatchEvent(new CustomEvent('dk-active-auth-change', { - bubbles: false, - detail: { tag }, - })); -} - -// dkFetch routes through swc's solFetch so a 401 anywhere in dk -// (calendar PATCH, sol-feed bookmark write, hand-written widget POSTs) -// triggers the chrome's auto-prompt + retry. The active -// auth tag (from #auth=…) is threaded through as `init.authTag` so -// AuthManager picks the right session when multiple are active. -window.dkFetch = async function dkFetch(url, init) { - const am = findAuthManager(); - if (!am) return fetch(url, init); - const { solFetch } = await import('sol-components/core/auth-fetch.js'); - const merged = { ...(init || {}), authTag: window.dkActiveAuthTag || undefined }; - return solFetch(url, merged); -}; - -updateActiveTag(); -window.addEventListener('hashchange', updateActiveTag); diff --git a/src/dk-boot.js b/src/dk-boot.js deleted file mode 100644 index e61ad34..0000000 --- a/src/dk-boot.js +++ /dev/null @@ -1,40 +0,0 @@ -// Runs first, synchronously, before paint: apply the user's SAVED theme / text -// size (their explicit localStorage choice) so there's no flash. The *default* -// when nothing is saved comes from 's RDF source (ui:colorScheme / -// ui:fontSize → the `color-scheme` / `font-size` attributes) and is resolved by -// CSS (see dk-chrome.css cascade, which suffix-matches the UI-vocab URI) — with -// the system preference as the final fallback. NOTE: the RDF-derived attributes -// arrive after a fetch, so on a brand-new profile (empty localStorage) first -// paint shows the system theme, then snaps to the RDF default once it resolves; -// returning users hit the localStorage path above and never flash. Dev write -// access is the `solid-kitchen` attribute on , read by the app; -// there is no window global any more. -(function () { - try { - var t = localStorage.getItem('dk:theme'); - if (t) document.documentElement.setAttribute('data-theme', t); - var f = localStorage.getItem('dk:fontsize'); - if (f) document.documentElement.setAttribute('data-fontsize', f); - } catch (e) {} -})(); - -// One-shot: wipe podz's persisted pod-registry seed AND the last-viewed pod -// selection so the next dk load takes podz's fresh-session path -// (leftPod.initialize() with no `source=` and an empty registry → -// sol-pod.discover() actually runs). Bump the flag (v2 → v3 → …) to re-clear. -(function () { - try { - if (localStorage.getItem('dk-cleared-session-pods-v2')) return; - var raw = localStorage.getItem('podz_v4'); - if (raw) { - var blob = JSON.parse(raw); - delete blob.sessionPods; - delete blob.selection; - localStorage.setItem('podz_v4', JSON.stringify(blob)); - } - // Legacy keys, in case an old podz version still wrote them. - localStorage.removeItem('podz_session_pods'); - localStorage.removeItem('podzPodSelection'); - localStorage.setItem('dk-cleared-session-pods-v2', '1'); - } catch (e) {} -})(); diff --git a/src/dk-config-settings.js b/src/dk-config-settings.js deleted file mode 100644 index 9cef062..0000000 --- a/src/dk-config-settings.js +++ /dev/null @@ -1,167 +0,0 @@ -// — the Electron + Pivot group of the settings page. -// -// The config (window geometry, server ports, pod root) is the pod resource -// dk-pod/dk/ui-data/data-kitchen-startup.ttl — the SOURCE OF TRUTH the user can browse. -// The main process publishes it on boot and keeps it current; userData is just a -// launch-time cache that trails it (see electron-config/main.cjs). This component -// mounts a real shape-driven directly on that pod resource (CSS -// handles GET/PATCH), so the fields match every other setting; on each save it -// reads the resource back as RDF and pushes the numbers to userData via the IPC -// saveConfig (so they survive the next launch and the window applies live). It -// never overwrites the resource on open — only seeds it if absent. Pod root -// stays read-only with a "Move my pod" button (a path edit wouldn't move the -// data). Outside Electron we show a short notice. - -import { rdf } from 'sol-components/core/rdf.js'; -import { loadRdfStore } from 'sol-components/core/rdf-utils.js'; -import { solFetch } from 'sol-components/core/auth-fetch.js'; - -const UI = 'http://www.w3.org/ns/ui#'; -const SCHEMA = 'http://schema.org/'; -const MIRROR = './dk-pod/dk/ui-data/data-kitchen-startup.ttl'; -const SHAPE = './dk-pod/dk/ui-data/data-kitchen-startup-config.shacl'; -const CONFIG_FILE_TYPE = 'http://www.wikidata.org/entity/Q1193846'; - -// form field key → predicate IRI (the editable numbers; pod root is not here) -const FIELD_PREDS = { - publicPort: UI + 'publicPort', - proxyPort: UI + 'proxyPort', - privatePort: UI + 'privatePort', - width: SCHEMA + 'width', - height: SCHEMA + 'height', - windowX: UI + 'windowX', - windowY: UI + 'windowY', -}; - -function esc(s) { - return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} - -class DkConfigSettings extends HTMLElement { - async connectedCallback() { - if (this._rendered) return; - this._rendered = true; - const dk = (typeof window !== 'undefined') ? window.dkElectron : null; - if (!dk || typeof dk.getConfig !== 'function') { - this.innerHTML = - `

Electron & Pivot

` + - `

Window size, position, server ports and the pod root are part of the ` + - `desktop app — open Data Kitchen as the Electron app to edit them.

`; - return; - } - try { - const { config, effective } = await dk.getConfig(); - this._effective = effective || {}; - await this.ensureMirror(config); - this.render(); - this.wire(); - } catch (e) { - this.innerHTML = - `

Couldn't load app settings: ${esc(e?.message || e)}

`; - } - } - - mirrorUrl() { return new URL(MIRROR, document.baseURI).href; } - subjectUrl() { return this.mirrorUrl() + '#config'; } - - // The pod resource is the SOURCE OF TRUTH — the main process publishes it on - // boot and keeps it current. Don't clobber it on open; only seed it if it's - // somehow missing. - async ensureMirror(config) { - try { - const r = await solFetch(this.mirrorUrl(), { headers: { accept: 'text/turtle' } }); - if (r.ok) return; - } catch { /* fall through and seed */ } - const t = (config && config.primaryTopic) || {}; - const stmts = Object.entries(FIELD_PREDS) - .filter(([k]) => Number.isFinite(t[k])) - .map(([k, pred]) => ` <${pred}> ${t[k]}`); - const body = - `<> a <${CONFIG_FILE_TYPE}> ;\n` + - ` <#config> .\n` + - `<#config>\n${stmts.join(' ;\n')} .\n`; - await solFetch(this.mirrorUrl(), { method: 'PUT', headers: { 'content-type': 'text/turtle' }, body }); - } - - render() { - const root = this._effective.root || ''; - this.innerHTML = ` -
-

Electron & Pivot

- -
- Root (pod home — changing it moves your pod) - - ${esc(root) || '—'} - - -
- - -
`; - } - - wire() { - this.querySelector('sol-form')?.addEventListener('sol-form-save', () => this.syncBack()); - this.querySelector('[data-act="move"]')?.addEventListener('click', () => this.movePod()); - this.querySelector('[data-act="reload"]')?.addEventListener('click', () => window.dkElectron.restart()); - } - - status(msg, kind) { - const el = this.querySelector('[data-status]'); - if (!el) return; - el.textContent = msg || ''; - el.hidden = !msg; - el.dataset.kind = kind || ''; - } - - // Read the freshly-edited mirror as RDF and push the numbers to userData. - async syncBack() { - try { - const store = await loadRdfStore(this.mirrorUrl(), solFetch); - const subj = rdf.sym(this.subjectUrl()); - const vals = {}; - for (const [k, pred] of Object.entries(FIELD_PREDS)) { - const o = store.any(subj, rdf.sym(pred)); - if (o) { const n = parseInt(o.value, 10); if (Number.isFinite(n)) vals[k] = n; } - } - const r = await window.dkElectron.saveConfig(vals); - if (r?.status === 'saved') { - this.status('Saved.', 'ok'); - const banner = this.querySelector('[data-reload]'); - if (banner) banner.hidden = !r.portsChanged; - } else { - this.status(`Couldn't save: ${r?.message || 'unknown error'}`, 'err'); - } - } catch (e) { - this.status(`Couldn't save: ${e?.message || e}`, 'err'); - } - } - - async movePod() { - this.status('Choose a new home folder…'); - try { - const r = await window.dkElectron.moveMyPod(); - if (r?.status === 'moved') this.status('Pod moved — restarting…'); - else if (r?.status === 'cancelled') this.status(''); - else if (r?.status === 'same') this.status('That is already the pod root.', 'err'); - else if (r?.status === 'nested') this.status("Can't move the pod inside itself.", 'err'); - else this.status(`Move failed: ${r?.message || 'unknown error'}`, 'err'); - } catch (e) { - this.status(`Move failed: ${e?.message || e}`, 'err'); - } - } -} - -if (!customElements.get('dk-config-settings')) { - customElements.define('dk-config-settings', DkConfigSettings); -} - -export { DkConfigSettings }; diff --git a/src/dk-issuers-editor.js b/src/dk-issuers-editor.js deleted file mode 100644 index e31eed0..0000000 --- a/src/dk-issuers-editor.js +++ /dev/null @@ -1,145 +0,0 @@ -// — edits the sign-in issuer -// list (solid:oidcIssuer) on the given pod subject. Order is preference: the -// FIRST issuer is the default. The "add" dropdown offers a curated set of known -// Solid issuers plus a custom-URL entry. Writes back to the pod over solFetch. -// -// Stored as plain solid:oidcIssuer triples; this editor rewrites the whole set -// in UI order on every change so the serialized order encodes "first = default". - -import { rdf } from 'sol-components/core/rdf.js'; -import { loadRdfStore } from 'sol-components/core/rdf-utils.js'; -import { solFetch } from 'sol-components/core/auth-fetch.js'; - -const SOLID_OIDC = 'http://www.w3.org/ns/solid/terms#oidcIssuer'; -const CURATED = [ - 'https://solidcommunity.net', - 'https://solidweb.me', - 'https://solidweb.org', - 'https://login.inrupt.com', -]; - -function esc(s) { - return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} - -class DkIssuersEditor extends HTMLElement { - async connectedCallback() { - if (this._wired) return; - this._wired = true; - this.issuers = []; - await this.load(); - } - - get source() { return this.getAttribute('source') || ''; } - docUrl() { return new URL(this.source.split('#')[0], document.baseURI).href; } - subjectUrl() { - const [doc, frag] = this.source.split('#'); - return new URL(doc, document.baseURI).href + '#' + (frag || 'Settings'); - } - - async load() { - try { - this._store = await loadRdfStore(this.docUrl(), solFetch); - const subj = rdf.sym(this.subjectUrl()); - this.issuers = this._store.each(subj, rdf.sym(SOLID_OIDC)).map((o) => o.value); - } catch { - this.issuers = []; - } - this.render(); - } - - async persist() { - try { - const docUrl = this.docUrl(); - const store = this._store || await loadRdfStore(docUrl, solFetch); - const subj = rdf.sym(this.subjectUrl()); - const pred = rdf.sym(SOLID_OIDC); - const doc = rdf.sym(docUrl); - store.removeMatches(subj, pred, null); - for (const url of this.issuers) store.add(subj, pred, rdf.sym(url), doc); - const body = await new Promise((res, rej) => - rdf.serialize(doc, store, docUrl, 'text/turtle', (err, str) => (err ? rej(err) : res(str)))); - const r = await solFetch(docUrl, { method: 'PUT', headers: { 'content-type': 'text/turtle' }, body }); - if (!r.ok) throw new Error(`HTTP ${r.status}`); - this.status('Saved.', 'ok'); - } catch (e) { - this.status(`Couldn't save issuers: ${e?.message || e}`, 'err'); - } - } - - status(msg, kind) { - const el = this.querySelector('[data-status]'); - if (!el) return; - el.textContent = msg || ''; - el.hidden = !msg; - el.dataset.kind = kind || ''; - } - - render() { - const rows = this.issuers.map((url, i) => ` -
- ${esc(url)} - ${i === 0 - ? 'default' - : ``} - -
`).join(''); - - const options = CURATED.map((u) => ``).join(''); - - this.innerHTML = ` -
- Sign-in issuers (first is the default) -
${rows || 'No issuers yet — add one below.'}
-
- - - -
- -
`; - - const pick = this.querySelector('[data-pick]'); - const custom = this.querySelector('[data-custom]'); - pick.addEventListener('change', () => { custom.hidden = pick.value !== '__custom__'; }); - this.querySelector('[data-act="add"]').addEventListener('click', () => this.add(pick, custom)); - for (const btn of this.querySelectorAll('[data-act="remove"]')) { - btn.addEventListener('click', () => this.remove(Number(btn.dataset.i))); - } - for (const btn of this.querySelectorAll('[data-act="default"]')) { - btn.addEventListener('click', () => this.makeDefault(Number(btn.dataset.i))); - } - } - - add(pick, custom) { - let url = pick.value === '__custom__' ? custom.value.trim() : pick.value; - if (!url) return; - try { url = new URL(url).href.replace(/\/$/, ''); } catch { this.status('Not a valid URL.', 'err'); return; } - if (this.issuers.includes(url)) { this.status('Already in the list.', 'err'); return; } - this.issuers.push(url); - this.render(); - this.persist(); - } - - remove(i) { - this.issuers.splice(i, 1); - this.render(); - this.persist(); - } - - makeDefault(i) { - const [u] = this.issuers.splice(i, 1); - this.issuers.unshift(u); - this.render(); - this.persist(); - } -} - -if (!customElements.get('dk-issuers-editor')) { - customElements.define('dk-issuers-editor', DkIssuersEditor); -} - -export { DkIssuersEditor }; diff --git a/src/dk-owner-session.js b/src/dk-owner-session.js deleted file mode 100644 index b33457f..0000000 --- a/src/dk-owner-session.js +++ /dev/null @@ -1,66 +0,0 @@ -// dk-owner-session — register the local pod OWNER as a logged-in session. -// -// data-kitchen's local CSS runs allow-all behind the gate token (gate.cjs — the -// REAL access control), so the app already reads/writes the pod without a login. -// What was missing is IDENTITY: with no session, AuthManager.getWebId() is null, -// so podz shows "no pods found", the chrome reads logged-out, and owner-only UI -// stays hidden. -// -// Approach (A): synthesize a local session for the owner WebID -// (/dk-pod/profile/card#me, seeded by electron-config/pod-template.cjs). -// Its `.fetch` is plain window.fetch — the gate header/cookie authorizes the -// traffic; no OIDC token is involved. This is a presentational/functional -// overlay over the gate, NOT WebAC enforcement. - -const origin = location.origin; -const OWNER_WEBID = `${origin}/dk-pod/profile/card#me`; - -// AuthManager session shape (see node_modules/sol-components/core/auth-core.js): -// { info: { isLoggedIn, webId, issuer, sessionId }, fetch }. sessionCoversOrigin -// matches the webId/issuer host (incl. port) against the request origin — and we -// derive both from location.origin, so the session covers every same-origin -// (i.e. local pod) request and never claims external origins. -const ownerSession = { - info: { - isLoggedIn: true, - webId: OWNER_WEBID, - issuer: `${origin}/`, - sessionId: 'dk-owner', - clientAppId: null, - }, - fetch: (input, init) => window.fetch(input, init), - // A consumer may call logout(); make it a harmless no-op — the gate, not this - // session, controls access, so "logging out" of a synthetic session is moot. - logout: async () => {}, -}; - -function register() { - const am = window.SolidWebComponents?.AuthManager?.shared; - if (!am) return false; - // 'default' is the tag AuthManager.getWebId()/window.dkFetch use when no - // `#auth=` tag is set. Embedded logins (podz, SolidOS) register under their - // own side tags, so this never clobbers a real session the user establishes — - // and we only seed 'default' if nothing logged-in already holds it. - if (!am.sessions.get('default')?.info?.isLoggedIn) { - am.sessions.set('default', ownerSession); - } - // dk mounts no , so the chrome (dk-auth-indicator) and any pod - // discovery that waits on a login event need an explicit nudge. - document.dispatchEvent(new CustomEvent('sol-login', { - bubbles: true, - composed: true, - detail: { webId: OWNER_WEBID, issuer: ownerSession.info.issuer, side: 'default' }, - })); - return true; -} - -// The swc bundle (hence AuthManager) is ready by the time dk-shell imports this -// (after ComponentInterop.ready); retry briefly in case the namespace alias lags. -if (!register()) { - let tries = 0; - const timer = setInterval(() => { - if (register() || ++tries > 20) clearInterval(timer); - }, 100); -} - -export { OWNER_WEBID }; diff --git a/src/dk-plugin-settings.js b/src/dk-plugin-settings.js deleted file mode 100644 index 111747c..0000000 --- a/src/dk-plugin-settings.js +++ /dev/null @@ -1,111 +0,0 @@ -// — renders the Settings page's per-plugin settings groups -// from RDF, gated on catalog "in-use" status. Manifest-driven: nothing about a -// plugin's settings is duplicated here — each plugin declares its own. -// -// For each plugin currently IN USE (its ui:name is referenced in the active shell -// menu, menu=…), this loads the plugin's manifest (plugins//manifest.jsonld) -// and, if it declares a settings shape (dct:conformsTo) plus a settings document -// (a .ttl in dct:requires), renders a shape-driven for it. The form's -// subject is the document's own foaf:primaryTopic — the established Solid -// "what this document is about" convention (every settings doc already declares -// `<> foaf:primaryTopic <#Settings>`), so no settings-subject term is needed. -// -// A plugin parked in the catalog (not wired into the shell) is skipped. sc's -// DOM-discovery is left untouched, so other apps keep zero-config -// auto-discovery. - -import { rdf } from 'sol-components/core/rdf.js'; -import { solFetch } from 'sol-components/core/auth-fetch.js'; - -const UI = 'http://www.w3.org/ns/ui#'; -const FOAF = 'http://xmlns.com/foaf/0.1/'; - -async function loadTurtle(url) { - const ttl = await (await solFetch(url)).text(); - const store = rdf.graph(); - rdf.parse(ttl, store, url, 'text/turtle'); - return store; -} - -class DkPluginSettings extends HTMLElement { - async connectedCallback() { - if (this._rendered) return; - this._rendered = true; - - const menu = this.getAttribute('menu') - || document.getElementById('dk-tabs')?.getAttribute('from-rdf'); - if (!menu) { console.warn('[dk-plugin-settings] no menu source'); return; } - - try { - for (const manifestUrl of await this._inUseManifests(menu)) { - const group = await this._groupFor(manifestUrl); - if (group) this.appendChild(group); - } - } catch (err) { - console.warn(`[dk-plugin-settings] ${err.message}`); - } - } - - // Manifest URLs of the plugins wired into the active shell (dk's "in use" = - // ui:name present in the menu doc), derived from each entry's source path. - async _inUseManifests(menuRef) { - const menuUrl = new URL(menuRef.split('#')[0], document.baseURI).href; - const store = await loadTurtle(menuUrl); - const seen = new Set(); - const out = []; - for (const st of store.statementsMatching(null, rdf.sym(UI + 'name'), null)) { - let src = null; - for (const a of store.each(st.subject, rdf.sym(UI + 'attribute'))) { - if (store.any(a, rdf.sym('http://schema.org/name'))?.value === 'source') { - src = store.any(a, rdf.sym('http://schema.org/value'))?.value; break; - } - } - const id = src && src.match(/plugins\/([^/]+)\//)?.[1]; - if (!id || seen.has(id)) continue; - seen.add(id); - out.push(new URL(`dk-pod/dk/plugins/${id}/manifest.jsonld`, document.baseURI).href); - } - return out; - } - - // Build a settings group from a plugin manifest, or null if it has no settings. - // The manifest is JSON-LD; its keys (shape/requires/label) are read directly. - async _groupFor(manifestUrl) { - let m; - try { m = await (await solFetch(manifestUrl)).json(); } catch { return null; } - if (!m.shape) return null; // no settings shape → no form - const requires = Array.isArray(m.requires) ? m.requires : (m.requires ? [m.requires] : []); - const ttl = requires.find((r) => String(r).endsWith('.ttl')); - if (!ttl) return null; - - const shape = new URL(m.shape, manifestUrl).href; // /node_modules/…/x.shacl - const docUrl = new URL(ttl, manifestUrl).href; // plugins//x.ttl - let subject; - try { - const dstore = await loadTurtle(docUrl); - subject = dstore.any(rdf.sym(docUrl), rdf.sym(FOAF + 'primaryTopic'))?.value; - } catch { return null; } - if (!subject) return null; - - return this._group(m.label || m.name || 'Settings', shape, subject); - } - - // One settings group, styled like the page's hardcoded groups. data-settings-skip - // keeps these forms out of the discovery accordion below. - _group(label, shape, subject) { - const section = document.createElement('section'); - section.className = 'dk-settings-group'; - section.setAttribute('data-settings-skip', ''); - const h3 = document.createElement('h3'); - h3.textContent = label; - const form = document.createElement('sol-form'); - form.setAttribute('data-settings-skip', ''); - form.setAttribute('shape', shape); - form.setAttribute('subject', subject); - form.setAttribute('save-to', subject); - section.append(h3, form); - return section; - } -} - -customElements.define('dk-plugin-settings', DkPluginSettings); diff --git a/src/dk-settings-applier.js b/src/dk-settings-applier.js deleted file mode 100644 index 2302822..0000000 --- a/src/dk-settings-applier.js +++ /dev/null @@ -1,95 +0,0 @@ -// dk-settings-applier applies the UI settings to the live page (data-theme on -// , --font-size CSS variable). It reads the RESOLVED values straight off -// — which loads them from its `source` RDF and exposes them as -// attributes (ui:colorScheme → `color-scheme`, ui:fontSize → `font-size`). So -// there is NO rdflib / store access here: dk imports ZERO swc internals; the -// RDF read lives in the component. Re-applies on `sol-default-change` (the -// component re-resolved), `sol-form-save` (the settings page saved), and -// system-theme change. First-run persistence is handled by sol-settings/sol-form -// on save; until then dk falls back to system/medium. The ☰ Theme / Text size -// toggles ALSO write their choice back to the settings RDF (dk-tabs-shell -// persistAppearance), so the file and the live state stay in agreement. - -const UI = 'http://www.w3.org/ns/ui#'; -const SCHEME_TO_VALUE = { - [UI + 'SystemColorScheme']: 'system', - [UI + 'LightColorScheme']: 'light', - [UI + 'DarkColorScheme']: 'dark', -}; -const FONT_TO_VALUE = { - [UI + 'SmallFont']: 'small', - [UI + 'MediumFont']: 'medium', - [UI + 'LargeFont']: 'large', -}; - -function applyTheme(theme) { - const html = document.documentElement; - if (theme === 'system') { - const dark = window.matchMedia('(prefers-color-scheme: dark)').matches; - html.dataset.theme = dark ? 'dark' : 'light'; - html.dataset.themeSource = 'system'; - } else { - html.dataset.theme = theme; - html.dataset.themeSource = 'user'; - } -} - -function applyFontSize(size) { - const s = (size === 'small' || size === 'large') ? size : 'medium'; - // Drive the root rem size the same way the ☰/"A" button does: → ia.css :root[data-fontsize] (and the [data-fontsize] - // #panel rules). Setting only --font-size below never resized the shell. - document.documentElement.dataset.fontsize = s; - const map = { small: 'var(--small-font, 16px)', - medium: 'var(--medium-font, 20px)', - large: 'var(--large-font, 24px)' }; - document.documentElement.style.setProperty('--font-size', map[s]); -} - -// Cache the resolved theme/font choice in localStorage so the -// before-first-paint inline script in index.html can apply them -// synchronously without waiting for the cross-origin fetch. -function cacheForFastPaint(theme, fontSize) { - try { localStorage.setItem('data-kitchen-settings', JSON.stringify({ theme, fontSize })); } - catch (_) {} -} - -let currentTheme = 'system'; - -// Read the resolved settings off (set by the component from its -// `source` RDF) and apply them. Pure DOM — no rdflib. -function readAndApply() { - const sd = document.querySelector('sol-default'); - const scheme = sd?.getAttribute('color-scheme'); - const font = sd?.getAttribute('font-size'); - const theme = (scheme && SCHEME_TO_VALUE[scheme]) || 'system'; - const size = (font && FONT_TO_VALUE[font]) || 'medium'; - currentTheme = theme; - applyTheme(theme); - applyFontSize(size); - cacheForFastPaint(theme, size); -} - -readAndApply(); -// `sol-default-change` bubbles + is composed, so it reaches document. -document.addEventListener('sol-default-change', readAndApply); -// A form save persists the RDF but still holds the old resolved -// values — re-read its source first (its attribute changes then fire -// sol-default-change → readAndApply). Covers the settings page's open -// preferences form, which no reload-wires. -document.addEventListener('sol-form-save', () => { - const sd = document.querySelector('sol-default'); - if (sd && typeof sd.reload === 'function') sd.reload().catch(() => {}); - readAndApply(); - // The Data Kitchen Pod Browser settings form (ui:ignorePattern / ui:editorKeys) - // saves to sol-pod's own doc; re-apply to any mounted so a change - // shows without reopening the pod browser tab. - document.querySelectorAll('sol-pod').forEach((p) => { try { p.reload?.(); } catch (_) {} }); -}); - -if (window.matchMedia) { - window.matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', () => { - if (currentTheme === 'system') applyTheme('system'); - }); -} diff --git a/src/dk-shell.js b/src/dk-shell.js deleted file mode 100644 index d4eb63a..0000000 --- a/src/dk-shell.js +++ /dev/null @@ -1,51 +0,0 @@ -// dk is loaded directly as a module (index.html:
-
-

Solidos

-

A two-pane workspace for browsing your Solid pod with the - SolidOS data - browser. The left pane is a pod tree (the same - <sol-pod> file browser Podz uses). The right - pane is an iframe hosting SolidOS panes — header with login, - a left side-menu, the outline view, and a footer.

-
-
-

Opening a resource

-

Each row in the pod tree has a Solid-logo icon - (formerly a ⚙ in dk-podz). Click it to load that resource into - the right pane via SolidOS's GotoSubject — the - same pipeline a fresh SolidOS browser would use, just driven by - a JS call from dk instead of a URL navigation. No iframe reload - between clicks.

-

On first paint the right pane is blank. Once - <sol-pod> finishes discovering pods, dk - automatically primes the iframe with your pod root so the - SolidOS banner appears immediately. Clicking another item - replaces the outline without redrawing the banner.

-
-
-

Why an iframe

-

SolidOS / mashlib carries a lot of CSS and JS that wants to - own the whole page (a fixed banner, font and theme overrides, - a global theme attribute on <html>). When - mashlib was loaded directly into dk those rules bled into the - Home and Podz pages — different theme, distorted layouts, - coloured borders around panels. Confining SolidOS to an iframe - keeps every one of those side effects inside its own document.

-

The iframe page is hosted under dk's own origin - (plugins/solidos/solidos-host.html), so:

-
    -
  • Auth is shared. Solid-client-authn keeps - its session in IndexedDB scoped to the origin. One login - covers both panes.
  • -
  • Theme and font size are shared. When dk's - settings change, the iframe's - <html> gets the new - data-theme and --font-size - pushed in directly. No flash on iframe load because the - host page also reads dk's settings from - localStorage on first paint.
  • -
  • Standalone use. - plugins/solidos/solidos-host.html?uri=https://your.pod/foo - works on its own in a new tab — same code path, no dk shell - needed.
  • -
-
-
-

Logging in

-

Two login paths land you in the same session: the pod-side - <sol-login> button in the left pane's - header, or the SolidOS banner's login button on the right. - Whichever you use, both panes see you as logged in for - subsequent fetches.

-
-