From 6fb60dfe5084241c4a82eaeb0c6db55bac8c0ac4 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 10 Jan 2023 13:37:40 +0000 Subject: [PATCH] Initial commit --- .gitignore | 15 + .hgignore | 16 + CHANGELOG.md | 24 + LICENSE | 288 ++++ README.md | 80 + doc/intro.md | 3 + project.clj | 25 + report.html | 12 + resources/i18n/en-GB.edn | 37 + resources/public/css/ft-syntax-highlight.css | 1399 ++++++++++++++++++ resources/public/css/style.css | 175 +++ src/dog_and_duck/quack/constants.clj | 116 ++ src/dog_and_duck/quack/control_variables.clj | 40 + src/dog_and_duck/quack/core.clj | 195 +++ src/dog_and_duck/quack/objects.clj | 521 +++++++ src/dog_and_duck/quack/time.clj | 66 + src/dog_and_duck/quack/utils.clj | 289 ++++ test/quack/core_test.clj | 7 + 18 files changed, 3308 insertions(+) create mode 100644 .gitignore create mode 100644 .hgignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 doc/intro.md create mode 100644 project.clj create mode 100644 report.html create mode 100644 resources/i18n/en-GB.edn create mode 100644 resources/public/css/ft-syntax-highlight.css create mode 100644 resources/public/css/style.css create mode 100644 src/dog_and_duck/quack/constants.clj create mode 100644 src/dog_and_duck/quack/control_variables.clj create mode 100644 src/dog_and_duck/quack/core.clj create mode 100644 src/dog_and_duck/quack/objects.clj create mode 100644 src/dog_and_duck/quack/time.clj create mode 100644 src/dog_and_duck/quack/utils.clj create mode 100644 test/quack/core_test.clj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b1dd65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +pom.xml +pom.xml.asc +*.jar +*.class +/lib/ +/classes/ +/target/ +/checkouts/ +.lein-deps-sum +.lein-repl-history +.lein-plugins/ +.lein-failures +.nrepl-port +.cpcache/ +.calva \ No newline at end of file diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..a79aea1 --- /dev/null +++ b/.hgignore @@ -0,0 +1,16 @@ +syntax: glob +pom.xml +pom.xml.asc +*.jar +*.class +.gitignore +.git/** + +syntax: regexp +^.nrepl-port +^.prepl-port +^.lein-.* +^target/ +^classes/ +^checkouts/ +profiles.clj diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9a2c04c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Change Log +All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). + +## [Unreleased] +### Changed +- Add a new arity to `make-widget-async` to provide a different widget shape. + +## [0.1.1] - 2023-01-10 +### Changed +- Documentation on how to make the widgets. + +### Removed +- `make-widget-sync` - we're all async, all the time. + +### Fixed +- Fixed widget maker to keep working when daylight savings switches over. + +## 0.1.0 - 2023-01-10 +### Added +- Files from the new template. +- Widget maker public API - `make-widget-sync`. + +[Unreleased]: https://sourcehost.site/your-name/quack/compare/0.1.1...HEAD +[0.1.1]: https://sourcehost.site/your-name/quack/compare/0.1.0...0.1.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..88d9717 --- /dev/null +++ b/LICENSE @@ -0,0 +1,288 @@ +# GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +## Preamble + +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, +we want its recipients to know that what they have is not the +original, so that any problems introduced by others will not reflect +on the original authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at +all. + +The precise terms and conditions for copying, distribution and +modification follow. + +## TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work +based on the Program" means either the Program or any derivative work +under copyright law: that is to say, a work containing the Program or +a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is +included without limitation in the term "modification".) Each licensee +is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a +fee. + +**2.** You may modify your copy or copies of the Program or any +portion of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +**a)** You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + +**b)** You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any part +thereof, to be licensed as a whole at no charge to all third parties +under the terms of this License. + +**c)** If the modified program normally reads commands interactively +when run, you must cause it, when started running for such interactive +use in the most ordinary way, to print or display an announcement +including an appropriate copyright notice and a notice that there is +no warranty (or else, saying that you provide a warranty) and that +users may redistribute the program under these conditions, and telling +the user how to view a copy of this License. (Exception: if the +Program itself is interactive but does not normally print such an +announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +**a)** Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections 1 +and 2 above on a medium customarily used for software interchange; or, + +**b)** Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your cost of +physically performing source distribution, a complete machine-readable +copy of the corresponding source code, to be distributed under the +terms of Sections 1 and 2 above on a medium customarily used for +software interchange; or, + +**c)** Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is allowed +only for noncommercial distribution and only if you received the +program in object code or executable form with such an offer, in +accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and +will automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on +the Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +**7.** If, as a consequence of a court judgment or allegation of +patent infringement or for any other reason (not limited to patent +issues), conditions are imposed on you (whether by court order, +agreement or otherwise) that contradict the conditions of this +License, they do not excuse you from the conditions of this License. +If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, +then as a consequence you may not distribute the Program at all. For +example, if a patent license would not permit royalty-free +redistribution of the Program by all those who receive copies directly +or indirectly through you, then the only way you could satisfy both it +and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +**9.** The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + +**10.** If you wish to incorporate parts of the Program into other +free programs whose distribution conditions are different, write to +the author to ask for permission. For software which is copyrighted by +the Free Software Foundation, write to the Free Software Foundation; +we sometimes make exceptions for this. Our decision will be guided by +the two goals of preserving the free status of all derivatives of our +free software and of promoting the sharing and reuse of software +generally. + +**NO WARRANTY** + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +## END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d51045 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# quack + +> If it walks like a duck, and it quacks like a duck, it's a duck. + +A validator for [ActivityStreams](https://www.w3.org/TR/activitystreams-core/) documents. + +Part of the [dog-and-duck](https://github.com/simon-brooke/dog-and-duck) project, q.v. + +## Installation + +Download from http://example.com/FIXME. + +## Usage + +``` +java -jar target/dog-and-duck-0.1.0-standalone.jar -i resources/activitystreams-test-documents/vocabulary-ex10-jsonld.json -f html -o report.html -s info +``` + +Note that it is almost certain that in some places I have misinterpreted the spec. Of all 205 documents in the [activitystreams-test-documents repository](https://github.com/w3c-social/activitystreams-test-documents), not a single one passes validation, and that must be wrong. + +Nevertheless I think that this is a basis on which a useful validator can be built. Feedback and contributions welcome. + +## Options + +The full range of command-line switches is as follows: +``` + -i, --input SOURCE standard input The file or URL to validate + -o, --output DEST standard output The file to write to, defaults to standard out + -f, --format FORMAT :edn The format to output, one of `edn` `csv` `html` + -l, --language LANG en-GB The ISO 639-1 code for the language to output + -s, --severity LEVEL :info The minimum severity of faults to report + -h, --help Print this message and exit +``` + +Note, though, that internationalisation files for languages other than British English have not yet been written, and that one is not complete. + +The following severity levels are understood: + + 0. `info` things which are not actuallys fault, but issues noted during + validation; + 1. `minor` things which I consider to be faults, but which + don't actually breach the spec; + 2. `should` instances where the spec says something *SHOULD* + be done, which isn't; + 3. `must` instances where the spec says something *MUST* + be done, which isn't; + 4. `critical` instances where I believe the fault means that + the object cannot be meaningfully processed. + + +## Examples + +... + +## Documentation + +Full documentation is [here](https://simon-brooke.github.io/dog-and-duck/). + +### Bugs + +... + +### Any Other Sections +### That You Think +### Might be Useful + +## License + +Copyright © 2023 FIXME + +This program and the accompanying materials are made available under the +terms of the Eclipse Public License 2.0 which is available at +http://www.eclipse.org/legal/epl-2.0. + +This Source Code may also be made available under the following Secondary +Licenses when the conditions for such availability set forth in the Eclipse +Public License, v. 2.0 are satisfied: GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or (at your +option) any later version, with the GNU Classpath Exception which is available +at https://www.gnu.org/software/classpath/license.html. diff --git a/doc/intro.md b/doc/intro.md new file mode 100644 index 0000000..57dc6f3 --- /dev/null +++ b/doc/intro.md @@ -0,0 +1,3 @@ +# Introduction to quack + +TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..d7c0230 --- /dev/null +++ b/project.clj @@ -0,0 +1,25 @@ +(defproject dog-and-duck/quack "0.1.0-SNAPSHOT" + :cloverage {:output "docs/cloverage" + :codecov? true + :emma-xml? true} +:codox {:metadata {:doc "**TODO**: write docs" + :doc/format :markdown} + :output-path "docs/codox" + :source-uri "https://github.com/simon-brooke/quack/blob/master/{filepath}#L{line}"} + :dependencies [[com.taoensso/timbre "6.0.4"] + [hiccup "1.0.5"] + [mvxcvi/clj-pgp "1.1.0"] + [org.clojars.simon_brooke/internationalisation "1.0.5"] + [org.clojure/clojure "1.10.3"] + [org.clojure/data.json "2.4.0"] + [org.clojure/tools.cli "1.0.214"] + [trptr/java-wrapper "0.2.3"]] + :description "A validator for ActivityStreams." + :license {:name "GPL-2.0-or-later" + :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} + :main ^:skip-aot dog-and-duck.quack.core + :profiles {:uberjar {:aot :all + :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}} + :repl-options {:init-ns dog-and-duck.quack.core} + :target-path "target/%s" + :url "http://example.com/FIXME") diff --git a/report.html b/report.html new file mode 100644 index 0000000..03ab795 --- /dev/null +++ b/report.html @@ -0,0 +1,12 @@ +Validation report for ../dog-and-duck/resources/activitystreams-test-documents/vocabulary-ex10-jsonld.json

Validation report for ../dog-and-duck/resources/activitystreams-test-documents/vocabulary-ex10-jsonld.json

Generated on 2023-01-10T13:21:52.260571 by dog-and-duck/quack 0.1.0-SNAPSHOT

The following faults were found

@contexttypenarrativefaultidseverity
https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.htmlFaultSection 3 of the ActivityPub specification states Implementers SHOULD include the ActivityPub context in their object definitions`.no-contexthttps://mason/fault/42756:1673356912254should
https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.htmlFaultThe ActivityPub specification allows objects without `id` fields only if they are intentionally transient; even so it is preferred that the object should have an explicit null id.no-id-transienthttps://mason/fault/42756:1673356912258minor

text-analysed

{"@context":"http:\/\/www.w3.org\/ns\/activitystreams",
+ "name":
+ "Sally added a picture of her cat to her cat picture collection",
+ "type":"Add",
+ "actor":{"type":"Person", "name":"Sally"},
+ "object":
+ {"type":"Image",
+  "name":"A picture of my cat",
+  "url":"http:\/\/example.org\/img\/cat.png"},
+ "origin":{"type":"Collection", "name":"Camera Roll"},
+ "target":{"type":"Collection", "name":"My Cat Pictures"}}
+
\ No newline at end of file diff --git a/resources/i18n/en-GB.edn b/resources/i18n/en-GB.edn new file mode 100644 index 0000000..949cf19 --- /dev/null +++ b/resources/i18n/en-GB.edn @@ -0,0 +1,37 @@ +;;; Copyright (C) Simon Brooke, 2023 + +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. + +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. + +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +;; Actual fault messages to which fault codes resolve: English language version. +{:by "by" + :expected-collection "A collection was expected, but was not found." + :faults-found "The following faults were found" + :generated-on "Generated on" + :id-not-https "Publicly facing content SHOULD use HTTPS URIs" + :id-not-uri "identifiers must be publicly dereferencable URIs" + :no-context "Section 3 of the ActivityPub specification states Implementers SHOULD include the ActivityPub context in their object definitions`." + :no-faults-found "No faults were found." + :no-id-persistent "Persistent objects MUST have unique global identifiers." + :no-id-transient "The ActivityPub specification allows objects without `id` fields only if they are intentionally transient; even so it is preferred that the object should have an explicit null id." + :no-inbox "Actor objects MUST have an `inbox` property, whose value MUST be a reference to an ordered collection." + :no-items-collection "A collection expected to be simple had no items." + :no-outbox "Actor objects MUST have an `outbox` property, whose value MUST be a reference to an ordered collection." + :no-type "The ActivityPub specification states that the `type` field is optional, but it is hard to process objects with no known type." + :not-actor-type "The `type` value of the object was not a recognised actor type." + :not-valid-date-time "A date/time of format required for `xsd:dateTime` was expected but was not found." + :null-id-persistent "Persistent objects MUST have non-null identifiers." + :not-an-object "ActivityStreams object must be JSON objects." + :text-analysed "Text analysed" + :validation-report-for "Validation report for"} \ No newline at end of file diff --git a/resources/public/css/ft-syntax-highlight.css b/resources/public/css/ft-syntax-highlight.css new file mode 100644 index 0000000..8649bcc --- /dev/null +++ b/resources/public/css/ft-syntax-highlight.css @@ -0,0 +1,1399 @@ +/* copied from https://github.com/soulshined/ft-syntax-highlight, but I choose + not to use Google fonts for privacy/legal reasons */ + +/* @import url('https://fonts.googleapis.com/css?family=Source+Code+Pro:200,300,400'); */ + +pre.ft-syntax-highlight { + display: block; + position: relative; + padding: 30px 0 0; + font-size: .85rem; letter-spacing: 0.5px; color: lightgrey; + + background-color: #2d2832; + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='304' height='304' viewBox='0 0 304 304' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M44.1 224a5 5 0 1 1 0 2H0v-2h44.1zm160 48a5 5 0 1 1 0 2H82v-2h122.1zm57.8-46a5 5 0 1 1 0-2H304v2h-42.1zm0 16a5 5 0 1 1 0-2H304v2h-42.1zm6.2-114a5 5 0 1 1 0 2h-86.2a5 5 0 1 1 0-2h86.2zm-206-48a5 5 0 1 1 0 2H0v-2h12.1zm185.8 34a5 5 0 1 1 0-2h86.2a5 5 0 1 1 0 2h-86.2zM258 12.1a5 5 0 1 1-2 0V0h2v12.1zm-64 208a5 5 0 1 1-2 0v-54.2a5 5 0 1 1 2 0v54.2zm48-198.2a5 5 0 1 0-2 0V82h64v-2h-62V21.9zm16 16a5 5 0 1 0-2 0V66h48v-2h-46V37.9zm-128 96a5 5 0 1 0-2 0V210h16v10.1a5 5 0 1 0 2 0V208h-16v-74.1zm-5.9-21.9a5 5 0 1 1 0 2H114v48H85.9a5 5 0 1 1 0-2H112v-48h12.1zm-6.2 130a5 5 0 1 1 0-2H176v-74.1a5 5 0 1 1 2 0V242h-60.1zm-16-64a5 5 0 1 1 0-2H114v48h10.1a5 5 0 1 1 0 2H112v-48h-10.1zM66 284.1a5 5 0 1 1-2 0V274H50v30h-2v-32h18v12.1zM236.1 176a5 5 0 1 1 0 2H226v94h48v32h-2v-30h-48v-98h12.1zm25.8-30a5 5 0 1 1 0-2H274v44.1a5 5 0 1 1-2 0V146h-10.1zm-64 96a5 5 0 1 1 0-2H208v-80h16v-14h-42.1a5 5 0 1 1 0-2H226v18h-16v80h-12.1zm86.2-210a5 5 0 1 1 0 2H272V0h2v32h10.1zM98 101.9a5 5 0 1 0-2 0V144H53.9a5 5 0 1 0 0 2H98v-44.1zM53.9 34a5 5 0 1 1 0-2H80V0h2v34H53.9zm60.1 3.9a5 5 0 1 0-2 0V64H80v64H69.9a5 5 0 1 0 0 2H82V66h32V37.9zM101.9 82a5 5 0 1 1 0-2H128V37.9a5 5 0 1 1 2 0V82h-28.1zm16-64a5 5 0 1 1 0-2H146v44.1a5 5 0 1 1-2 0V18h-26.1zm102.2 270a5 5 0 1 1 0 2H98v14h-2v-16h124.1zM242 149.9a5 5 0 1 0-2 0V162h16v30h-16v66h48v46h2v-48h-48v-62h16v-34h-16v-10.1zM53.9 18a5 5 0 1 1 0-2H64V2H48V0h18v18H53.9zm112 32a5 5 0 1 1 0-2H192V0h50v2h-48v48h-28.1zm-48-48a5 5 0 0 1-9.8-2h2.07a3 3 0 1 0 5.66 0H178v34h-18V21.9a5 5 0 1 1 2 0V32h14V2h-58.1zm0 96a5 5 0 1 1 0-2H137l32-32h39V21.9a5 5 0 1 1 2 0V66h-40.172l-32 32H117.9zm28.1 90.1a5 5 0 1 1-2 0v-76.513L175.586 80H224V21.9a5 5 0 1 1 2 0V82h-49.586L146 112.414V188.1zm16 32a5 5 0 1 1-2 0v-99.513L184.586 96H300.1a5.004 5.004 0 0 1 3.9-3.9v2.07a3.004 3.004 0 0 0 0 5.66v2.07a5.004 5.004 0 0 1-3.9-3.9H185.414L162 121.414V220.1zm-144-64a5 5 0 1 1-2 0v-3.513l48-48V48h32V0h2v50H66v55.413l-48 48v2.687zM50 53.9a5 5 0 1 0-2 0v42.686l-48 48V210h28.1a5 5 0 1 0 0-2H2v-62.586l48-48V53.9zm-16 16a5 5 0 1 0-2 0v18.686l-32 32v2.828l34-34V69.9zM12.1 32a5 5 0 1 1 0 2H9.414L0 43.414v-2.828L8.586 32H12.1zm265.8 18a5 5 0 1 1 0-2h18.686L304 40.586v2.828L297.414 50H277.9zm-16 160a5 5 0 1 1 0-2H288v-71.413l16-16v2.827l-14 14V210h-28.1zm-208 32a5 5 0 1 1 0-2H64v-22.586L40.586 194H21.9a5 5 0 1 1 0-2h19.513L66 216.586V242H53.9zm150.2 14a5 5 0 1 1 0 2H96v-56.598L56.598 162H37.9a5 5 0 1 1 0-2h19.502L98 200.598V256h106.1zm-150.2 2a5 5 0 1 1 0-2H80v-46.586L48.586 178H21.9a5 5 0 1 1 0-2h27.513L82 208.586V258H53.9zM97 100a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-48 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 96a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-144a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-96 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm96 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-32 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM49 36a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-32 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM33 68a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 240a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm80-176a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm112 176a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM17 180a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM17 84a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM34 39.793V0h-2v40.586L8.586 64H0v2h9.413L34 41.414v-1.62zM2 300.1V258h14v46h2v-48H0v46.17A3.004 3.004 0 0 1 3.83 304H5.9a5.004 5.004 0 0 0-3.9-3.9zM34 241v63h-2v-62H0v-2h34v1zM17 18h1V0h-2v16H0v2h17zm273-2V0h-2v18h16v-2h-14zm-32 273v15h-2v-14h-14v14h-2v-16h18v1zM0 92.1A5.002 5.002 0 0 1 6 97a5.002 5.002 0 0 1-6 4.9v-2.07a3 3 0 1 0 0-5.66V92.1zM80 272h2v32h-2v-32zm37.9 32a5 5 0 0 0-9.8 0h2.07a3.004 3.004 0 0 1 5.66 0h2.07zM5.9 0A5.002 5.002 0 0 1 0 5.9V3.83A3 3 0 0 0 3.83 0H5.9zm294.2 0a5 5 0 0 0 3.9 5.9V3.83A3.004 3.004 0 0 1 302.17 0h-2.07zm3.9 300.1a5.004 5.004 0 0 0-3.9 3.9h2.07a3.016 3.016 0 0 1 1.83-1.83v-2.07z' fill='%2345404b' fill-opacity='.1' fill-rule='evenodd'/%3E%3C/svg%3E"); +} +pre.ft-syntax-highlight::before { + position: absolute; + top: 0; left: 0; + width: 100%; + padding: 10px 0; + + text-indent: 25px; + font-weight: bold; + font-size: 1.2rem; + text-transform: uppercase; + color: #888; + letter-spacing: 0.8px; + content: attr(data-syntax); + direction: rtl; +} +pre.ft-syntax-highlight code { + font-family: 'Source Code Pro', monospace; + counter-reset: line -1; + + display: block; + margin: -25px 0 -28px; + padding: 0 10px 10px 0; + max-height: 400px; + overflow: auto; +} +pre.ft-syntax-highlight code span.newline::before { + position: relative; + right: 20px; + + color: lightgrey; + + counter-increment: line; + content: counter(line); + font-weight: 200; +} +pre.ft-syntax-highlight code span.newline:nth-of-type(1n+11)::before { + position: relative; + right: 28px; +} +pre.ft-syntax-highlight code span.newline:nth-of-type(1n+101)::before { + position: relative; + right: 36px; +} + +@media all and (max-width: 550px) { + pre.ft-syntax-highlight { + /*this ensures a minimum font size of 9px is maintained + when viewport is less than 550px + calculating against vw gives it a responsive feel*/ + font-size: calc(9px + 0.8vw); + } +} + +@media all and (max-width: 225px) { + pre.ft-syntax-highlight[data-ui-theme="macosx" i]::before { + text-align: left; + text-indent: 55%; + } + pre.ft-syntax-highlight[data-ui-theme="win95" i]::after { + display: none; + } +} + +/* scrollbar customizations (not applicable to all browsers) */ +pre.ft-syntax-highlight code::-webkit-scrollbar-track { + background-color: rgba(0,0,0,0); +} +pre.ft-syntax-highlight code::-webkit-scrollbar { + width: 10px; height: 10px; + border-radius: 10px; + background-color: rgba(255,255,255,0.4); +} +pre.ft-syntax-highlight code::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 0 0 6px rgba(0,0,0,.3); + background-color: rgba(255,255,255,0.5); +} +pre.ft-syntax-highlight code::-webkit-scrollbar-corner { + display: none; +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4"] code::-webkit-scrollbar-thumb, +pre.ft-syntax-highlight[data-ui-theme="burberry"] code::-webkit-scrollbar-thumb, +pre.ft-syntax-highlight[data-ui-theme="light"] code::-webkit-scrollbar-thumb, +pre.ft-syntax-highlight[data-ui-theme="simple"] code::-webkit-scrollbar-thumb { + background-color: rgba(0,0,0,0.4); +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4"] code::-webkit-scrollbar, +pre.ft-syntax-highlight[data-ui-theme="burberry"] code::-webkit-scrollbar, +pre.ft-syntax-highlight[data-ui-theme="light"] code::-webkit-scrollbar, +pre.ft-syntax-highlight[data-ui-theme="simple"] code::-webkit-scrollbar { + background-color: rgba(0,0,0,0.4); +} + +/*Tooltips*/ +pre.ft-syntax-highlight[data-showTooltips="true" i] code span:not(.newline):not(.url) { + position: relative; + cursor: help; +} +pre.ft-syntax-highlight[data-showTooltips="true" i] code span:not(.newline)::before, +pre.ft-syntax-highlight[data-showTooltips="true" i] code span:not(.newline)::after { + text-transform: none; + font-size: 0.95em; + line-height: 1; + user-select: none; + pointer-events: none; + position: absolute; + display: none; + opacity: 0; + -webkit-animation: tooltips-vert 300ms ease-out forwards; + -moz-animation: tooltips-vert 300ms ease-out forwards; + -o-animation: tooltips-vert 300ms ease-out forwards; + animation: tooltips-vert 300ms ease-out forwards; + left: 50%; + -webkit-transform: translate(-50%, -0.5em); + -moz-transform: translate(-50%, -0.5em); + -ms-transform: translate(-50%, -0.5em); + -o-transform: translate(-50%, -0.5em); + transform: translate(-50%, -0.5em); +} +pre.ft-syntax-highlight[data-showTooltips="true" i] code span:not(.newline)::before { + content: ''; + border: 8px solid transparent; + z-index: 1001; + bottom: 100%; + border-bottom-width: 0; + border-top-color: ghostwhite; +} +pre.ft-syntax-highlight[data-showTooltips="true" i] code span:not(.newline)::after { + font-family: Helvetica, sans-serif; + text-align: center; + min-width: 3em; + max-width: 21em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 1ch 1.5ch; + border-radius: .3ch; + -webkit-box-shadow: 0 1em 2em -.5em rgba(0, 0, 0, 0.35); + -moz-box-shadow: 0 1em 2em -.5em rgba(0, 0, 0, 0.35); + box-shadow: 0 1em 2em -.5em rgba(0, 0, 0, 0.35); + background: ghostwhite; + color: black; + z-index: 1000; + bottom: calc(100% + 8px); +} +pre.ft-syntax-highlight[data-showTooltips="true" i] code span:not(.newline):hover::before, +pre.ft-syntax-highlight[data-showTooltips="true" i] code span:not(.newline):hover::after { + display: block; +} + /*=> tooltip text*/ +pre.ft-syntax-highlight[data-showTooltips="true" i] code span.comment::after { + content: 'comment'; +} +pre.ft-syntax-highlight[data-showTooltips="true" i] code span.value::after { + content: 'value'; +} + +/* TOOLTIP KEYFRAMES */ +@-webkit-keyframes tooltips-vert { + to { opacity: 0.9; -webkit-transform: translate(-50%, 0); transform: translate(-50%, 0); } +} +@-moz-keyframes tooltips-vert { + to { opacity: 0.9; -moz-transform: translate(-50%, 0); transform: translate(-50%, 0); } +} +@-o-keyframes tooltips-vert { + to { opacity: 0.9; -o-transform: translate(-50%, 0); transform: translate(-50%, 0); } +} +@keyframes tooltips-vert { + to { opacity: 0.9; -webkit-transform: translate(-50%, 0); -moz-transform: translate(-50%, 0); -o-transform: translate(-50%, 0); transform: translate(-50%, 0); } +} + +/* UI SYNTAX THEMES */ +/* => UI themes : Beach */ +pre.ft-syntax-highlight[data-ui-theme="beach" i] { + background-image: url(); + border: 1px solid rgba(0,0,0,.125); + border-radius: .25rem; + color: #888; + background-color: beige; +} +pre.ft-syntax-highlight[data-ui-theme="beach" i]::before { + direction: ltr; + font-weight: normal; + color: white; + background: -webkit-linear-gradient(79deg, cornflowerblue 0%, MediumTurquoise 70%, turquoise); + background: -moz-linear-gradient(79deg, cornflowerblue 0%, MediumTurquoise 70%, turquoise); + background: -o-linear-gradient(79deg, cornflowerblue 0%, MediumTurquoise 70%, turquoise); + background: linear-gradient(11deg, cornflowerblue 0%, MediumTurquoise 70%, turquoise); +} +pre.ft-syntax-highlight[data-ui-theme="beach" i] code { + margin-top: 0; +} +pre.ft-syntax-highlight[data-ui-theme="beach" i] code span.newline::before { + color: black; +} +pre.ft-syntax-highlight[data-ui-theme="beach" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: cornflowerblue; +} +pre.ft-syntax-highlight[data-ui-theme="beach" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: cornflowerblue; + color: ghostwhite; +} +/* => UI themes : Bootstrap 4 theme */ +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i] { + background-color: white; + background-image: url(); + border: 1px solid rgba(0,0,0,.125); + border-radius: .25rem; + color: #888; +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i]::before { + direction: ltr; + font-weight: normal; + color: black; + background-color: #f7f7f9; + border-bottom: 1px solid rgba(0,0,0,.125); +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i] code { + margin-top: 0; +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i] code span.comment { + color: rgba(160,160,160, 1); + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i] code span.newline::before { + color: black; +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i] code span.value { + color: dimgray; +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: #333; +} +pre.ft-syntax-highlight[data-ui-theme="bootstrap4" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: #333; + color: ghostwhite; +} +/* => UI themes : Burberry theme */ +pre.ft-syntax-highlight[data-ui-theme="burberry" i] { + background-color: beige; + background-image: url(); + border: 1px solid #797C73; + border-radius: .25rem; + color: #888; +} +pre.ft-syntax-highlight[data-ui-theme="burberry" i]::before { + direction: ltr; + font-weight: bold; + color: white; + border-bottom: 1px solid #797C73; + background-color: hsl(34, 53%, 82%); + background-image: -webkit-repeating-linear-gradient( + 45deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 120px, hsla(197, 62%, 11%, 0.5) 120px, hsla(197, 62%, 11%, 0.5) 140px + ), + -webkit-repeating-linear-gradient(315deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 140px, hsla(197, 62%, 11%, 0.5) 140px, hsla(197, 62%, 11%, 0.5) 160px); + background-image: -moz-repeating-linear-gradient( + 45deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 120px, hsla(197, 62%, 11%, 0.5) 120px, hsla(197, 62%, 11%, 0.5) 140px + ), + -moz-repeating-linear-gradient(315deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 140px, hsla(197, 62%, 11%, 0.5) 140px, hsla(197, 62%, 11%, 0.5) 160px); + background-image: -o-repeating-linear-gradient( + 45deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 120px, hsla(197, 62%, 11%, 0.5) 120px, hsla(197, 62%, 11%, 0.5) 140px + ), + -o-repeating-linear-gradient(315deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 140px, hsla(197, 62%, 11%, 0.5) 140px, hsla(197, 62%, 11%, 0.5) 160px); + background-image: repeating-linear-gradient( + 45deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 120px, hsla(197, 62%, 11%, 0.5) 120px, hsla(197, 62%, 11%, 0.5) 140px + ), + repeating-linear-gradient(135deg, transparent 5px, hsla(197, 62%, 11%, 0.5) 5px, hsla(197, 62%, 11%, 0.5) 10px, + hsla(5, 53%, 63%, 0) 10px, hsla(5, 53%, 63%, 0) 35px, hsla(5, 53%, 63%, 0.5) 35px, hsla(5, 53%, 63%, 0.5) 40px, + hsla(197, 62%, 11%, 0.5) 40px, hsla(197, 62%, 11%, 0.5) 50px, hsla(197, 62%, 11%, 0) 50px, hsla(197, 62%, 11%, 0) 60px, + hsla(5, 53%, 63%, 0.5) 60px, hsla(5, 53%, 63%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 70px, hsla(35, 91%, 65%, 0.5) 80px, + hsla(35, 91%, 65%, 0) 80px, hsla(35, 91%, 65%, 0) 90px, hsla(5, 53%, 63%, 0.5) 90px, hsla(5, 53%, 63%, 0.5) 110px, + hsla(5, 53%, 63%, 0) 110px, hsla(5, 53%, 63%, 0) 140px, hsla(197, 62%, 11%, 0.5) 140px, hsla(197, 62%, 11%, 0.5) 160px); +} +pre.ft-syntax-highlight[data-ui-theme="burberry" i] code { + margin-top: 0; +} +pre.ft-syntax-highlight[data-ui-theme="burberry" i] code span.comment { + color: rgba(160,160,160, 1); + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="burberry" i] code span.newline::before { + color: #797C73; +} +pre.ft-syntax-highlight[data-ui-theme="burberry" i] code span.value { + color: dimgray; +} +pre.ft-syntax-highlight[data-ui-theme="burberry" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: #404F4F; +} +pre.ft-syntax-highlight[data-ui-theme="burberry" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: #404F4F; + color: ghostwhite; +} +/* => UI themes : christmas theme */ +pre.ft-syntax-highlight[data-ui-theme="christmas" i] { + background-color: seagreen; + border: 1px solid rgba(255,0,0,.3); + border-radius: .25rem; + color: #DDD; + background: -webkit-radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent), + -webkit-radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent) 50px 50px, + -webkit-linear-gradient(rgba(255,250,250,0.035) 8px, transparent 8px) 0 -4px, + -webkit-linear-gradient(left, rgba(255,250,250,0.035) 8px, transparent 8px) -4px 0; + background: -moz-radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent), + -moz-radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent) 50px 50px, + -moz-linear-gradient(rgba(255,250,250,0.035) 8px, transparent 8px) 0 -4px, + -moz-linear-gradient(left, rgba(255,250,250,0.035) 8px, transparent 8px) -4px 0; + background: -o-radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent), + -o-radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent) 50px 50px, + -o-linear-gradient(rgba(255,250,250,0.035) 8px, transparent 8px) 0 -4px, + -o-linear-gradient(left, rgba(255,250,250,0.035) 8px, transparent 8px) -4px 0; + background: radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent), + radial-gradient(circle, transparent 20%, seagreen 20%, seagreen 80%, transparent 80%, transparent) 50px 50px, + linear-gradient(rgba(255,250,250,0.035) 8px, transparent 8px) 0 -4px, + linear-gradient(90deg, rgba(255,250,250,0.035) 8px, transparent 8px) -4px 0; + background-color: seagreen; + background-size:100px 100px, 100px 100px, 50px 50px, 50px 50px; +} +pre.ft-syntax-highlight[data-ui-theme="christmas" i]::before { + direction: ltr; + font-weight: bold; + color: black; + border-bottom: 1px solid red; + background: -webkit-repeating-linear-gradient( + 45deg, + #f4f4d7, + #f4f4d7 10px, + red 10px, + #f4f4d7 15px, + red 20px, + #f4f4d7 20px, + #f4f4d7 40px, + red 40px, + red 60px, + #f4f4d7 60px, + #f4f4d7 90px, + red 90px, + red 110px + ); + background: -moz-repeating-linear-gradient( + 45deg, + #f4f4d7, + #f4f4d7 10px, + red 10px, + #f4f4d7 15px, + red 20px, + #f4f4d7 20px, + #f4f4d7 40px, + red 40px, + red 60px, + #f4f4d7 60px, + #f4f4d7 90px, + red 90px, + red 110px + ); + background: -o-repeating-linear-gradient( + 45deg, + #f4f4d7, + #f4f4d7 10px, + red 10px, + #f4f4d7 15px, + red 20px, + #f4f4d7 20px, + #f4f4d7 40px, + red 40px, + red 60px, + #f4f4d7 60px, + #f4f4d7 90px, + red 90px, + red 110px + ); + background: repeating-linear-gradient( + 45deg, + #f4f4d7, + #f4f4d7 10px, + red 10px, + #f4f4d7 15px, + red 20px, + #f4f4d7 20px, + #f4f4d7 40px, + red 40px, + red 60px, + #f4f4d7 60px, + #f4f4d7 90px, + red 90px, + red 110px + ); +} +pre.ft-syntax-highlight[data-ui-theme="christmas" i] code { + margin-top: 0; +} +pre.ft-syntax-highlight[data-ui-theme="christmas" i] code span.comment { + color: rgba(200,200,200, 1); + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="christmas" i] code span.newline::before { + color: white; +} +pre.ft-syntax-highlight[data-ui-theme="christmas" i] code span.value { + color: dimgray; +} +pre.ft-syntax-highlight[data-ui-theme="christmas" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: tomato; +} +pre.ft-syntax-highlight[data-ui-theme="christmas" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: tomato; + color: ghostwhite; +} +/* => UI themes : halloween theme */ +pre.ft-syntax-highlight[data-ui-theme="halloween" i] { + background-color: #222; + border: 1px solid rgba(255,165,0,1); + border-radius: .25rem; + color: #888; + background-image: url("data:image/svg+xml,%3Csvg width='180' height='180' viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M82.42 180h-1.415L0 98.995v-2.827L6.167 90 0 83.833V81.004L81.005 0h2.827L90 6.167 96.167 0H98.996L180 81.005v2.827L173.833 90 180 96.167V98.996L98.995 180h-2.827L90 173.833 83.833 180H82.42zm0-1.414L1.413 97.58 8.994 90l-7.58-7.58L82.42 1.413 90 8.994l7.58-7.58 81.006 81.005-7.58 7.58 7.58 7.58-81.005 81.006-7.58-7.58-7.58 7.58zM175.196 0h-25.832c1.033 2.924 2.616 5.59 4.625 7.868C152.145 9.682 151 12.208 151 15c0 5.523 4.477 10 10 10 1.657 0 3 1.343 3 3v4h16V0h-4.803c.51.883.803 1.907.803 3 0 3.314-2.686 6-6 6s-6-2.686-6-6c0-1.093.292-2.117.803-3h10.394-13.685C161.18.938 161 1.948 161 3v4c-4.418 0-8 3.582-8 8s3.582 8 8 8c2.76 0 5 2.24 5 5v2h4v-4h2v4h4v-4h2v4h2V0h-4.803zm-15.783 0c-.27.954-.414 1.96-.414 3v2.2c-1.25.254-2.414.74-3.447 1.412-1.716-1.93-3.098-4.164-4.054-6.612h7.914zM180 17h-3l2.143-10H180v10zm-30.635 163c-.884-2.502-1.365-5.195-1.365-8 0-13.255 10.748-24 23.99-24H180v32h-30.635zm12.147 0c.5-1.416 1.345-2.67 2.434-3.66l-1.345-1.48c-1.498 1.364-2.62 3.136-3.186 5.14H151.5c-.97-2.48-1.5-5.177-1.5-8 0-12.15 9.84-22 22-22h8v30h-18.488zm13.685 0c-1.037-1.793-2.976-3-5.197-3-2.22 0-4.16 1.207-5.197 3h10.394zM0 148h8.01C21.26 148 32 158.742 32 172c0 2.805-.48 5.498-1.366 8H0v-32zm0 2h8c12.15 0 22 9.847 22 22 0 2.822-.53 5.52-1.5 8h-7.914c-.567-2.004-1.688-3.776-3.187-5.14l-1.346 1.48c1.09.99 1.933 2.244 2.434 3.66H0v-30zm15.197 30c-1.037-1.793-2.976-3-5.197-3-2.22 0-4.16 1.207-5.197 3h10.394zM0 32h16v-4c0-1.657 1.343-3 3-3 5.523 0 10-4.477 10-10 0-2.794-1.145-5.32-2.992-7.134C28.018 5.586 29.6 2.924 30.634 0H0v32zm0-2h2v-4h2v4h4v-4h2v4h4v-2c0-2.76 2.24-5 5-5 4.418 0 8-3.582 8-8s-3.582-8-8-8V3c0-1.052-.18-2.062-.512-3H0v30zM28.5 0c-.954 2.448-2.335 4.683-4.05 6.613-1.035-.672-2.2-1.16-3.45-1.413V3c0-1.04-.144-2.046-.414-3H28.5zM0 17h3L.857 7H0v10zM15.197 0c.51.883.803 1.907.803 3 0 3.314-2.686 6-6 6S4 6.314 4 3c0-1.093.292-2.117.803-3h10.394zM109 115c-1.657 0-3 1.343-3 3v4H74v-4c0-1.657-1.343-3-3-3-5.523 0-10-4.477-10-10 0-2.793 1.145-5.318 2.99-7.132C60.262 93.638 58 88.084 58 82c0-13.255 10.748-24 23.99-24h16.02C111.26 58 122 68.742 122 82c0 6.082-2.263 11.636-5.992 15.866C117.855 99.68 119 102.206 119 105c0 5.523-4.477 10-10 10zm0-2c-2.76 0-5 2.24-5 5v2h-4v-4h-2v4h-4v-4h-2v4h-4v-4h-2v4h-4v-4h-2v4h-4v-2c0-2.76-2.24-5-5-5-4.418 0-8-3.582-8-8s3.582-8 8-8v-4c0-2.64 1.136-5.013 2.946-6.66L72.6 84.86C70.39 86.874 69 89.775 69 93v2.2c-1.25.254-2.414.74-3.447 1.412C62.098 92.727 60 87.61 60 82c0-12.15 9.84-22 22-22h16c12.15 0 22 9.847 22 22 0 5.61-2.097 10.728-5.55 14.613-1.035-.672-2.2-1.16-3.45-1.413V93c0-3.226-1.39-6.127-3.6-8.14l-1.346 1.48C107.864 87.987 109 90.36 109 93v4c4.418 0 8 3.582 8 8s-3.582 8-8 8zM90.857 97L93 107h-6l2.143-10h1.714zM80 99c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm20 0c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6z' fill='%239C92AC' fill-opacity='0.05' fill-rule='evenodd'/%3E%3C/svg%3E"); +} +pre.ft-syntax-highlight[data-ui-theme="halloween" i]::before { + direction: ltr; + font-weight: normal; + color: white; + background-color: orange; + border-bottom: 1px solid orange; +} +pre.ft-syntax-highlight[data-ui-theme="halloween" i] code { + margin-top: 0; +} +pre.ft-syntax-highlight[data-ui-theme="halloween" i] code span.comment { + color: rgba(160,160,160, 1); + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="halloween" i] code span.newline::before { + color: orange; +} +pre.ft-syntax-highlight[data-ui-theme="halloween" i] code span.value { + color: dimgray; +} +pre.ft-syntax-highlight[data-ui-theme="halloween" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: orange; +} +pre.ft-syntax-highlight[data-ui-theme="halloween" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: orange; + color: white; +} +/* => UI themes : Light theme */ +pre.ft-syntax-highlight[data-ui-theme="light" i] { + color: black; + font-weight: 400; + background-color: ghostwhite; + box-shadow: 1px 0px 2px darkgrey; + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='304' height='304' viewBox='0 0 304 304' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M44.1 224a5 5 0 1 1 0 2H0v-2h44.1zm160 48a5 5 0 1 1 0 2H82v-2h122.1zm57.8-46a5 5 0 1 1 0-2H304v2h-42.1zm0 16a5 5 0 1 1 0-2H304v2h-42.1zm6.2-114a5 5 0 1 1 0 2h-86.2a5 5 0 1 1 0-2h86.2zm-256-48a5 5 0 1 1 0 2H0v-2h12.1zm185.8 34a5 5 0 1 1 0-2h86.2a5 5 0 1 1 0 2h-86.2zM258 12.1a5 5 0 1 1-2 0V0h2v12.1zm-64 208a5 5 0 1 1-2 0v-54.2a5 5 0 1 1 2 0v54.2zm48-198.2a5 5 0 1 0-2 0V82h64v-2h-62V21.9zm16 16a5 5 0 1 0-2 0V66h48v-2h-46V37.9zm-128 96a5 5 0 1 0-2 0V210h16v10.1a5 5 0 1 0 2 0V208h-16v-74.1zm-5.9-21.9a5 5 0 1 1 0 2H114v48H85.9a5 5 0 1 1 0-2H112v-48h12.1zm-6.2 130a5 5 0 1 1 0-2H176v-74.1a5 5 0 1 1 2 0V242h-60.1zm-16-64a5 5 0 1 1 0-2H114v48h10.1a5 5 0 1 1 0 2H112v-48h-10.1zM66 284.1a5 5 0 1 1-2 0V274H50v30h-2v-32h18v12.1zM236.1 176a5 5 0 1 1 0 2H226v94h48v32h-2v-30h-48v-98h12.1zm25.8-30a5 5 0 1 1 0-2H274v44.1a5 5 0 1 1-2 0V146h-10.1zm-64 96a5 5 0 1 1 0-2H208v-80h16v-14h-42.1a5 5 0 1 1 0-2H226v18h-16v80h-12.1zm86.2-210a5 5 0 1 1 0 2H272V0h2v32h10.1zM98 101.9a5 5 0 1 0-2 0V144H53.9a5 5 0 1 0 0 2H98v-44.1zM53.9 34a5 5 0 1 1 0-2H80V0h2v34H53.9zm60.1 3.9a5 5 0 1 0-2 0V64H80v64H69.9a5 5 0 1 0 0 2H82V66h32V37.9zM101.9 82a5 5 0 1 1 0-2H128V37.9a5 5 0 1 1 2 0V82h-28.1zm16-64a5 5 0 1 1 0-2H146v44.1a5 5 0 1 1-2 0V18h-26.1zm102.2 270a5 5 0 1 1 0 2H98v14h-2v-16h124.1zM242 149.9a5 5 0 1 0-2 0V162h16v30h-16v66h48v46h2v-48h-48v-62h16v-34h-16v-10.1zM53.9 18a5 5 0 1 1 0-2H64V2H48V0h18v18H53.9zm112 32a5 5 0 1 1 0-2H192V0h50v2h-48v48h-28.1zm-48-48a5 5 0 0 1-9.8-2h2.07a3 3 0 1 0 5.66 0H178v34h-18V21.9a5 5 0 1 1 2 0V32h14V2h-58.1zm0 96a5 5 0 1 1 0-2H137l32-32h39V21.9a5 5 0 1 1 2 0V66h-40.172l-32 32H117.9zm28.1 90.1a5 5 0 1 1-2 0v-76.513L175.586 80H224V21.9a5 5 0 1 1 2 0V82h-49.586L146 112.414V188.1zm16 32a5 5 0 1 1-2 0v-99.513L184.586 96H300.1a5.004 5.004 0 0 1 3.9-3.9v2.07a3.004 3.004 0 0 0 0 5.66v2.07a5.004 5.004 0 0 1-3.9-3.9H185.414L162 121.414V220.1zm-144-64a5 5 0 1 1-2 0v-3.513l48-48V48h32V0h2v50H66v55.413l-48 48v2.687zM50 53.9a5 5 0 1 0-2 0v42.686l-48 48V210h28.1a5 5 0 1 0 0-2H2v-62.586l48-48V53.9zm-16 16a5 5 0 1 0-2 0v18.686l-32 32v2.828l34-34V69.9zM12.1 32a5 5 0 1 1 0 2H9.414L0 43.414v-2.828L8.586 32H12.1zm265.8 18a5 5 0 1 1 0-2h18.686L304 40.586v2.828L297.414 50H277.9zm-16 160a5 5 0 1 1 0-2H288v-71.413l16-16v2.827l-14 14V210h-28.1zm-208 32a5 5 0 1 1 0-2H64v-22.586L40.586 194H21.9a5 5 0 1 1 0-2h19.513L66 216.586V242H53.9zm150.2 14a5 5 0 1 1 0 2H96v-56.598L56.598 162H37.9a5 5 0 1 1 0-2h19.502L98 200.598V256h106.1zm-150.2 2a5 5 0 1 1 0-2H80v-46.586L48.586 178H21.9a5 5 0 1 1 0-2h27.513L82 208.586V258H53.9zM97 100a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-48 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 96a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-144a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-96 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm96 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-32 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM49 36a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-32 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM33 68a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 240a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm80-176a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm112 176a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM17 180a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM17 84a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM34 39.793V0h-2v40.586L8.586 64H0v2h9.413L34 41.414v-1.62zM2 300.1V258h14v46h2v-48H0v46.17A3.004 3.004 0 0 1 3.83 304H5.9a5.004 5.004 0 0 0-3.9-3.9zM34 241v63h-2v-62H0v-2h34v1zM17 18h1V0h-2v16H0v2h17zm273-2V0h-2v18h16v-2h-14zm-32 273v15h-2v-14h-14v14h-2v-16h18v1zM0 92.1A5.002 5.002 0 0 1 6 97a5.002 5.002 0 0 1-6 4.9v-2.07a3 3 0 1 0 0-5.66V92.1zM80 272h2v32h-2v-32zm37.9 32a5 5 0 0 0-9.8 0h2.07a3.004 3.004 0 0 1 5.66 0h2.07zM5.9 0A5.002 5.002 0 0 1 0 5.9V3.83A3 3 0 0 0 3.83 0H5.9zm294.2 0a5 5 0 0 0 3.9 5.9V3.83A3.004 3.004 0 0 1 302.17 0h-2.07zm3.9 300.1a5.004 5.004 0 0 0-3.9 3.9h2.07a3.016 3.016 0 0 1 1.83-1.83v-2.07z' fill='%2345404b' fill-opacity='.05' fill-rule='evenodd'/%3E%3C/svg%3E"); +} +pre.ft-syntax-highlight[data-ui-theme="light" i] code span.newline::before { + color: black; +} +pre.ft-syntax-highlight[data-ui-theme="light" i] code span.comment { + color: rgba(160,160,160, 1); + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="light" i] code span.value { + color: dimgray; +} +pre.ft-syntax-highlight[data-ui-theme="light" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: #333; +} +pre.ft-syntax-highlight[data-ui-theme="light" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: #333; + color: ghostwhite; +} +/* => UI themes : MacOSX */ +pre.ft-syntax-highlight[data-ui-theme="macosx" i] { + background-image: url(); + border-radius: .3rem; + background: rgba(0,0,0,0.8); + padding: 11px 0 0; +} +pre.ft-syntax-highlight[data-ui-theme="macosx" i]::before { + direction: ltr; + text-indent: 0px; + text-align: center; + font-weight: normal; + color: rgb(85, 74, 74); + background: -webkit-gradient(linear, left bottom, left top, from(#D3D3D3), to(#EBEBEB)); + background: -webkit-linear-gradient(bottom, #D3D3D3, #EBEBEB); + background: -moz-linear-gradient(bottom, #D3D3D3, #EBEBEB); + background: -o-linear-gradient(bottom, #D3D3D3, #EBEBEB); + background: linear-gradient(to top, #D3D3D3, #EBEBEB); + padding: 5px 0; + border-radius: .25rem; +} +pre.ft-syntax-highlight[data-ui-theme="macosx" i]::after { + content: ' '; + position: absolute; + top: 10px; left: 10px; + background: #F95151; + border-radius: 100%; + width: 13px; + z-index: 5; + box-shadow: 20px 0 0 0 #FFB400, 40px 0 0 0 #96D46F; +} +pre.ft-syntax-highlight[data-ui-theme="macosx" i] code { + margin-top: 0; + font-family: Courier, monospace; + border: 10px solid #D3D3D3; + border-radius: .25rem; +} +pre.ft-syntax-highlight[data-showTooltips="true" i][data-ui-theme="macosx" i] code span:not(.newline)::after { + font-family: Courier, monospace; +} +/* => UI themes : midnight theme */ +pre.ft-syntax-highlight[data-ui-theme="midnight" i] { + border: 1px solid #b299e6; + border-radius: .25rem; + color: #888; + background-color: black; + background-image: -webkit-radial-gradient(white, rgba(255,255,255,.2) 2px, transparent 40px), + -webkit-radial-gradient(white, rgba(255,255,255,.15) 1px, transparent 30px), + -webkit-radial-gradient(white, rgba(255,255,255,.1) 2px, transparent 40px), + -webkit-radial-gradient(rgba(255,255,255,.4), rgba(255,255,255,.1) 2px, transparent 30px); + background-image: -moz-radial-gradient(white, rgba(255,255,255,.2) 2px, transparent 40px), + -moz-radial-gradient(white, rgba(255,255,255,.15) 1px, transparent 30px), + -moz-radial-gradient(white, rgba(255,255,255,.1) 2px, transparent 40px), + -moz-radial-gradient(rgba(255,255,255,.4), rgba(255,255,255,.1) 2px, transparent 30px); + background-image: -o-radial-gradient(white, rgba(255,255,255,.2) 2px, transparent 40px), + -o-radial-gradient(white, rgba(255,255,255,.15) 1px, transparent 30px), + -o-radial-gradient(white, rgba(255,255,255,.1) 2px, transparent 40px), + -o-radial-gradient(rgba(255,255,255,.4), rgba(255,255,255,.1) 2px, transparent 30px); + background-image: radial-gradient(white, rgba(255,255,255,.2) 2px, transparent 40px), + radial-gradient(white, rgba(255,255,255,.15) 1px, transparent 30px), + radial-gradient(white, rgba(255,255,255,.1) 2px, transparent 40px), + radial-gradient(rgba(255,255,255,.4), rgba(255,255,255,.1) 2px, transparent 30px); + background-size: 550px 550px, 350px 350px, 250px 250px, 150px 150px; + background-position: 0 -80px, 40px -20px, 130px 200px, 70px 20px; +} +pre.ft-syntax-highlight[data-ui-theme="midnight" i]::before { + direction: ltr; + font-weight: normal; + color: white; + background-color: #b299e6; + border-bottom: 1px solid #b299e6; +} +pre.ft-syntax-highlight[data-ui-theme="midnight" i] code { + margin-top: 0; +} +pre.ft-syntax-highlight[data-ui-theme="midnight" i] code span.comment { + color: rgba(160,160,160, 1); + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="midnight" i] code span.newline::before { + color: thistle; +} +pre.ft-syntax-highlight[data-ui-theme="midnight" i] code span.value { + color: dimgray; +} +pre.ft-syntax-highlight[data-ui-theme="midnight" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: #b299e6; +} +pre.ft-syntax-highlight[data-ui-theme="midnight" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: #b299e6; + color: snow; +} +/* => UI themes : Nightvision */ +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] { + background-image: url(); + border-radius: .25rem; + color: snow; + background: #2c3e52; +} +pre.ft-syntax-highlight[data-ui-theme="nightvision" i]::before { + direction: ltr; + font-weight: normal; + color: white; + background: #1abd9e; +} +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] code { + margin-top: 0; +} +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] code span.newline::before { + color: #1abd9e; +} +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] code::-webkit-scrollbar-track, +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] code::-webkit-scrollbar { + width: 14px; height: 14px; + border-radius: 0px; + background-color: #1abd9f2c; +} +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] code::-webkit-scrollbar-thumb { + border-radius: 0px; + background-color: #1abd9e; + box-shadow: unset; +} +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] code::-webkit-resizer, +pre.ft-syntax-highlight[data-ui-theme="nightvision" i] code::-webkit-scrollbar-corner { + background-color: #1abd9f2c; +} + + +/* => UI themes : Simple theme */ +pre.ft-syntax-highlight[data-ui-theme="simple" i] { + color: darkslategray; + background-color: rgb(235, 235, 235); + border-radius: 5px; + box-shadow: 1px 0px 2px darkgrey; + background-image: url(); +} +pre.ft-syntax-highlight[data-ui-theme="simple" i] code span.newline::before { + color: peru; +} +pre.ft-syntax-highlight[data-ui-theme="simple" i] code span.comment { + color: rgba(110,110,110, 1) !important; + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="simple" i] code span.value { + color: dimgray !important; +} +pre.ft-syntax-highlight[data-ui-theme="simple" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::before { + border-top-color: #333; +} +pre.ft-syntax-highlight[data-ui-theme="simple" i][data-showTooltips="true" i] code span:not(.newline):not(.url)::after { + background: #333; + color: ghostwhite; +} +/* => UI themes : Win95 */ +pre.ft-syntax-highlight[data-ui-theme="win95" i] { + background-image: url(); + background: rgb(192, 192, 192); + color: black; + border: 4px solid rgb(192, 192, 192); +} +pre.ft-syntax-highlight[data-ui-theme="win95" i]::before { + content: '🗁 ' attr(data-syntax); + direction: ltr; + font-weight: normal; + color:white; + background: navy; + font-family: Times, sans-serif; + font-size: 0.89rem; + letter-spacing: 0; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i]::after { + content : '🞪'; + position: absolute; + top: 9px; right: 5px; + color: black; + font-size: 0.9rem; + padding: 0 4px; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code { + margin-top: 0; + font-family: Times, sans-serif; + padding-left: 35px; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code span.newline::before { + color: black; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code span.comment { + color: rgba(110,110,110, 1) !important; + font-weight: 200; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code span.value { + color: dimgray !important; +} +pre.ft-syntax-highlight[data-showTooltips="true" i][data-ui-theme="win95" i] code span:not(.newline)::after { + font-family: Times, sans-serif; +} +/* win95 scrollbar styling by Lou Huang Pen: https://codepen.io/louh/pen/oZJQvm */ +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-track { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFElEQVQIW2M4fPz0////GYAYyAIASnoKpV3w4kgAAAAASUVORK5CYII=); + image-rendering: pixelated; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar { + width: 18px; height: 18px; + border-radius: 0px; + background-color: rgba(255,255,255,0.6); +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-corner { + display: block; + background-color: rgb(192, 192, 192); +} +pre.ft-syntax-highlight[data-ui-theme="win95" i]::after, +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-button, +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-thumb { + border-radius: 0px; + background-color: rgb(192, 192, 192); + border: 1px solid black; + border-top: 1px solid rgb(192, 192, 192); + border-left: 1px solid rgb(192, 192, 192); + box-shadow: inset 1px 1px 0 0 white, inset -1px -1px 0 0 #868a8e; + background-position: center center; + background-repeat: no-repeat; + image-rendering: pixelated; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-button:vertical:decrement { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGklEQVR4AWMYxuA/SYphmETFhDX9x4mHGQAAcL4P8dQiMq8AAAAASUVORK5CYII='); +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-button:vertical:increment { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAQAAAD8fJRsAAAAF0lEQVQY02NgoBf4jwJxSOHQhcNAOgMAWWAP8Rv2U3UAAAAASUVORK5CYII='); +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-button:horizontal:decrement { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAQAAAD8fJRsAAAAHklEQVQY02NgoBT8xyX8H5fwf1zCpOjAYwceV1EEAAO2D/HsQ4vsAAAAAElFTkSuQmCC'); +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-button:horizontal:increment { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAQAAAD8fJRsAAAAHUlEQVQY02NgIB/8xy3xH7fEf9wS/0nUQZqrKAYAK44P8ZRmzLQAAAAASUVORK5CYII='); +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-button:active { + background-position: 3px 3px; +} +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-resizer, +pre.ft-syntax-highlight[data-ui-theme="win95" i] code::-webkit-scrollbar-corner { + background-color: lightgrey; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAN0lEQVR4Ae3MgQUAMBRDwU5fFF05lb/CARTBw2Ulof0DxPtcwp3hNuEYnjbcEW4TjuFpwx3h9gMWGgZ2Y/PT2gAAAABJRU5ErkJggg=='); + background-position: bottom right; + background-repeat: no-repeat; + image-rendering: pixelated; +} + +/*checking for nested
s > although, this isn't a deep check
+this is all we are going to consider altering at this time.
+it's up to users to not nest 
s/s*/
+
+/*nesting 
s cause interferance because of UI themes.
+If a nested 
 is not the same theme it will create an
+unwanted, unpolished interface*/
+[class*='ft-syntax-highlight'] > * > pre {
+  background-color: transparent;
+  box-shadow: none;
+  background-image: url();
+  color: inherit;
+}
+
+/* SYNTAX SYNTAX THEMES */
+  /*Universal styles*/
+pre.ft-syntax-highlight code span.comment {
+  color: rgba(240,240,240, 0.3);
+  font-weight: 200;
+}
+pre.ft-syntax-highlight code span.url {
+  text-decoration: underline;
+}
+pre.ft-syntax-highlight code span.value {
+  color: rgb(180,180,180);
+  font-weight: 300;
+}
+/*    => syntax theme: bootstrap4 */
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.boolean,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-constant,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-enum,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-structure,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.null,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.unit {
+ color: tomato;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-function,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-udf {
+ color: #4f9fcf;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-class,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-sub,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.variable  {
+ color: #0B9C5E;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.object,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.property {
+ color: inherit;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.keyword {
+  color: #70b5db;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-native,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.identifier-namespace {
+ color: #2f6f9f;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.operand {
+ color: #70b5db;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.typecast {
+ color: mediumblue;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.url,
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i] code span.value {
+  color: orangered;
+}
+/*    => syntax theme: midnight */
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.boolean,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-constant,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-enum,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-structure,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.null,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.unit {
+ color: #db7970;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-function,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-udf {
+ color: #ceafce;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-class,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-sub  {
+ color: #93db70;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.url {
+  color: #c9db70;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.object,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.property {
+ color: palegoldenrod;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.keyword {
+  color: #70b5db;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-native,
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.identifier-namespace {
+ color: palevioletred;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.operand {
+ color: #70b5db;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.typecast {
+ color: violet;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i] code span.variable {
+  color: inherit;
+}
+/*    => syntax theme: one-dark*/
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.boolean,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-constant,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-enum,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-structure,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.null,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.unit {
+ color: goldenrod;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-function,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-udf {
+ color: #3b94d9;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-class,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-sub  {
+ color: #f8da7b;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-native {
+  color: lightseagreen;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.object,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.property {
+ color: tomato;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.keyword,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.identifier-namespace {
+ color: #d979c4;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.operand,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.url {
+ color: MediumTurquoise;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.typecast {
+ color: violet;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i] code span.variable {
+  color: inherit;
+}
+/*    => syntax theme: one-light */
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.boolean,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-constant,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-enum,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-structure,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.null,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.unit {
+ color: orange;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-function,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-udf {
+ color: #16C07B;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-class,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-sub  {
+ color: slateblue;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-native,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.url {
+  color: #6BBCFF;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.object,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.property {
+ color: tan;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.keyword,
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.identifier-namespace {
+ color: #de6f5b;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.operand {
+ color: MediumTurquoise;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.typecast {
+ color: violet;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i] code span.variable {
+  color: inherit;
+}
+/*    => syntax theme: simple */
+pre.ft-syntax-highlight[data-syntax-theme="simple" i] * {
+  color: darkslategray !important;
+  font-weight: 400;
+}
+pre.ft-syntax-highlight[data-syntax-theme="simple" i] code span.newline::before {
+  color: darkslategray !important;
+}
+/*    => syntax theme: vbgreen */
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.comment {
+  color: #4E9C3A;
+  font-weight: 400;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.value {
+  color: sandybrown;
+  font-weight: 200;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.boolean,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier-namespace,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.keyword,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.null {
+  color: #3C8DCC;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier {
+  color: #5AAFFC;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier-native {
+  color: #cc7b3c;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier-class,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier-structure,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier-sub {
+  color: #3CBEA1;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier-constant,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.identifier-enum,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.unit {
+  color: #B8D288;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.operand {
+ color: #70b5db;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.url {
+  color: #3C8DCC;
+  text-decoration: underline;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.typecast {
+  color: blueviolet;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i] code span.variable {
+  color: inherit;
+}
+
+/* HTML OVERRIDES */
+/*    => HTML TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="html" i][data-showTooltips="true" i] code span.identifier::after {
+  content: 'selector';
+}
+pre.ft-syntax-highlight[data-syntax="html" i][data-showTooltips="true" i] code span.identifier-native::after {
+  content: 'selector-native';
+}
+/*    => HTML SYNTAX THEMES */
+/*      => html syntax theme: one-dark */
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="html" i] code span.identifier {
+  color: orange;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="html" i] code span.identifier-native {
+  color: tomato;
+}
+
+/* CSS OVERRIDES */
+/*    => CSS TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.at-rule::after {
+  content: "at-rule";
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.identifier::after {
+  content: "selector";
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.identifier-function::after {
+  content: "function"
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.identifier-native::after {
+  content: "selector-native"
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.operand::after {
+  content: "operand"
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.property::after {
+  content: "property";
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.pseudo::after {
+  content: "pseudo";
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.unit::after {
+  content: "unit";
+}
+pre.ft-syntax-highlight[data-syntax="css" i][data-showTooltips="true" i] code span.variable::after {
+  content: "variable";
+}
+/*    => CSS SYNTAX THEMES */
+/*      => css syntax theme: bootstrap */
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i][data-syntax="css" i] code span.at-rule {
+  color: #0B9C5E;
+}
+/*      => css syntax theme: one-dark */
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="css" i] code span.at-rule {
+  color: violet;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="css" i] code span.identifier {
+  color: orange;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="css" i] code span.identifier-function,
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="css" i] code span.property {
+  color: #96cbfe;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="css" i] code span.identifier-native {
+  color: tomato;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="css" i] code span.pseudo {
+  color: #FCBB7E;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="css" i] code span.variable {
+  color: tomato;
+}
+/*      => css syntax theme: one-light */
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i][data-syntax="css" i] code span.at-rule {
+  color: #FF6090;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i][data-syntax="css" i] code span.identifier-function {
+  color: tan;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i][data-syntax="css" i] code span.pseudo {
+  color: peru;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i][data-syntax="css" i] code span.variable {
+  color: tomato;
+}
+/*      => css syntax theme: midnight */
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i][data-syntax="css" i] code span.at-rule {
+  color: #70b5db;
+}
+/*      => css syntax theme: vbgreen */
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i][data-syntax="css" i] code span.at-rule,
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i][data-syntax="css" i] code span.identifier-function {
+  color: mediumpurple;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i][data-syntax="css" i] code span.variable {
+  color: orangered;
+}
+
+/* JAVASCRIPT OVERRIDES */
+/*    => JAVASCRIPT TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.boolean::after {
+	content: 'boolean';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.identifier::after {
+  content: 'identifier';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.identifier-class::after {
+  content: 'indentifier-class';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.identifier-constant::after {
+  content: 'indentifier-constant';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.identifier-native::after {
+  content: 'identifier-native';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.identifier-udf::after {
+  content: 'identifier-udf';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.keyword::after {
+  content: 'keyword';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.null::after {
+	content: 'null';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.unit::after {
+  content: 'unit';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.object::after {
+  content: 'object';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.operand::after {
+  content: 'operand';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.parameter::after {
+  content: 'parameter';
+}
+pre.ft-syntax-highlight[data-syntax="js" i][data-showTooltips="true" i] code span.property::after {
+  content: 'property';
+}
+
+/* JQUERY OVERRIDES */
+/*    => JQUERY TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.boolean::after {
+  content: 'boolean';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.identifier::after {
+  content: 'identifier';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.identifier-class::after {
+  content: 'indentifier-class';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.identifier-constant::after {
+  content: 'indentifier-constant';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.identifier-native::after {
+  content: 'identifier-native';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.identifier-udf::after {
+  content: 'identifier-udf';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.keyword::after {
+  content: 'keyword';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.null::after {
+  content: 'null';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.unit::after {
+  content: 'unit';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.object::after {
+  content: 'object';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.operand::after {
+  content: 'operand';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.parameter::after {
+  content: 'parameter';
+}
+pre.ft-syntax-highlight[data-syntax="jquery" i][data-showTooltips="true" i] code span.property::after {
+  content: 'property';
+}
+
+/* PHP OVERRIDES */
+/*    => PHP TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.boolean::after {
+	content: 'boolean';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.identifier::after {
+  content: 'identifier';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.identifier-class::after {
+  content: 'indentifier-class';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.identifier-constant::after {
+  content: 'indentifier-constant';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.identifier-native::after {
+  content: 'identifier-native';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.identifier-udf::after {
+  content: 'identifier-udf';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.keyword::after {
+  content: 'keyword';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.null::after {
+	content: 'null';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.unit::after {
+  content: 'unit';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.object::after {
+  content: 'object';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.operand::after {
+  content: 'operand';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.parameter::after {
+  content: 'parameter';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.property::after {
+  content: 'property';
+}
+pre.ft-syntax-highlight[data-syntax="php" i][data-showTooltips="true" i] code span.typecast::after {
+  content: 'typecast';
+}
+/*    => PHP SYNTAX THEMES */
+/*      => php syntax theme: one-dark */
+pre.ft-syntax-highlight[data-syntax="php" i][data-syntax-theme="one-dark" i] code span.identifier {
+  color: tomato;
+}
+
+/* PYTHON OVERRIDES */
+/*    => PYTHON TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.boolean::after {
+  content: 'boolean';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.identifier::after {
+  content: 'identifier';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.identifier-class::after {
+  content: 'identifier-class';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.identifier-constant::after {
+  content: 'identifier-constant';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.identifier-native::after {
+  content: 'identifier-native';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.identifier-udf::after {
+  content: 'identifier-udf';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.keyword::after {
+  content: 'keyword';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.null::after {
+  content: 'null';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.unit::after {
+  content: 'unit';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.object::after {
+  content: 'object';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.operand::after {
+  content: 'operand';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.parameter::after {
+  content: 'parameter';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.property::after {
+  content: 'property';
+}
+pre.ft-syntax-highlight[data-syntax="python" i][data-showTooltips="true" i] code span.variable::after {
+	content: 'variable';
+}
+
+/* VB OVERRIDES */
+/*    => VB TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.boolean::after {
+	content: 'boolean';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier::after {
+	content: 'identifier';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier-class::after {
+	content: 'identifier-class';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier-constant::after {
+	content: 'identifier-constant';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier-enum::after {
+	content: 'identifier-enum';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier-function::after {
+	content: 'identifier-function';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier-namespace::after {
+	content: 'identifier-namespace';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier-structure::after {
+	content: 'identifier-structure';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.identifier-sub::after {
+	content: 'identifier-sub';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.keyword::after {
+	content: 'keyword';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.null::after {
+	content: 'null';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.operand::after {
+	content: 'operand';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.parameter::after {
+	content: 'parameter';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.property::after {
+	content: 'property';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.typecast::after {
+	content: 'typecast';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.unit::after {
+	content: 'unit';
+}
+pre.ft-syntax-highlight[data-syntax="vb" i][data-showTooltips="true" i] code span.variable::after {
+	content: 'variable';
+}
+/*    => VB SYNTAX THEMES */
+/*      => VB theme: bootstrap */
+pre.ft-syntax-highlight[data-syntax="vb" i][data-syntax-theme="bootstrap" i] code span.variable {
+  color: inherit;
+}
+/*      => VB theme: one-dark */
+pre.ft-syntax-highlight[data-syntax="vb" i][data-syntax-theme="one-dark" i] code span.identifier-sub {
+  color: #3b94d9;
+}
+/*      => VB theme: one-light */
+pre.ft-syntax-highlight[data-syntax="vb" i][data-syntax-theme="one-light" i] code span.keyword {
+  color: #6BBCFF;
+}
+
+/* XML OVERRIDES */
+/*    => XML TOOLTIPS */
+pre.ft-syntax-highlight[data-syntax="xml" i][data-showTooltips="true" i] code span.attribute::after {
+  content: "attribute";
+}
+pre.ft-syntax-highlight[data-syntax="xml" i][data-showTooltips="true" i] code span.identifier::after {
+  content: "element";
+}
+pre.ft-syntax-highlight[data-syntax="xml" i][data-showTooltips="true" i] code span.identifier-namespace::after {
+  content: "namespace";
+}
+/*    => XML SYNTAX THEMES */
+/*      => XML syntax theme: bootstrap */
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i][data-syntax="xml" i] code span.attribute {
+  color: #4f9fcf;
+}
+pre.ft-syntax-highlight[data-syntax-theme="bootstrap" i][data-syntax="xml" i] code span.identifier {
+  color:  #2f6f9f;
+}
+/*      => XML syntax theme: one-dark */
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="xml" i] code span.attribute {
+  color: orange;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="xml" i] code span.identifier {
+  color: tomato;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-dark" i][data-syntax="xml" i] code span.identifier-namespace {
+  color: coral;
+}
+/*      => XML syntax theme: one-light */
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i][data-syntax="xml" i] code span.attribute {
+  color: #16C07B;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i][data-syntax="xml" i] code span.identifier {
+  color: #6BBCFF;
+}
+pre.ft-syntax-highlight[data-syntax-theme="one-light" i][data-syntax="xml" i] code span.identifier-namespace {
+  color: #8ac1ff;
+}
+/*      => XML syntax theme: midnight */
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i][data-syntax="xml" i] code span.attribute {
+  color: thistle;
+}
+pre.ft-syntax-highlight[data-syntax-theme="midnight" i][data-syntax="xml" i] code span.identifier {
+  color: palevioletred;
+}
+/*      => XML syntax theme: vbgreen */
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i][data-syntax="xml" i] code span.attribute {
+  color: #085A99;
+}
+pre.ft-syntax-highlight[data-syntax-theme="vbgreen" i][data-syntax="xml" i] code span.identifier {
+  color: #3C8DCC;
+}
\ No newline at end of file
diff --git a/resources/public/css/style.css b/resources/public/css/style.css
new file mode 100644
index 0000000..f7d318a
--- /dev/null
+++ b/resources/public/css/style.css
@@ -0,0 +1,175 @@
+@import url('ft-syntax-highlight.css');
+
+body {
+    color: #333;
+    background-color: #f2f2f2;
+    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+    padding: 1em 5em;
+}
+
+h1,h2,h3,h4,h5,h6,th {
+    font-family: 'Archivo Black', Helvetica, Arial, sans-serif;
+}
+
+th,td {
+    border: thin solid darkgray;
+    padding: 0.25em 1em;
+    text-align: left;
+    vertical-align: top;
+}
+
+.info {
+    background-color: lightgreen;
+}
+
+.minor {
+    background-color: greenyellow;
+}
+
+.should {
+    background-color: orange;
+}
+
+.must {
+    background-color: orangered;
+    color: white;
+}
+
+.critical {
+    background-color: maroon;
+    color: white;
+}
+
+th {
+    background-color: silver;
+}
+
+.container {
+    max-width: 1000px;
+}
+
+.right {
+    float: right;
+    text-align: right;
+}
+
+.navbar {
+    border-radius: 0;
+    box-shadow: 0 0 0 0, 0 6px 12px rgba(34, 34, 34, 0.3);
+}
+
+.navbar-default {
+    background-color: #002b00;
+    border: none;
+}
+
+.navbar-default .navbar-brand {
+    color: #fff;
+    font-family: 'Archivo Black', Helvetica, Arial, sans-serif;
+}
+
+.navbar-default .navbar-brand:hover {
+    color: #fff;
+}
+
+.navbar-default .navbar-nav li a {
+    color: #fff;
+}
+
+.navbar-default .navbar-nav li a:hover {
+    color: #fff;
+    background-color: #002b00;
+}
+
+.navbar-default .navbar-nav .active a {
+    color: #fff;
+    background-color: #002b00;
+}
+
+.navbar-default .navbar-toggle:hover {
+    background-color: #002b00;
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+    background-color: #fff;
+}
+
+#sidebar {
+    margin-left: 15px;
+    margin-top: 50px;
+}
+
+#content {
+    background-color: #fff;
+    border-radius: 3px;
+    box-shadow: 0 0 0 0, 0 6px 12px rgba(34, 34, 34, 0.1);
+}
+
+#content img {
+    max-width: 100%;
+    height: auto;
+}
+
+footer {
+    font-size: 14px;
+    text-align: center;
+    padding-top: 75px;
+    padding-bottom: 30px;
+}
+
+blockquote footer {
+    text-align: left;
+    padding-top: 0px;
+    padding-bottom: 0px;
+}
+
+#post-tags {
+    margin-top: 30px;
+}
+
+#prev-next {
+    padding: 15px 0;
+}
+
+.post-header {
+    margin-bottom: 20px;
+}
+
+.post-header h2 {
+    font-size: 32px;
+}
+
+#post-meta {
+    font-size: 14px;
+    color: rgba(0, 0, 0, 0.4)
+}
+
+#page-header {
+    border-bottom: 1px solid #dbdbdb;
+    margin-bottom: 20px;
+}
+
+#page-header h2 {
+    font-size: 32px;
+}
+
+pre {
+    overflow-x: auto;
+}
+
+pre code {
+    display: block;
+    padding: 0.5em;
+    overflow-wrap: normal;
+    white-space: pre;
+}
+
+code {
+    color: #002b00;
+}
+
+pre,
+code,
+.hljs {
+    background-color: #f7f9fd;
+}
diff --git a/src/dog_and_duck/quack/constants.clj b/src/dog_and_duck/quack/constants.clj
new file mode 100644
index 0000000..8f12e7b
--- /dev/null
+++ b/src/dog_and_duck/quack/constants.clj
@@ -0,0 +1,116 @@
+(ns dog-and-duck.quack.constants
+  "Constants supporting the validator.")
+
+;;;     Copyright (C) Simon Brooke, 2022
+
+;;;     This program is free software; you can redistribute it and/or
+;;;     modify it under the terms of the GNU General Public License
+;;;     as published by the Free Software Foundation; either version 2
+;;;     of the License, or (at your option) any later version.
+
+;;;     This program is distributed in the hope that it will be useful,
+;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;     GNU General Public License for more details.
+
+;;;     You should have received a copy of the GNU General Public License
+;;;     along with this program; if not, write to the Free Software
+;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+(def ^:const activitystreams-context-uri
+  "The URI of the context of an ActivityStreams object is expected to be this
+   literal string.
+   
+   **NOTE THAT** the URI actually used in the published suite of 
+   activitystreams-test-documents use this URI with 'http' rather than
+   'https' as the property part, but the spec itself specifies 'https'."
+  "https://www.w3.org/ns/activitystreams")
+
+(def ^:const actor-types
+  "The set of types we will accept as actors.
+   
+   There's an [explicit set of allowed actor types]
+   (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)."
+  #{"Application"
+    "Group"
+    "Organization"
+    "Person"
+    "Service"})
+
+(def ^:const context-key
+  "The Clojure reader barfs on `:@context`, although it is in principle a valid 
+   keyword. So we'll make it once, here, to make the code more performant and
+   easier to read."
+  (keyword "@context"))
+
+(def ^:const re-rfc5646 
+  "A regex which tests conformity to RFC 5646. Cribbed from
+   https://newbedev.com/regex-to-detect-locales"
+  #"^[a-z]{2,4}(-[A-Z][a-z]{3})?(-([A-Z]{2}|[0-9]{3}))?$")
+
+(def ^:const severity
+  "Severity of faults found, as follows:
+   
+   0. `:info` not actually a fault, but an issue noted during validation;
+   1. `:minor` things which I consider to be faults, but which 
+      don't actually breach the spec;
+   2. `:should` instances where the spec says something SHOULD
+      be done, which isn't;
+   3. `:must` instances where the spec says something MUST
+      be done, which isn't;
+   4. `:critical` instances where I believe the fault means that
+      the object cannot be meaningfully processed."
+  #{:info :minor :should :must :critical})
+
+(def ^:const severity-filters
+  "Hack for implementing a severity hierarchy"
+  {:all #{}
+   :info #{}
+   :minor #{:info}
+   :should #{:info :minor}
+   :must #{:info :minor :should}
+   :critical #{:info :minor :should :must}})
+
+(def ^:const validation-fault-context-uri
+  "The URI of the context of a validation fault report object shall be this
+   literal string."
+  "https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html")
+
+(def ^:const activity-types
+  "The set of types we will accept as activities.
+   
+   There's an [explicit set of allowed activity types]
+   (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)."
+  #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike"
+    "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move"
+    "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept"
+    "TentativeReject" "Travel" "Undo" "Update" "View"})
+
+(def ^:const noun-types
+  "The set of object types we will accept as nouns.
+   
+   There's an [explicit set of allowed 'object types']
+   (https://www.w3.org/TR/activitystreams-vocabulary/#object-types), but by 
+   implication it is not exhaustive."
+  #{"Article" 
+    "Audio" 
+    "Document"
+    "Event"
+    "Image"
+    "Link"
+    "Mention"
+    "Note"
+    "Object"
+    "Page"
+    "Place"
+    "Profile"
+    "Relationsip"
+    "Tombstone"
+    "Video"})
+
+(def ^:const implicit-noun-types
+  "These types are not explicitly listed in [Section 3.3 of the spec]
+   (https://www.w3.org/TR/activitystreams-vocabulary/#object-types), but are 
+   mentioned in narrative"
+  #{"Link"})
+
diff --git a/src/dog_and_duck/quack/control_variables.clj b/src/dog_and_duck/quack/control_variables.clj
new file mode 100644
index 0000000..89723c9
--- /dev/null
+++ b/src/dog_and_duck/quack/control_variables.clj
@@ -0,0 +1,40 @@
+(ns dog-and-duck.quack.control-variables
+  "Control variables for the validator.")
+
+;;;     Copyright (C) Simon Brooke, 2022
+
+;;;     This program is free software; you can redistribute it and/or
+;;;     modify it under the terms of the GNU General Public License
+;;;     as published by the Free Software Foundation; either version 2
+;;;     of the License, or (at your option) any later version.
+
+;;;     This program is distributed in the hope that it will be useful,
+;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;     GNU General Public License for more details.
+
+;;;     You should have received a copy of the GNU General Public License
+;;;     along with this program; if not, write to the Free Software
+;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+(def ^:dynamic *reify-refs*
+  "If `true`, references to objects in fields will be reified and validated. 
+   If `false`, they won't, but an `:info` level fault report will be generated.
+   
+   There are several things in the spec which, in a document, may correctly be
+   either
+   
+   1. a fully fleshed out object, or
+   2. a URI pointing to such an object.
+   
+   Obviously to fully validate a document we ought to reify all the refs and 
+   check that they are themselves valid, but
+   
+   a. in some of the published test documents the URIs do not reference a
+      valid document;
+   b. there will be performance costs to reifying all the refs;
+   c. in perverse cases, reifying refs might result in runaway recursion.
+   
+   TODO: I think that in production this should default to `true`."
+  false)
+
diff --git a/src/dog_and_duck/quack/core.clj b/src/dog_and_duck/quack/core.clj
new file mode 100644
index 0000000..090db21
--- /dev/null
+++ b/src/dog_and_duck/quack/core.clj
@@ -0,0 +1,195 @@
+(ns dog-and-duck.quack.core
+  (:require [clojure.data.json :as json :refer [read-str]]
+            [clojure.java.io :refer [resource]]
+            [clojure.pprint :as pprint]
+            [clojure.string :refer [join]]
+            [clojure.tools.cli :refer [parse-opts]]
+            [clojure.walk :refer [keywordize-keys]]
+            [dog-and-duck.quack.constants :refer [severity]]
+            [dog-and-duck.quack.objects :refer [object-faults]]
+            [dog-and-duck.quack.utils :refer [filter-severity]]
+            [hiccup.core :refer [html]]
+            [scot.weft.i18n.core :refer [get-message *config*]]
+            [trptr.java-wrapper.locale :as locale])
+  (:gen-class))
+
+;;;     Copyright (C) Simon Brooke, 2023
+
+;;;     This program is free software; you can redistribute it and/or
+;;;     modify it under the terms of the GNU General Public License
+;;;     as published by the Free Software Foundation; either version 2
+;;;     of the License, or (at your option) any later version.
+
+;;;     This program is distributed in the hope that it will be useful,
+;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;     GNU General Public License for more details.
+
+;;;     You should have received a copy of the GNU General Public License
+;;;     along with this program; if not, write to the Free Software
+;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+(def ^:const stylesheet-url
+  ;; TODO: fix this to github pages before go live
+  ;;"https://simon-brooke.github.io/quack/style.css"
+  "resources/public/css/style.css")
+
+(def cli-options
+  ;; An option with a required argument
+  [["-i" "--input SOURCE" "The file or URL to validate"
+    :default "standard input"]
+   ["-o" "--output DEST" "The file to write to, defaults to standard out"
+    :default "standard output"]
+   ["-f" "--format FORMAT" "The format to output, one of `edn` `csv` `html`"
+    :default :edn
+    :parse-fn #(keyword %)
+    :validate [#(#{:csv :edn :html} %) "Expect one of `edn` `csv` `html`"]]
+   ["-l" "--language LANG" "The ISO 639-1 code for the language to output"
+    :default (-> (locale/get-default) locale/to-language-tag)]
+   ["-s" "--severity LEVEL" "The minimum severity of faults to report"
+    :default :info
+    :parse-fn #(keyword %)
+    :validate [#(severity %) (join " "
+                                   (cons
+                                    "Expected one of"
+                                    (map name severity)))]]
+   ["-h" "--help"]])
+
+(defn validate
+  [source]
+  (println (str "Reading " source))
+  (let [input (read-str (slurp source))]
+    (cond (map? input) (object-faults (keywordize-keys input))
+          (and (coll? input)
+               (every? map? input)) (map #(object-faults
+                                           (keywordize-keys %)
+                                           input)))))
+
+(defn output-csv
+  [faults]
+  (let [cols (set (reduce concat (map keys faults)))]
+    (with-out-str
+      (if-not (empty? faults)
+        (doall
+         (println (join ", " (map name cols)))
+         (map
+          #(println (join ", " (map (fn [p] (p %)) cols)))
+          faults))
+        (println (get-message :no-faults-found))))))
+
+(defn output-json
+  [faults]
+  (with-out-str
+    (json/pprint (if-not (empty? faults)
+                   faults
+                   (get-message :no-faults-found)))))
+
+(defn html-header-row
+  [cols]
+  (apply vector (cons :tr (map #(vector :th (name %)) cols))))
+
+(defn html-fault-row
+  [fault cols]
+  (apply
+   vector
+   (cons :tr
+         (cons
+          {:class (name (or (:severity fault) :info))}
+          (map (fn [col] (vector :td (col fault))) cols)))))
+
+(defn- version-string []
+  (join
+   " "
+   ["dog-and-duck/quack"
+    (try
+      (some->>
+       (resource "META-INF/maven/dog-and-duck/quack/pom.properties")
+       slurp
+       (re-find #"version=(.*)")
+       second)
+      (catch Exception _ nil))]))
+
+(defn- output-html-text-analysed 
+  [options]
+  [:div
+   {:class "text-analysed"}
+   [:h2 :text-analysed]
+   [:pre {:class "ft-syntax-highlight"
+          :data-syntax "javascript"
+          :data-syntax-theme "bootstrap"
+          :data-ui-theme "light"}
+    (with-out-str
+      (json/pprint
+       (read-str
+        (slurp
+         (:input options)))))]])
+
+(defn output-html
+  [faults options]
+  (let [source-name (if (= (:input options) *in*) "Standard input" (str (:input options)))
+        title (join " " [(get-message :validation-report-for) source-name])
+        cols (set (reduce concat (map keys faults)))
+        version (version-string)]
+    (str
+     ""
+     (html
+      [:html
+       [:head
+        [:title title]
+        [:meta {:name "generator" :content version}]
+        [:link {:rel "stylesheet" :media "screen" :href stylesheet-url :type "text/css"}]]
+       [:body
+        [:h1 title]
+        [:p (join " " (remove nil? [(get-message :generated-on)
+                                    (java.time.LocalDateTime/now)
+                                    (get-message :by)
+                                    version]))]
+        [:h2 (get-message :faults-found)]
+        (if-not
+         (empty? faults)
+          (apply
+           vector
+           :table
+           (html-header-row cols)
+           (map
+            #(html-fault-row % cols)
+            faults))
+          [:p (get-message :no-faults-found)])
+        (when-not (= (:input options) *in*)
+          (output-html-text-analysed options))]]))))
+
+(defn output
+  "Output this `content` as directed by these `options`."
+  [content options]
+  (let [faults (filter-severity content (:severity options))]
+    (spit (:output options)
+          (case (:format options)
+            :html (output-html faults options)
+            :csv (output-csv faults)
+            :json (output-json faults)
+            (with-out-str (if-not (empty? faults)
+                            (pprint/pprint faults)
+                            (println (get-message :no-faults-found))))))))
+
+(defn -main
+  "Parse command line `args`, and, using the options found therein,
+   validate one ActivityStreams document and exit."
+  [& args]
+  (let [opts (parse-opts args cli-options)
+        options (assoc (:options opts)
+                       :input (if (= (:input (:options opts)) "standard input")
+                                *in*
+                                (:input (:options opts)))
+                       :output (if (= (:output (:options opts)) "standard output")
+                                 *out*
+                                 (:output (:options opts))))]
+    ;;(println options)
+    (when (:help options)
+      (println (:summary opts)))
+    (when (:errors opts)
+      (println (:errors opts)))
+    (when-not (or (:help options) (:errors options))
+      (binding [*config* (assoc *config* :default-language (:language options))]
+        (output
+         (validate (:input options))
+         options)))))
\ No newline at end of file
diff --git a/src/dog_and_duck/quack/objects.clj b/src/dog_and_duck/quack/objects.clj
new file mode 100644
index 0000000..c4826e4
--- /dev/null
+++ b/src/dog_and_duck/quack/objects.clj
@@ -0,0 +1,521 @@
+(ns dog-and-duck.quack.objects
+  (:require [clojure.data.json :as json]
+            [clojure.set :refer [union]]
+            [dog-and-duck.quack.constants :refer [actor-types
+                                                        noun-types
+                                                        re-rfc5646]]
+            [dog-and-duck.quack.control-variables :refer [*reify-refs*]]
+            [dog-and-duck.quack.time :refer [xsd-date-time?
+                                                   xsd-duration?]]
+            [dog-and-duck.quack.utils :refer [concat-non-empty
+                                                    cond-make-fault-object
+                                                    has-activity-type?
+                                                    has-context?
+                                                    has-type?
+                                                    has-type-or-fault
+                                                    make-fault-object
+                                                    nil-if-empty
+                                                    object-or-uri?
+                                                    truthy?
+                                                    xsd-non-negative-integer?]]
+            [taoensso.timbre :refer [warn]])
+  (:import [java.io FileNotFoundException]
+           [java.net URI URISyntaxException]))
+
+(defn- xsd-float?
+  [pv]
+  (or (integer? pv) (float? pv)))
+
+;;;     Copyright (C) Simon Brooke, 2022
+
+;;;     This program is free software; you can redistribute it and/or
+;;;     modify it under the terms of the GNU General Public License
+;;;     as published by the Free Software Foundation; either version 2
+;;;     of the License, or (at your option) any later version.
+
+;;;     This program is distributed in the hope that it will be useful,
+;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;     GNU General Public License for more details.
+
+;;;     You should have received a copy of the GNU General Public License
+;;;     along with this program; if not, write to the Free Software
+;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+(def object-expected-properties
+  "Requirements of properties of object, cribbed from
+   https://www.w3.org/TR/activitystreams-vocabulary/#properties
+   
+   Note the following sub-key value types:
+   
+   * `:collection` opposite of `:functional`: if true, value should be a
+      collection (in the Clojure sense), not a single object;
+   * `:functional` if true, value should be a single object; if false, may
+      be a single object or a sequence of objects, but each must pass 
+      validation checks;
+   * `:if-invalid` a sequence of two keywords, first indicating severity,
+      second being a message key;
+   * `:if-missing` a sequence of two keywords, first indicating severity,
+      second being a message key;
+   * `:required` a boolean, or a function of one argument returning a 
+      boolean, in which case the function will be applied to the object
+      having the property;
+   * `:validator` a function of one argument returning a boolean, which will 
+      be applied to the value or values of the identified property."
+  {:accuracy {:functional false
+              :if-invalid [:must :invalid-number]
+              :validator (fn [pv] (and (xsd-float? pv)
+                                       (>= pv 0)
+                                       (<= pv 100)))}
+   :actor {:functional false
+           :if-invalid [:must :invalid-actor]
+           :if-missing [:must :no-actor]
+           :required has-activity-type?
+           :validator object-or-uri?}
+   :altitude {:functional false
+              :if-invalid [:must :invalid-number]
+              :validator xsd-float?}
+   :anyOf {:collection true
+           :functional false
+           ;; a Question should have a `:oneOf` or `:anyOf`, but at this layer
+           ;; that's hard to check.
+           :if-invalid [:must :invalid-option]
+           :validator object-or-uri?}
+   :attachment {:functional false
+                :if-invalid [:must :invalid-attachment]
+                :validator object-or-uri?}
+   :attributedTo {:functional false
+                  :if-invalid [:must :invalid-attribution]
+                  :validator object-or-uri?}
+   :audience {:functional false
+              :if-invalid [:must :invalid-audience]
+              :validator object-or-uri?}
+   :bcc {:functional false
+         :if-invalid [:must :invalid-audience] ;; do we need a separate message for bcc, cc, etc?
+         :validator object-or-uri?}
+   :cc {:functional false
+        :if-invalid [:must :invalid-audience] ;; do we need a separate message for bcc, cc, etc?
+        :validator object-or-uri?}
+   :closed {:functional false
+            :if-invalid [:must :invalid-closed]
+            :validator (fn [pv] (truthy? (or (object-or-uri? pv)
+                                             (xsd-date-time? pv)
+                                             (#{"true" "false"} pv))))}
+   :content {:functional false
+             :if-invalid [:must :invalid-content]
+             :validator string?}
+   :context {:functional false
+             :if-invalid [:must :invalid-context]
+             :validator object-or-uri?}
+   :current {:functional true
+             :if-missing [:minor :paged-collection-no-current]
+             :if-invalid [:must :paged-collection-invalid-current]
+             :required (fn [x] ;; if an object is a collection which has pages,
+                                 ;; it ought to have a `:current` page. But 
+                                 ;; 1. it isn't required to, and
+                                 ;; 2. there's no certain way of telling that it
+                                 ;;    does have pages - although if it has a
+                                 ;;    `:first`, then it is.
+                         (and
+                          (or (has-type? x "Collection")
+                              (has-type? x "OrderedCollection"))
+                          (:first x)))
+             :validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
+                                                      "OrderedCollectionPage"}))}
+   :deleted {:functional true
+             :if-missing [:minor :tombstone-missing-deleted]
+             :if-invalid [:must :invalid-deleted]
+             :required (fn [x] (has-type? x "Tombstone"))
+             :validator xsd-date-time?}
+   :describes {:functional true
+               :required (fn [x] (has-type? x "Profile"))
+               :if-invalid [:must :invalid-describes]
+               ;; TODO: actually the spec says this MUST be an object and
+               ;; not a URI, which it doesn't say anywhere else, but this seems
+               ;; to make no sense?
+               :validator object-or-uri?}
+   :duration {:functional false
+              :if-invalid [:must :invalid-duration]
+              :validator xsd-duration?}
+   :endTime {:functional true
+             :if-invalid [:must :invalid-date-time]
+             :validator xsd-date-time?}
+   :first {:functional true
+           :if-missing [:minor :paged-collection-no-first]
+           :if-invalid [:must :paged-collection-invalid-first]
+           :required (fn [x] ;; if an object is a collection which has pages,
+                                 ;; it ought to have a `:first` page. But 
+                                 ;; 1. it isn't required to, and
+                                 ;; 2. there's no certain way of telling that it
+                                 ;;    does have pages - although if it has a
+                                 ;;    `:last`, then it is.
+                       (and
+                        (or (has-type? x "Collection")
+                            (has-type? x "OrderedCollection"))
+                        (:last x)))
+           :validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
+                                                    "OrderedCollectionPage"}))}
+   :formerType {:functional false
+                :if-missing [:minor :tombstone-missing-former-type]
+                :if-invalid [:must :invalid-former-type]
+                :required (fn [x] (has-type? x "Tombstone"))
+                ;; The narrative of the spec says this should be an `Object`,
+                ;; but in all the provided examples it's a string.
+                :validator string?}
+   :generator {:functional false
+               :if-invalid [:must :invalid-generator]
+               :validator object-or-uri?}
+   :height {:functional false
+            :if-invalid [:must :invalid-non-negative]
+            :validator xsd-non-negative-integer?}
+   :href {:functional false
+          :if-invalid [:must :invalid-href]
+          :validator (fn [pv] (try (uri? (URI. pv))
+                                   (catch URISyntaxException _ false)))}
+   :hreflang {:validator (fn [pv] (truthy? (re-matches re-rfc5646 pv)))}
+   :icon {:functional false
+          :if-invalid [:must :invalid-icon]
+          ;; an icon is also expected to have a 1:1 aspect ratio, but that's
+          ;; too much detail at this level of verification
+          :validator (fn [pv] (object-or-uri? pv "Image"))}
+   :id {:functional true
+        :if-missing [:minor :no-id-transient]
+        :if-invalid [:must :invalid-id]
+        :validator (fn [pv] (try (uri? (URI. pv))
+                                 (catch URISyntaxException _ false)))}
+   :image {:functional false
+           :if-invalid [:must :invalid-image]
+           :validator (fn [pv] (object-or-uri? pv "Image"))}
+   :inReplyTo {:functional false
+               :if-invalid [:must :invalid-in-reply-to]
+               :validator (fn [pv] (object-or-uri? pv noun-types))}
+   :instrument {:functional false
+                :if-invalid [:must :invalid-instrument]
+                :validator object-or-uri?}
+   :items {:collection true
+           :functional false
+           :if-invalid [:must :invalid-items]
+           :if-missing [:must :no-items-or-pages]
+           :required (fn [x] (or (has-type? x "CollectionPage")
+                                 (and (has-type? x "Collection")
+                                      ;; if it's a collection and has pages,
+                                      ;; it doesn't need items.
+                                      (not (:current x))
+                                      (not (:first x))
+                                      (not (:last x)))))
+           :validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))}
+   :last {:functional true
+          :if-missing [:minor :paged-collection-no-last]
+          :if-invalid [:must :paged-collection-invalid-last]
+          :required (fn [x] (if (and
+                                 (string? x)
+                                 (try (uri? (URI. x))
+                                      (catch URISyntaxException _ false)))
+                              true
+                                 ;; if an object is a collection which has pages,
+                                 ;; it ought to have a `:last` page. But 
+                                 ;; 1. it isn't required to, and
+                                 ;; 2. there's no certain way of telling that it
+                                 ;;    does have pages - although if it has a
+                                 ;;    `:first`, then it is.
+                              (and
+                               (has-type? x #{"Collection"
+                                              "OrderedCollection"})
+                               (:first x))))
+          :validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
+                                                   "OrderedCollectionPage"}))}
+   :latitude {:functional true
+              :if-invalid [:must :invalid-latitude]
+              ;; The XSD spec says this is an IEEE 754-2008, and the IEEE
+              ;; wants US$104 for me to find out what that is. So I don't
+              ;; strictly know that an integer is valid here.
+              :validator xsd-float?}
+   :location {:functional false
+              :if-invalid [:must :invalid-location]
+              :validator (fn [pv] (object-or-uri? pv #{"Place"}))}
+   :longitude {:functional true
+               :if-invalid [:must :invalid-longitude]
+               :validator xsd-float?}
+   :mediaType {:functional true
+               :if-invalid [:must :invalid-mime-type]
+               :validator (fn [pv] (truthy? (re-matches #"\w+/[-.\w]+(?:\+[-.\w]+)?" pv)))}
+   :name {:functional false
+          :if-invalid [:must :invalid-name]
+          :validator string?}
+   :next {:functional true
+          :if-invalid [:must :invalid-next-page]
+          :validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
+                                                   "OrderedCollectionPage"}))}
+   :object {:functional false
+            :if-invalid [:must :invalid-direct-object]
+            :validator object-or-uri?}
+   :oneOf {:collection true
+           :functional false
+           ;; a Question should have a `:oneOf` ot `:anyOf`, but at this layer
+           ;; that's hard to check.
+           :if-invalid [:must :invalid-option]
+           :validator object-or-uri?}
+   
+   :orderedItems {:collection true
+           :functional false
+           :if-invalid [:must :invalid-items]
+           :if-missing [:must :no-items-or-pages]
+           :required (fn [x] (or (has-type? x "OrderedCollectionPage")
+                                 (and (has-type? x "OrderedCollection")
+                                      ;; if it's a collection and has pages,
+                                      ;; it doesn't need items.
+                                      (not (:current x))
+                                      (not (:first x))
+                                      (not (:last x)))))
+           :validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))}
+   :origin {:functional false
+            :if-invalid [:must :invalid-origin]
+            :validator object-or-uri?}
+   :partOf {:functional true
+            :if-missing [:must :missing-part-of]
+            :if-invalid [:must :invalid-part-of]
+            :required (fn [x] (object-or-uri? x #{"CollectionPage"
+                                                  "OrderedCollectionPage"}))
+            :validator (fn [pv] (object-or-uri? pv #{"Collection"
+                                                     "OrderedCollection"}))}
+   :prev {:functional true
+          :if-invalid [:must :invalid-prior-page]
+          :validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
+                                                   "OrderedCollectionPage"}))}
+   :preview {:functional false
+             :if-invalid [:must :invalid-preview]
+             ;; probably likely to be an Image or Video, but that isn't stated.
+             :validator object-or-uri?}
+   :published {:functional true
+               :if-invalid [:must :invalid-date-time]
+               :validator xsd-date-time?}
+   :replies {:functional true
+             :if-invalid [:must :invalid-replies]
+             :validator (fn [pv] (object-or-uri? pv #{"Collection"
+                                                      "OrderedCollection"}))}
+   :radius {:functional true
+            :if-invalid [:must :invalid-positive-number]
+            :validator (fn [pv] (and (xsd-float? pv) (> pv 0)))}
+   :rel {:functional false
+         :if-invalid [:must :invalid-link-relation]
+         ;; TODO: this is not really good enough.
+         :validator (fn [pv] (truthy? (re-matches #"[a-zA-A0-9_\-\.\:\?/\\]*" pv)))}
+   :relationship {;; this exists in the spec, but it doesn't seem to be required and it's
+                  ;; extremely hazily specified. 
+                  }
+   :result {:functional false
+            :if-invalid [:must :invalid-result]
+            :validator object-or-uri?}
+   :startIndex {:functional true
+                :if-invalid [:must :invalid-start-index]
+                :validator xsd-non-negative-integer?}
+   :start-time {:functional true
+                :if-invalid [:must :invalid-date-time]
+                :validator xsd-date-time?}
+   :subject {:functional true
+             :if-invalid [:must :invalid-subject]
+             :if-missing [:minor :no-relationship-subject]
+             :required (fn [x] (has-type? x "Relationship"))
+             :validator object-or-uri?}
+   :summary {:functional false
+             :if-invalid [:must :invalid-summary]
+             ;; TODO: HTML formatting is allowed, but other forms of formatting
+             ;; are not. Can this be validated?
+             :validator string?}
+   :tag {:functional false
+         :if-invalid [:must :invalid-tag]
+         :validator object-or-uri?}
+   :target {:functional false
+            :if-invalid [:must :invalid-target]
+            :validator object-or-uri?}
+   :to {:functional false
+        :if-invalid [:must :invalid-to]
+        :validator (fn [pv] (object-or-uri? pv actor-types))}
+   :totalItems {:functional true
+                :if-invalid [:must :invalid-total-items]
+                :validator xsd-non-negative-integer?}
+   :type {:functional false
+          :if-missing [:minor :no-type]
+          :if-invalid [:must :invalid-type]
+          ;; strictly, it's an `anyURI`, but realistically these are not checkable.
+          :validator string?}
+   :units {:functional true
+           :if-invalid [:must :invalid-units]
+           ;; the narrative says that `anyURI`, but actually unless it's a recognised
+           ;; unit the property is useless. These are the units explicitly specified.
+           :validator (fn [pv] (#{"cm" "feet" "inches" "km" "m" "miles"} pv))}
+   :updated {:functional true
+             :if-invalid [:must :invalid-updated]
+             :validator xsd-date-time?}
+   :url {:functional false
+         :if-invalid [:must :invalid-url-property]
+         :validator (fn [pv] (object-or-uri? pv "Link"))}
+   :width {:functional true
+           :if-invalid [:must :invalid-width]
+           :validator xsd-non-negative-integer?}})
+
+(defn check-property-required [obj prop clause]
+  (let [required (:required clause)
+        [severity token] (:if-missing clause)]
+    (when required
+      (when
+       (and (apply required (list obj)) (not (obj prop)))
+        (make-fault-object severity token)))))
+
+(defn check-property-valid
+  [obj prop clause]
+  ;; (info "obj" obj "prop" prop "clause" clause)
+  (let [val (obj prop)
+        validator (:validator clause)
+        [severity token] (:if-invalid clause)]
+    (when (and val validator)
+      (cond-make-fault-object
+       (apply validator (list val))
+       severity token))))
+
+(defn check-property [obj prop]
+  (assert (map? obj))
+  (assert (keyword? prop))
+  (let [clause (object-expected-properties prop)]
+    (nil-if-empty
+     (remove nil?
+             (list
+              (check-property-required obj prop clause)
+              (check-property-valid obj prop clause))))))
+
+(defn properties-faults
+  "Return a lost of faults found on properties of the object `x`, or
+   `nil` if none are."
+  [x]
+  (apply 
+   concat-non-empty
+   (let [props (set (keys x))
+         required (set
+                   (filter
+                    #((object-expected-properties %) :required)
+                    (keys object-expected-properties)))]
+     (map
+      (fn [p] (check-property x p))
+      (union props required)))))
+
+(defn object-faults
+  "Return a list of faults found in object `x`, or `nil` if none are.
+   
+   If `expected-type` is also passed, verify that `x` has `expected-type`.
+   `expected-type` may be passed as a string or as a set of strings. Detailed
+   verification of the particular features of types is not done here."
+
+  ;; TODO: many more properties which are nor required, nevertheless have required
+  ;; property TYPES as detailed in
+  ;; https://www.w3.org/TR/activitystreams-vocabulary/#properties
+  ;; if these properties are present, these types should be checked.
+  ([x]
+   (concat-non-empty
+    (remove empty?
+            (list
+             (when-not (map? x)
+               (make-fault-object :critical :not-an-object))
+             (when-not
+              (has-context? x)
+               (make-fault-object :should :no-context))
+             (when-not (:type x)
+               (make-fault-object :minor :no-type))
+             (when-not (and (map? x) (contains? x :id))
+               (make-fault-object :minor :no-id-transient))))
+    (properties-faults x)))
+  ([x expected-type]
+   (concat-non-empty
+    (object-faults x)
+    (when expected-type
+      (list
+       (has-type-or-fault x expected-type :critical :unexpected-type))))))
+
+(def maybe-reify
+  "If `*reify-refs*` is `true`, return the object at this `target` URI.
+   Returns `nil` if
+   
+   1. `*reify-refs*` is false;
+   2. the object was not found;
+   3. access to the object was not permitted.
+   
+   Consequently, use with care."
+  (memoize
+   (fn [target]
+     (try (let [uri (URI. target)]
+            (when *reify-refs*
+              (json/read-str (slurp uri))))
+          (catch URISyntaxException _
+            (warn "Reification target" target "was not a valid URI.")
+            nil)
+          (catch FileNotFoundException _
+            (warn "Reification target" target "was not found.")
+            nil)))))
+
+(defn maybe-reify-or-faults
+  "If `*reify-refs*` is `true`, runs basic checks on the object at this 
+   `target` URI, if it is found, or a list containing a fault object with
+   this `severity` and `token` if it is not."
+  [value expected-type severity token]
+  (let [object (maybe-reify value)]
+    (cond object
+          (object-faults object expected-type)
+          *reify-refs* (list (make-fault-object severity token)))))
+
+(defn object-reference-or-faults
+  "If this `value` is either 
+   
+   1. an object of `expected-type`;
+   2. a URI referencing an object of  `expected-type`; or
+   3. a link object referencing an object of  `expected-type`
+   
+   and no faults are returned from validating the linked object, then return
+   `nil`; else return a sequence comprising a fault object with this `severity`
+   and `token`, prepended to the faults returned.
+   
+   As with `has-type-or-fault` (q.v.), `expected-type` may be passed as a
+   string, as a set of strings, or `nil` (indicating the type of the 
+   referenced object should not be checked).
+   
+   **NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not
+   actually be checked."
+  [value expected-type severity token]
+  (let [faults (cond
+                 (string? value) (maybe-reify-or-faults value severity token expected-type)
+                 (map? value) (if (has-type? value "Link")
+                                (cond
+                                  ;; if we were looking for a link and we've 
+                                  ;; found a link, that's OK.
+                                  (= expected-type "Link") nil
+                                  (and (set? expected-type) (expected-type "Link")) nil
+                                  (nil? expected-type) nil
+                                  :else
+                                  (object-reference-or-faults
+                                   (:href value) expected-type severity token))
+                                (object-faults value expected-type))
+                 :else (throw
+                        (ex-info
+                         "Argument `value` was not an object or a link to an object"
+                         {:arguments {:value value}
+                          :expected-type expected-type
+                          :severity severity
+                          :token token})))]
+    (when faults (cons (make-fault-object severity token) faults))))
+
+(defn coll-object-reference-or-fault
+  "As object-reference-or-fault, except `value` argument may also be a list of
+   objects and/or object references."
+  [value expected-type severity token]
+  (cond
+    (map? value) (object-reference-or-faults value expected-type severity token)
+    (coll? value) (concat-non-empty
+                   (map
+                    #(object-reference-or-faults
+                      % expected-type severity token)
+                    value))
+    :else (throw
+           (ex-info
+            "Argument `value` was not an object, a link to an object, nor a list of these."
+            {:arguments {:value value}
+             :expected-type expected-type
+             :severity severity
+             :token token}))))
diff --git a/src/dog_and_duck/quack/time.clj b/src/dog_and_duck/quack/time.clj
new file mode 100644
index 0000000..f460f49
--- /dev/null
+++ b/src/dog_and_duck/quack/time.clj
@@ -0,0 +1,66 @@
+(ns dog-and-duck.quack.time
+  "Time, gentleman, please! Recognising and validating date time values."
+  (:require [dog-and-duck.quack.utils :refer [cond-make-fault-object
+                                              make-fault-object
+                                              truthy?]]
+            [scot.weft.i18n.core :refer [get-message]]
+            [taoensso.timbre :refer [warn error]])
+  (:import [java.time LocalDateTime]
+           [java.time.format DateTimeFormatter DateTimeParseException]
+           [javax.xml.datatype DatatypeFactory]))
+
+;;;     Copyright (C) Simon Brooke, 2023
+
+;;;     This program is free software; you can redistribute it and/or
+;;;     modify it under the terms of the GNU General Public License
+;;;     as published by the Free Software Foundation; either version 2
+;;;     of the License, or (at your option) any later version.
+
+;;;     This program is distributed in the hope that it will be useful,
+;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;     GNU General Public License for more details.
+
+;;;     You should have received a copy of the GNU General Public License
+;;;     along with this program; if not, write to the Free Software
+;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+(defn xsd-date-time?
+  "Return `true` if `value` matches the pattern for an 
+   [xsd:dateTime](https://www.w3.org/TR/xmlschema11-2/#dateTime), else `false`"
+  [^String value]
+  (try
+    (if (LocalDateTime/from (.parse DateTimeFormatter/ISO_DATE_TIME value)) true false)
+    (catch DateTimeParseException _
+      (warn (get-message :bad-date-time) ":" value)
+      false)
+    (catch Exception e
+      (error "Exception thrown while parsing date" value e)
+      false)))
+
+(defn xsd-duration?
+  "Return `true` if `value` matches the pattern for an 
+   [xsd:duration](https://www.w3.org/TR/xmlschema11-2/#duration), else `false`"
+  [value]
+  (truthy?
+   (and (string? value)
+        (try (.newDuration (DatatypeFactory/newInstance) value)
+             (catch IllegalArgumentException _
+               (warn (get-message :bad-duration) ":" value)
+               false)
+             (catch Exception e
+               (error "Exception thrown while parsing duration" value e)
+               false)))))
+
+(defn date-time-property-or-fault
+  "If the value of this `property` of object `x` is a valid xsd:dateTime 
+   value, return a fault object with this `token` and `severity`. 
+   
+   If `required?` is false and there is no such property, no fault will be
+   returned."
+  [x property severity token required?]
+  (let [value (property x)]
+    (if (and required? (not (x property)))
+      (make-fault-object severity token)
+      (cond-make-fault-object
+       (and value (xsd-date-time? value)) severity token))))
diff --git a/src/dog_and_duck/quack/utils.clj b/src/dog_and_duck/quack/utils.clj
new file mode 100644
index 0000000..6d2abf4
--- /dev/null
+++ b/src/dog_and_duck/quack/utils.clj
@@ -0,0 +1,289 @@
+(ns dog-and-duck.quack.utils
+  "Utility functions supporting the picky validator"
+  (:require [clojure.set :refer [intersection]]
+            [clojure.string :refer [split]]
+            [dog-and-duck.quack.constants :refer [activitystreams-context-uri
+                                                  actor-types
+                                                  context-key severity-filters
+                                                  validation-fault-context-uri
+                                                  activity-types]]
+            [scot.weft.i18n.core :refer [get-message]]
+            [taoensso.timbre :as log :refer [warn]])
+
+  (:import [java.net URI URISyntaxException]))
+
+;;;     Copyright (C) Simon Brooke, 2022
+
+;;;     This program is free software; you can redistribute it and/or
+;;;     modify it under the terms of the GNU General Public License
+;;;     as published by the Free Software Foundation; either version 2
+;;;     of the License, or (at your option) any later version.
+
+;;;     This program is distributed in the hope that it will be useful,
+;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;     GNU General Public License for more details.
+
+;;;     You should have received a copy of the GNU General Public License
+;;;     along with this program; if not, write to the Free Software
+;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+
+;; (defn actor-type?
+;;   "Return `true` if the `x` is a recognised actor type, else `false`."
+;;   [^String x]
+;;   (if (actor-types x) true false))
+
+(defn truthy?
+  "Return `true` if `x` is truthy, else `false`. There must be some more 
+    idiomatic way to do this?"
+  [x]
+  (if x true false))
+
+(defn xsd-non-negative-integer?
+   "Return `true` if `value` matches the pattern for an 
+    [xsd:nonNegativeInteger](https://www.w3.org/TR/xmlschema11-2/#nonNegativeInteger), else `false`"
+   [x]
+   (and (integer? x)(>= x 0)))
+
+(defn has-type?
+  "Return `true` if object `x` has a type in `acceptable`, else `false`.
+
+   The values of `:type` fields of ActivityStreams objects may be lists; they
+   are considered to have a type if a member of the list is in `acceptable`.
+
+   `acceptable` may be passed as a string, in which case there is only one
+   acceptable value, or as a set of strings, in which case any member of the
+   set is acceptable."
+   [x acceptable]
+   (assert (map? x) (or (string? acceptable) (set? acceptable)))
+   (let [tv (:type x)]
+     (truthy?
+      (cond
+        (and (string? acceptable) 
+             (coll? tv)) (not-empty (filter #(= % acceptable) tv))
+        (and (set? acceptable) 
+             (coll? tv)) (not-empty (filter #(acceptable %) tv))
+        (string? acceptable) (= tv acceptable)
+        (set? acceptable) (acceptable tv)))))
+
+(defn object-or-uri?
+  "Very basic check that `x` is either an object or a URI."
+  ([x]
+   (try
+     (cond (string? x) (uri? (URI. x))
+           (map? x) true
+           :else false)
+     (catch URISyntaxException _ false)
+     (catch NullPointerException _ false)))
+  ([x type]
+   (if (object-or-uri? x)
+     (if (map? x)
+       (has-type? x type)
+       true)
+     false)))
+
+;; (defmacro link-or-uri?
+;;   "Very basic check that `x` is either a link object or a URI."
+;;   [x]
+;;   `(if (object-or-uri? ~x) (has-type? ~x "Link") false))
+
+
+(defn activity-type?
+  "`true` if `x`, a string, represents a recognised ActivityStreams activity
+    type."
+  [^String x]
+  (if (activity-types x) true false))
+
+(defn has-activity-type?
+  "Return `true` if the object `x` has a type which is an activity type, else 
+   `false`."
+  [x]
+  (let [tv (:type x)]
+    (cond
+      (coll? tv) (truthy? (not-empty (filter activity-type? tv)))
+      :else (activity-type? tv))))
+
+;; (defn has-actor-type?
+;;   "Return `true` if the object `x` has a type which is an actor type, else 
+;;    `false`."
+;;   [x]
+;;   (let [tv (:type x)]
+;;     (cond
+;;       (coll? tv) (truthy? (not-empty (filter actor-type? tv)))
+;;       :else (actor-type? tv))))
+
+(defn filter-severity
+  "Return a list of reports taken from these `reports` where the severity
+    of the report is greater than this or equal to this `severity`."
+  [reports severity]
+  (cond (nil?
+         (severity-filters severity)) (throw
+                                       (ex-info
+                                        "Argument `severity` was not a valid severity key"
+                                        {:arguments {:reports reports
+                                                     :severity severity}}))
+        (empty? reports) nil
+        (and
+         (coll? reports)
+         (every? map? reports)
+         (every? :severity reports)) (remove
+                                      #(if  (:severity %)
+                                         ((severity-filters severity) (:severity %))
+                                         false)
+                                      reports)
+        :else (throw
+               (ex-info
+                "Argument `reports` was not a collection of fault reports"
+                {:arguments {:reports reports
+                             :severity severity}}))))
+
+(defn context?
+   "Returns `true` iff `x` quacks like an ActivityStreams context, else false.
+
+    A context is either
+    1. the URI (actually an IRI) `activitystreams-context-uri`, or
+    2. a collection comprising that URI and a map."
+   [x]
+   (cond
+     (nil? x) false ;; fail fast!
+     (string? x) (and (= x activitystreams-context-uri) true)
+     (coll? x) (or
+                (= ((keyword "@vocab") x) activitystreams-context-uri)
+                (and (context? (first (remove map? x)))
+                    (= (count x) 2)
+                    true))
+     :else false))
+
+ (defmacro has-context?
+   "True if `x` is an ActivityStreams object with a valid context, else `false`."
+   [x]
+   `(context? (context-key ~x)))
+
+(def get-pid
+  "Get the process id of the current process.
+
+    OK, this is hacky as fuck, but I hope it works. The problem is that the
+    way to get the process id has changed several times during the history
+    of Java development, and the code for one version of Java won't even compile
+    in a different version."
+  (memoize
+   (fn []
+     (let [java-version (read-string (apply str (take 2
+                                                      (split
+                                                       (System/getProperty "java.version")
+                                                       #"[_\.]"))))
+           cmd (case java-version
+                 18 "(let [[_ pid hostname]
+                     (re-find
+                       #\"^(\\d+)@(.*)\"
+                       (.getName
+                         (java.lang.management.ManagementFactory/getRuntimeMXBean)))]
+                     pid)"
+                 (19 110) "(.pid (java.lang.ProcessHandle/current))"
+                 111 "(.getPid (java.lang.management.ManagementFactory/getRuntimeMXBean))"
+                 ":default")]
+       (eval (read-string cmd))))))
+
+(def get-hostname
+  "return the hostname of the current host.
+
+    Java's methods for getting the hostname are quite startlingly slow, we
+    do not want todo this repeatedly!"
+  (memoize (fn [] (.. java.net.InetAddress getLocalHost getHostName))))
+
+(defn make-fault-object
+  "Return a fault object with these `severity`, `fault` and `narrative` values.
+
+    An ActivityPub object MUST have a globally unique ID. Whether this is 
+    meaningful depends on whether we persist fault report objects and serve
+    them, which at present I have no plans to do."
+   ;; TODO: should not pass in the narrative; instead should use the :fault value
+   ;; to look up the narrative in a resource file.
+  [severity fault]
+  (assoc {}
+         context-key validation-fault-context-uri
+         :id (str "https://"
+                  (get-hostname)
+                  "/fault/"
+                  (get-pid)
+                  ":"
+                  (inst-ms (java.util.Date.)))
+         :type "Fault"
+         :severity severity
+         :fault fault
+         :narrative (or (get-message fault)
+                        (do
+                          (warn "No narrative provided for fault token " fault)
+                          (str fault)))))
+
+(defmacro nil-if-empty
+  "if `x` is an empty collection, return `nil`; else return `x`."
+  [x]
+  `(if (and (coll? ~x) (empty? ~x)) nil
+       ~x))
+
+(defn concat-non-empty
+  "Quick function to replace the pattern (nil-if-empty (remove nil? (concat ...)))
+   which I'm using a lot!"
+  [& lists]
+  (nil-if-empty (remove nil? (apply concat lists))))
+
+(defn has-type-or-fault
+   "If object `x` has a `:type` value which is `acceptable`, return `nil`;
+    else return a fault object with this `severity` and `token`.
+
+    `acceptable` may be passed as either nil, a string, or a set of strings.
+    If `acceptable` is `nil`, no type specific tests will be performed."
+   [x acceptable severity token]
+   (when acceptable
+     (let [tv (:type x)]
+       (when-not
+        (cond
+          (and (string? tv) (string? acceptable)) (= tv acceptable)
+          (and (string? tv) (set? acceptable)) (acceptable tv)
+          (and (coll? tv) (string? acceptable)) ((set tv) acceptable)
+          (and (coll? tv) (set? acceptable)) (not-empty
+                                              (intersection (set tv) acceptable))
+          (not
+           (or (string? acceptable)
+               (set? acceptable))) (throw
+                                    (ex-info
+                                     "`acceptable` argument not as expected."
+                                     {:arguments {:x x
+                                                  :acceptable acceptable
+                                                  :severity severity
+                                                  :token token}})))
+         (make-fault-object severity token)))))
+
+;; (defn any-or-faults
+;;   "Return `nil` if validating one of these options returns `nil`; otherwise 
+;;    return a list comprising a fault report object with this `severity-if-none`
+;;    and this token followed by all the fault reports from validating each
+;;    option.
+
+;;    There are several places - but especially in validating collections - where
+;;    there are several different valid configurations, but few or no properties
+;;    are always required."
+;;   [options severity-if-none token]
+;;   (let [faults (filter empty? options)]
+;;     (when (empty? faults)
+;;       ;; i.e. there was at least one option that returned no faults...
+;;       (cons (make-fault-object severity-if-none token) faults))))
+
+(defn cond-make-fault-object
+  "If `v` is `false` or `nil`, return a fault object with this `severity` and `token`,
+    else return nil."
+  [v severity token]
+  (when-not v (make-fault-object severity token)))
+
+;; (defn string-or-fault
+;;   "If this `value` is not a string, return a fault object with this `severity` 
+;;    and `token`, else `nil`. If `pattern` is also passed, it is expected to be
+;;    a Regex, and the fault object will be returned unless `value` matches the 
+;;    `pattern`."
+;;   ([value severity token]
+;;    (when-not (string? value) (make-fault-object severity token)))
+;;   ([value severity token pattern]
+;;    (when not (and (string? value) (re-matches pattern value))
+;;          (make-fault-object severity token))))
diff --git a/test/quack/core_test.clj b/test/quack/core_test.clj
new file mode 100644
index 0000000..d5af5b6
--- /dev/null
+++ b/test/quack/core_test.clj
@@ -0,0 +1,7 @@
+(ns quack.core-test
+  (:require [clojure.test :refer :all]
+            [quack.core :refer :all]))
+
+(deftest a-test
+  (testing "FIXME, I fail."
+    (is (= 0 1))))