diff --git a/lockscreens/kitsune/LICENSE b/lockscreens/kitsune/LICENSE
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/lockscreens/kitsune/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. 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
+them 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 prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. 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.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ 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.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ 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 3 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, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/lockscreens/kitsune/PROVENANCE.txt b/lockscreens/kitsune/PROVENANCE.txt
new file mode 100644
index 00000000..e822ae6f
--- /dev/null
+++ b/lockscreens/kitsune/PROVENANCE.txt
@@ -0,0 +1,43 @@
+KITSUNE — provenance
+
+The face is 0xSrcAura's own work, built 2026-10-21 for Jurai shell:
+Ase, the seven-tailed kitsune, and her seven tarot folds — his lock
+and login composition, first raised on his own machines, preview-
+proven the night it was built (typing binds the folds one by one;
+at seven the Cast rises; a refusal hurts her and scatters the
+cards; the true key transforms her and opens the seal).
+
+Two roots were written then, and both are preserved in this repo:
+
+ content/Main.qml — the greeter root, seated over SDDM's own
+ machinery (and, through Ryoku's SddmShim,
+ over the session lock as well). Unmodified.
+ faces/lock/Main.qml — the lock-face root, driven by the shell's
+ threshold `gate` contract. Kept as the
+ record of the original seat; not installed.
+
+Her scene (content/gotchi/, the Insei* files and their qmldir),
+her shaders (content/shaders/, .frag sources beside the compiled
+.qsb), and her palette-and-accents singleton
+(content/tw/TransparentWorld.qml) are carried byte-for-byte from
+the Jurai shell tree (apsara/mandara/soul/). Nothing of hers was
+redrawn, renamed, or reworded for the store.
+
+The port's own hand, kept as small as the Fūin port's:
+
+ * content/tw/ArOrchestrator.qml — a resting singleton under the
+ real one's name. In the shell he is the AR stage's living
+ state; the one scene seat that reads him (InseiHub) is never
+ raised at the door, so his shape stands here at rest and the
+ scene files stay untouched.
+ * content/tw/qmldir — declares the two singletons; a greeter
+ registers singletons only when their folder stands as a module.
+ * content/theme.conf + metadata.desktop — the store/greeter
+ dress: the one config key her greeter reads
+ (AllowUppercaseLettersInUsernames=false, her original rule)
+ and her name plate.
+ * Her ground: the greeter still looks for a theme-local
+ Backgrounds/wallpaper.jpg under a manager-published still at
+ /var/lib/valence-greeter/wallpaper.jpg. Neither is bundled —
+ where neither exists she stands on the veiled dark, exactly
+ as the sealed resting state was drawn.
diff --git a/lockscreens/kitsune/assets/preview.webp b/lockscreens/kitsune/assets/preview.webp
new file mode 100644
index 00000000..893a7c2d
Binary files /dev/null and b/lockscreens/kitsune/assets/preview.webp differ
diff --git a/lockscreens/kitsune/content/Main.qml b/lockscreens/kitsune/content/Main.qml
new file mode 100644
index 00000000..fb7a66e8
--- /dev/null
+++ b/lockscreens/kitsune/content/Main.qml
@@ -0,0 +1,412 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Effects
+import "gotchi"
+
+// KITSUNE — the greeter (built 2026-10-21, the captain's order).
+//
+// Ase and her seven folds at the outer door — the same composition
+// that guards the lock, seated over SDDM's own machinery. The login
+// speaks astronaut's exact idiom (the valence theme's Input.qml):
+// userModel/sessionModel ComboBoxes on lastIndex, and
+// sddm.login(user.toLowerCase(), password, selectedSession)
+// because kitsune.conf carries AllowUppercaseLettersInUsernames=false,
+// the same rule valence runs. Nothing of SDDM's PAM path is touched.
+//
+// typing -> one fold binds per character, Ase reacts
+// 7 bound -> INVOKE THE SEAL rises (the Cast = login)
+// login failed -> she is hurt, the cards scatter (astronaut's
+// failed flag + 2s resetError, her ceremony over it)
+// login success -> all seven revealed, she transforms, the seal opens
+//
+// Self-contained by necessity: the greeter runs as user `sddm` and
+// cannot read the captain's home. Her palette is baked at install;
+// the ground is the manager-published still
+// (/var/lib/valence-greeter/wallpaper.jpg, world-readable), with a
+// theme-local copy underneath as the fallback.
+// The user ComboBox is omitted by design: one captain, one account —
+// the username field stands prefilled from userModel.lastUser and
+// stays editable.
+Rectangle {
+ id: root
+ color: "#06070c"
+
+ // Baked palette — matugen's word at install time (matches the
+ // lock face's live palette the night this was built).
+ readonly property color palBackground: "#12131c"
+ readonly property color palText: "#e3e1ef"
+ readonly property color palPrimary: "#bec2ff"
+ readonly property color palSecondary: "#ccc1e9"
+ readonly property color palAlert: "#f43f5e"
+
+ property bool _checking: false
+ property bool _failed: false
+ property bool _leaving: false
+
+ // ---- Her fold canon (the scene's own, mirrored for the wiring) --
+ readonly property var _foldIds: ["truth","power","wisdom","judgment","life","love","discipline"]
+ readonly property var _foldLabels: ["THE EYE","THE PHANTOM","THE BLADE","THE SAGE","THE SHROUD","THE FORGE","THE MIRROR"]
+ readonly property var _foldPrinciples: ["TRUTH","POWER","WISDOM","JUDGMENT","LIFE","LOVE","DISCIPLINE"]
+ readonly property var _foldColors: ["#4ade80","#f43f5e","#00B4D8","#9B1FA0","#818cf8","#f59e0b","#6366f1"]
+
+ // ---- Ceremony state, driven by the password field -----------------
+ readonly property string _pw: passwordField.text
+ readonly property int _bound: Math.min(7, root._pw.length)
+
+ property var _tapped: []
+ property int _activeFoldIdx: -1
+ property color _activeFoldColor: "#00B4D8"
+ property real _activePulse: 0.0
+ property int _lastLen: 0
+
+ NumberAnimation {
+ id: pulseDecay
+ target: root; property: "_activePulse"
+ to: 0.0; duration: 500; easing.type: Easing.OutCubic
+ running: false
+ }
+
+ readonly property real _entropy: {
+ const pw = root._pw;
+ if (pw.length === 0) return 0;
+ const uniq = new Set(pw.split("")).size;
+ const lenScore = Math.min(1, pw.length / 16);
+ const variety = uniq / pw.length;
+ return Math.min(1, lenScore * 0.65 + variety * 0.25 + lenScore * variety * 0.3);
+ }
+
+ function _passwordEdited() {
+ const n = root._pw.length;
+ const bound = Math.min(7, n);
+ if (root._leaving) return;
+
+ if (n > root._lastLen && bound > 0) {
+ const ci = bound - 1;
+ root._activeFoldIdx = ci;
+ root._activeFoldColor = root._foldColors[ci];
+ root._activePulse = 1.0;
+ pulseDecay.restart();
+ scene.aseReact(Qt.color(root._foldColors[ci]));
+ }
+ root._lastLen = n;
+ root._tapped = root._foldIds.slice(0, bound);
+
+ if (root._failed || root._checking) return;
+ if (bound === 0)
+ root._setWhisper("the sigils await · read the cards · invoke your sequence");
+ else if (bound < 7)
+ root._setWhisper(root._foldLabels[bound - 1] + " · " + root._foldPrinciples[bound - 1] + " · bound to your will · " + bound + " of 7");
+ else
+ root._setWhisper("the sequence is complete · invoke the seal to pass");
+ }
+
+ // ---- The whisper (her typewriter, 22ms a letter) ------------------
+ property string _whisperTarget: ""
+ property string _whisperDisplay: ""
+ property int _whisperIdx: 0
+
+ function _setWhisper(msg) {
+ root._whisperTarget = msg;
+ root._whisperDisplay = "";
+ root._whisperIdx = 0;
+ whisperTimer.restart();
+ }
+ Timer {
+ id: whisperTimer; interval: 22; repeat: true
+ onTriggered: {
+ if (root._whisperIdx < root._whisperTarget.length) {
+ root._whisperDisplay += root._whisperTarget[root._whisperIdx];
+ root._whisperIdx++;
+ } else { stop(); }
+ }
+ }
+
+ // ---- Time driver ---------------------------------------------------
+ property real _time: 0.0
+ NumberAnimation on _time {
+ from: 0; to: 100000; duration: 100000000
+ loops: Animation.Infinite; running: true
+ }
+
+ // ---- The ground -----------------------------------------------------
+ Image {
+ anchors.fill: parent
+ source: "Backgrounds/wallpaper.jpg"
+ fillMode: Image.PreserveAspectCrop
+ asynchronous: true
+ }
+ Image {
+ anchors.fill: parent
+ source: "file:///var/lib/valence-greeter/wallpaper.jpg"
+ fillMode: Image.PreserveAspectCrop
+ asynchronous: true
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ blurEnabled: true
+ blur: root._leaving ? 0.3 : 0.9
+ blurMax: 64
+ brightness: root._leaving ? -0.05 : -0.25
+ Behavior on blur { NumberAnimation { duration: 380; easing.type: Easing.OutQuad } }
+ Behavior on brightness { NumberAnimation { duration: 380; easing.type: Easing.OutQuad } }
+ }
+ }
+ Rectangle {
+ anchors.fill: parent
+ color: Qt.alpha(root.palBackground, 0.55)
+ }
+
+ // ---- Her scene (rides high — the fields hold the foot) --------------
+ InseiScene {
+ id: scene
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: parent.height * 0.84
+
+ layout: []
+ tappedFolds: root._tapped
+ lockedCount: root._bound
+ entropy: root._entropy
+ sequenceStr: root._tapped.join(":")
+ time: root._time
+ activeFoldIdx: root._activeFoldIdx
+ activeFoldColor: root._activeFoldColor
+ activePulse: root._activePulse
+ whisper: root._whisperDisplay
+ hasError: root._failed
+ isLoading: root._checking
+ isUnlocked: root._leaving
+ readyToCast: root._bound >= 7 && !root._checking && !root._leaving
+
+ onCastRequested: root.attemptLogin()
+ }
+
+ // ---- The login (astronaut's call, letter for letter) -----------------
+ function attemptLogin() {
+ if (passwordField.text.length === 0 || root._checking || root._leaving) return;
+ root._checking = true;
+ root._setWhisper("the binding speaks · the seal yields…");
+ const u = usernameField.text;
+ sddm.login(config.AllowUppercaseLettersInUsernames == "false" ? u.toLowerCase() : u,
+ passwordField.text, sessionSelect.selectedSession);
+ }
+
+ Connections {
+ target: sddm
+ function onLoginSucceeded() {
+ root._checking = false;
+ root._leaving = true;
+ root._tapped = root._foldIds.slice(0, 7);
+ scene.aseTransform();
+ root._setWhisper("the seal opens · welcome, 0xSrcAura");
+ }
+ function onLoginFailed() {
+ root._checking = false;
+ root._failed = true;
+ scene.aseHurt();
+ shake.restart();
+ passwordField.text = "";
+ root._lastLen = 0;
+ root._tapped = [];
+ resetError.restart();
+ failWhisper.restart();
+ passwordField.forceActiveFocus();
+ }
+ }
+ Timer { id: resetError; interval: 2000; repeat: false; onTriggered: root._failed = false }
+ Timer {
+ id: failWhisper; interval: 150; repeat: false
+ onTriggered: root._setWhisper("the binding refuses · login failed")
+ }
+
+ // ---- The fields, at the foot of her scene -----------------------------
+ Column {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 36
+ spacing: 9
+ z: 2
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ visible: root._failed
+ text: "LOGIN FAILED"
+ color: root.palAlert
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 12
+ font.letterSpacing: 2
+ }
+
+ Rectangle {
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 300
+ height: 40
+ radius: 20
+ color: Qt.alpha("#0a0a12", 0.55)
+ border.width: 1
+ border.color: usernameField.activeFocus ? Qt.alpha(root.palPrimary, 0.6) : Qt.alpha(root.palText, 0.22)
+ Behavior on border.color { ColorAnimation { duration: 200 } }
+
+ TextField {
+ id: usernameField
+ anchors.fill: parent
+ anchors.leftMargin: 18
+ anchors.rightMargin: 18
+ text: userModel.lastUser
+ color: root.palText
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 13
+ font.letterSpacing: 2
+ horizontalAlignment: TextInput.AlignHCenter
+ verticalAlignment: TextInput.AlignVCenter
+ background: Item {}
+ onAccepted: passwordField.forceActiveFocus()
+ }
+ }
+
+ Rectangle {
+ id: pill
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 300
+ height: 46
+ radius: 23
+ color: Qt.alpha("#0a0a12", 0.55)
+ border.width: 1
+ border.color: root._failed
+ ? Qt.alpha(root.palAlert, 0.8)
+ : (passwordField.activeFocus ? Qt.alpha(root.palPrimary, 0.6) : Qt.alpha(root.palText, 0.22))
+ Behavior on border.color { ColorAnimation { duration: 200 } }
+ transform: Translate { id: pillShift }
+
+ SequentialAnimation {
+ id: shake
+ NumberAnimation { target: pillShift; property: "x"; to: -7; duration: 45 }
+ NumberAnimation { target: pillShift; property: "x"; to: 6; duration: 55 }
+ NumberAnimation { target: pillShift; property: "x"; to: -4; duration: 55 }
+ NumberAnimation { target: pillShift; property: "x"; to: 2; duration: 50 }
+ NumberAnimation { target: pillShift; property: "x"; to: 0; duration: 45 }
+ }
+ opacity: root._leaving ? 0 : 1
+ Behavior on opacity { NumberAnimation { duration: 300 } }
+
+ TextField {
+ id: passwordField
+ anchors.fill: parent
+ anchors.leftMargin: 18
+ anchors.rightMargin: 18
+ echoMode: TextInput.Password
+ placeholderText: "PASSWORD"
+ placeholderTextColor: Qt.alpha(root.palText, 0.35)
+ color: root.palText
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 14
+ font.letterSpacing: 2
+ horizontalAlignment: TextInput.AlignHCenter
+ verticalAlignment: TextInput.AlignVCenter
+ background: Item {}
+ enabled: !root._checking
+ onTextChanged: root._passwordEdited()
+ onAccepted: root.attemptLogin()
+ }
+ }
+
+ // The session, astronaut's ComboBox idiom in quiet dress.
+ ComboBox {
+ id: sessionSelect
+ property var selectedSession: currentIndex
+ anchors.horizontalCenter: parent.horizontalCenter
+ model: sessionModel
+ currentIndex: model.lastIndex
+ textRole: "name"
+ background: Item {}
+ indicator: Item {}
+ contentItem: Text {
+ text: "Session (" + sessionSelect.currentText + ")"
+ color: Qt.alpha(root.palText, sessionSelect.hovered ? 0.9 : 0.55)
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 11
+ font.letterSpacing: 1.5
+ horizontalAlignment: Text.AlignHCenter
+ }
+ delegate: ItemDelegate {
+ width: sessionSelect.width
+ contentItem: Text {
+ text: model.name
+ color: root.palText
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 11
+ verticalAlignment: Text.AlignVCenter
+ }
+ background: Rectangle {
+ color: highlighted ? Qt.alpha(root.palPrimary, 0.25) : "transparent"
+ }
+ }
+ popup: Popup {
+ y: -implicitHeight - 4
+ width: sessionSelect.width
+ padding: 6
+ contentItem: ListView {
+ clip: true
+ implicitHeight: contentHeight
+ model: sessionSelect.popup.visible ? sessionSelect.delegateModel : null
+ currentIndex: sessionSelect.highlightedIndex
+ }
+ background: Rectangle {
+ radius: 8
+ color: Qt.alpha("#0a0a12", 0.92)
+ border.width: 1
+ border.color: Qt.alpha(root.palText, 0.18)
+ }
+ }
+ }
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "0XSRCAURA"
+ color: Qt.alpha(root.palText, 0.55)
+ opacity: root._leaving ? 0 : 1
+ Behavior on opacity { NumberAnimation { duration: 150 } }
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 13
+ font.letterSpacing: 4
+ }
+ }
+
+ // ---- The door's other duties: power ----------------------------------
+ Row {
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: 22
+ anchors.bottomMargin: 16
+ spacing: 18
+ z: 2
+
+ Text {
+ text: "reboot"
+ color: Qt.alpha(root.palText, rebootArea.containsMouse ? 0.9 : 0.45)
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 11
+ font.letterSpacing: 1.5
+ MouseArea { id: rebootArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: sddm.reboot() }
+ }
+ Text {
+ text: "power off"
+ color: Qt.alpha(root.palText, powerArea.containsMouse ? 0.9 : 0.45)
+ font.family: "3270 Nerd Font Mono, Fira Code, monospace"
+ font.pixelSize: 11
+ font.letterSpacing: 1.5
+ MouseArea { id: powerArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: sddm.powerOff() }
+ }
+ }
+
+ // ---- Entrance ---------------------------------------------------------
+ Timer {
+ id: manifestTimer; interval: 60; repeat: false
+ onTriggered: {
+ scene.aseWake();
+ root._setWhisper("the sigils await · read the cards · invoke your sequence");
+ passwordField.forceActiveFocus();
+ }
+ }
+ Component.onCompleted: manifestTimer.restart()
+}
diff --git a/lockscreens/kitsune/content/gotchi/InseiCompanion.qml b/lockscreens/kitsune/content/gotchi/InseiCompanion.qml
new file mode 100644
index 00000000..f0bd1a8b
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/InseiCompanion.qml
@@ -0,0 +1,126 @@
+/* InseiCompanion.qml — Ase · Digital Kitsune Companion
+ * ===================================================
+ * Ase is a 7-tailed digital fox spirit rendered via SDF shader.
+ * Item is 280×320 — generously sized so tails and aura have UV
+ * space to render without hitting the texture boundary.
+ * clip is never set — Item does not clip by default.
+ *
+ * Each tail tip is tinted with its fold color via per-tail uniforms.
+ */
+import QtQuick
+import "../tw" as TW
+
+Item {
+ id: root
+
+ implicitWidth: 320
+ implicitHeight: 260
+
+ // ── Public API ────────────────────────────────────────────────────
+ property color foldColor: TW.TransparentWorld.accentWater
+ property real entropy: 0.0
+ property real time: 0.0
+
+ // State machine
+ property string _state: "idle"
+
+ function wake() { _state = "wake"; wakeAnim.restart() }
+ function react(col) { foldColor = col; _state = "react"; reactAnim.restart() }
+ function hurt() { _state = "hurt"; hurtAnim.restart() }
+ function aseTransform() { _state = "transform"; transformAnim.restart() }
+ function dissolve() { _state = "dissolve"; dissolveAnim.restart() }
+ function resetIdle() { _state = "idle" }
+
+ // ── Shader ────────────────────────────────────────────────────────
+ ShaderEffect {
+ anchors.fill: parent
+ blending: true
+
+ property real time: root.time
+ property real entropy: root.entropy
+ property real mood_joy: 0.0
+ property real mood_sad: 0.0
+ property real mood_sleep: 0.0
+ property real fold_r: root.foldColor.r
+ property real fold_g: root.foldColor.g
+ property real fold_b: root.foldColor.b
+ property real w: width
+ property real h: height
+ property real tailCount: 7.0
+
+ // Per-tail fold colors — driven from TransparentWorld, updated to new Sibyl palette
+ // tail 0 → EYE / TRUTH #00C2FF electric cerulean
+ property real tail0_r: TW.TransparentWorld.accentEye.r
+ property real tail0_g: TW.TransparentWorld.accentEye.g
+ property real tail0_b: TW.TransparentWorld.accentEye.b
+ // tail 1 → PHANTOM / POWER #B44FE8 creative violet
+ property real tail1_r: TW.TransparentWorld.accentPhantom.r
+ property real tail1_g: TW.TransparentWorld.accentPhantom.g
+ property real tail1_b: TW.TransparentWorld.accentPhantom.b
+ // tail 2 → BLADE / WISDOM #FF3CAC hot magenta
+ property real tail2_r: TW.TransparentWorld.accentBlade.r
+ property real tail2_g: TW.TransparentWorld.accentBlade.g
+ property real tail2_b: TW.TransparentWorld.accentBlade.b
+ // tail 3 → SAGE / JUDGMENT #FFB300 sun amber
+ property real tail3_r: TW.TransparentWorld.accentSage.r
+ property real tail3_g: TW.TransparentWorld.accentSage.g
+ property real tail3_b: TW.TransparentWorld.accentSage.b
+ // tail 4 → SHROUD / LIFE #00FFC8 bioluminescent teal
+ property real tail4_r: TW.TransparentWorld.accentShroud.r
+ property real tail4_g: TW.TransparentWorld.accentShroud.g
+ property real tail4_b: TW.TransparentWorld.accentShroud.b
+ // tail 5 → FORGE / LOVE #FF6B35 volcanic orange-red
+ property real tail5_r: TW.TransparentWorld.accentForgeFold.r
+ property real tail5_g: TW.TransparentWorld.accentForgeFold.g
+ property real tail5_b: TW.TransparentWorld.accentForgeFold.b
+ // tail 6 → MIRROR / DISCIPLINE #C8B8FF pale lavender
+ property real tail6_r: TW.TransparentWorld.accentMirror.r
+ property real tail6_g: TW.TransparentWorld.accentMirror.g
+ property real tail6_b: TW.TransparentWorld.accentMirror.b
+
+ fragmentShader: "../shaders/InseiKitsune.frag.qsb"
+ }
+
+ // ── Tap to stroke ─────────────────────────────────────────────────
+ TapHandler {
+ onTapped: {
+ var req = new XMLHttpRequest()
+ req.open("POST", "http://127.0.0.1:8001/api/ase/interact", true)
+ req.setRequestHeader("Content-Type", "application/json")
+ req.send(JSON.stringify({ action: "stroke" }))
+ root.react(TW.TransparentWorld.accentWater)
+ }
+ }
+
+ // ── Animations ────────────────────────────────────────────────────
+ SequentialAnimation {
+ id: wakeAnim
+ NumberAnimation { target: root; property: "opacity"; from: 0; to: 1; duration: 1000 }
+ ScriptAction { script: root._state = "idle" }
+ }
+
+ SequentialAnimation {
+ id: reactAnim
+ NumberAnimation { target: root; property: "entropy"; to: 0.8; duration: 200 }
+ NumberAnimation { target: root; property: "entropy"; to: 0.0; duration: 400 }
+ ScriptAction { script: root._state = "idle" }
+ }
+
+ SequentialAnimation {
+ id: hurtAnim
+ NumberAnimation { target: root; property: "entropy"; to: 1.0; duration: 80 }
+ NumberAnimation { target: root; property: "entropy"; to: 0.0; duration: 600 }
+ ScriptAction { script: root._state = "idle" }
+ }
+
+ SequentialAnimation {
+ id: transformAnim
+ PauseAnimation { duration: 1000 }
+ ScriptAction { script: root.dissolve() }
+ }
+
+ SequentialAnimation {
+ id: dissolveAnim
+ NumberAnimation { target: root; property: "opacity"; to: 0; duration: 800 }
+ }
+}
diff --git a/lockscreens/kitsune/content/gotchi/InseiGotchi.qml b/lockscreens/kitsune/content/gotchi/InseiGotchi.qml
new file mode 100644
index 00000000..85da7e03
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/InseiGotchi.qml
@@ -0,0 +1,652 @@
+/* InseiGotchi.qml — The Ase Companion & Tarot Gate · Holographic Scene
+ * ============================================================
+ * Ase floats in open space above a holographic Tarot reveal.
+ * No device shell. Just the scene, the companion, and the sequence.
+ * ============================================================ */
+pragma ComponentBehavior: Bound
+import QtQuick
+import "../tw" as TW
+
+Item {
+ id: aseGotchi
+
+ property bool active: true
+ readonly property real entropyOut: _entropy
+
+ signal authenticated(string token)
+ signal catalystFailed()
+
+ anchors.fill: parent
+ visible: active
+
+ // ── Phase ─────────────────────────────────────────────────────────
+ property int _phase: 0
+ property bool _isLoading: _phase === 2
+ property bool _isUnlocked: _phase === 3
+ property bool _hasError: false
+
+ // ── Tarot state ───────────────────────────────────────────────────
+ property var _layout: []
+ property string _challenge: ""
+ property var _sequence: []
+ property var _tappedFolds: [] // fold IDs in tap order — drives card flip
+ property int _lockedCount: 0
+ property real _entropy: 0.0
+ property bool _configured: false
+ property int _resetKey: 0 // increment to force card re-evaluation on reset
+ property bool _readyToCast: false // true when all 7 drawn, waiting for CAST
+
+ readonly property string _sequenceStr: _sequence.join(":")
+
+ property bool _setupMode: false
+ property var _setupSeq1: []
+ property var _setupSeq2: []
+ property bool _setupPhase2: false
+
+ property int _failCount: 0
+ property bool _lockdown: false
+ property int _lockdownSecs: 30
+
+ // ── Active fold ───────────────────────────────────────────────────
+ property int _activeFoldIdx: -1
+ property color _activeFoldColor: TW.TransparentWorld.accentWater
+ property real _activePulse: 0.0
+
+ NumberAnimation {
+ id: pulseDecay
+ target: aseGotchi; property: "_activePulse"
+ to: 0.0; duration: 500; easing.type: Easing.OutCubic
+ running: false
+ }
+
+ // ── Breach ────────────────────────────────────────────────────────
+ property real _breach: 0.0
+ NumberAnimation {
+ id: breachAnim
+ target: aseGotchi; property: "_breach"
+ from: 0.0; to: 1.0; duration: 900
+ easing.type: Easing.OutCubic; running: false
+ }
+
+ // ── Whisper ───────────────────────────────────────────────────────
+ property string _whisperTarget: "the sigils await..."
+ property string _whisperDisplay: ""
+ property int _whisperIdx: 0
+ property bool _whisperStreaming: false // true when AI is streaming
+
+ function _setWhisper(msg) {
+ _whisperTarget = msg
+ _whisperDisplay = ""
+ _whisperIdx = 0
+ _whisperStreaming = false
+ whisperTimer.restart()
+ }
+
+ // AI whisper — streams from local Ollama via kasugai dispatch
+ // Falls back to static msg if daemon is offline or response is slow
+ function _whisperAI(context, fallback) {
+ if (_daemonState !== "alive") { _setWhisper(fallback); return }
+
+ _whisperDisplay = ""
+ _whisperStreaming = true
+ whisperTimer.stop()
+
+ var prompt = "You are Ase, a 7-tailed kitsune spirit guarding the Akashic Gate for SourceAura. " +
+ "Speak in 1 sentence. Mystical, direct, sorcerer register. No markdown. Just the whisper. " +
+ "Context: " + context
+
+ var req = new XMLHttpRequest()
+ req.onreadystatechange = function() {
+ if (req.readyState === XMLHttpRequest.LOADING || req.readyState === XMLHttpRequest.DONE) {
+ // Stream tokens as they arrive
+ var raw = req.responseText || ""
+ // Parse SSE lines — each line is "data: {token}" or the full response
+ var lines = raw.split("\n")
+ var newText = ""
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i].trim()
+ if (line.startsWith("data: ")) {
+ try {
+ var d = JSON.parse(line.slice(6))
+ if (d.token) newText += d.token
+ else if (d.response) newText = d.response
+ } catch(e) {}
+ }
+ }
+ if (newText !== "") aseGotchi._whisperDisplay = newText.slice(0, 120)
+ }
+ if (req.readyState === XMLHttpRequest.DONE) {
+ aseGotchi._whisperStreaming = false
+ // If nothing came back, use fallback
+ if (aseGotchi._whisperDisplay === "") aseGotchi._setWhisper(fallback)
+ }
+ }
+ req.onerror = function() { aseGotchi._whisperStreaming = false; aseGotchi._setWhisper(fallback) }
+
+ // 3s timeout — gate must be fast
+ var timeoutTimer = Qt.createQmlObject('import QtQuick; Timer { interval: 3000; repeat: false }', aseGotchi)
+ timeoutTimer.triggered.connect(function() {
+ if (aseGotchi._whisperStreaming && aseGotchi._whisperDisplay === "") {
+ req.abort()
+ aseGotchi._whisperStreaming = false
+ aseGotchi._setWhisper(fallback)
+ }
+ timeoutTimer.destroy()
+ })
+ timeoutTimer.start()
+
+ req.open("POST", "http://127.0.0.1:8001/api/kasugai/dispatch", true)
+ req.setRequestHeader("Content-Type", "application/json")
+ req.send(JSON.stringify({ message: prompt, stream: true, max_tokens: 40 }))
+ }
+
+ Timer {
+ id: whisperTimer; interval: 22; repeat: true
+ onTriggered: {
+ if (aseGotchi._whisperIdx < aseGotchi._whisperTarget.length) {
+ aseGotchi._whisperDisplay += aseGotchi._whisperTarget[aseGotchi._whisperIdx]
+ aseGotchi._whisperIdx++
+ } else { stop() }
+ }
+ }
+
+ // ── Daemon state ──────────────────────────────────────────────────
+ property string _daemonState: "checking"
+
+ function _fetchThreats() {
+ var req = new XMLHttpRequest()
+ req.onreadystatechange = function() {
+ if (req.readyState !== XMLHttpRequest.DONE) return
+ if (req.status === 200) {
+ try {
+ var d = JSON.parse(req.responseText)
+ aseGotchi._daemonState = d.status === "alive" ? "alive" : "degraded"
+ } catch(e) { aseGotchi._daemonState = "degraded" }
+ } else { aseGotchi._daemonState = "dead" }
+ }
+ req.onerror = function() { aseGotchi._daemonState = "dead" }
+ req.open("GET", "http://127.0.0.1:8001/api/health/ready", true)
+ req.send()
+ }
+
+ // ── Lockdown ──────────────────────────────────────────────────────
+ Timer {
+ id: lockdownTimer; interval: 1000; repeat: true; running: aseGotchi._lockdown
+ onTriggered: {
+ aseGotchi._lockdownSecs--
+ aseGotchi._setWhisper("binding locked · " + aseGotchi._lockdownSecs + "s until the seal breathes again")
+ if (aseGotchi._lockdownSecs <= 0) {
+ aseGotchi._lockdown = false
+ aseGotchi._failCount = 0
+ aseGotchi._lockdownSecs = 30
+ aseGotchi._phase = 1
+ aseGotchi._setWhisper("the seal breathes · read the sigils · invoke again")
+ numpadFocus.forceActiveFocus()
+ }
+ }
+ }
+
+ // ── Entrance / exit ───────────────────────────────────────────────
+ property real _scale: 0.0
+ property real _opacity: 0.0
+ Behavior on _scale { NumberAnimation { duration: 400; easing.type: Easing.OutBack; easing.overshoot: 0.25 } }
+ Behavior on _opacity { NumberAnimation { duration: 300 } }
+
+ Component.onCompleted: { if (active) _manifest() }
+ onActiveChanged: { if (active) _manifest(); else _dismiss() }
+
+ function _manifest() {
+ _phase = 0
+ _sequence = []
+ _tappedFolds = []
+ _lockedCount = 0
+ _entropy = 0.0
+ _hasError = false
+ _failCount = 0
+ _lockdown = false
+ _lockdownSecs = 30
+ _setupMode = false
+ _setupSeq1 = []
+ _setupSeq2 = []
+ _setupPhase2 = false
+ _activeFoldIdx = -1
+ _activePulse = 0.0
+ _breach = 0.0
+ _readyToCast = false
+ _resetKey = 0
+ _whisperAI("the gate opens for SourceAura · greet them as they approach",
+ "the sigils await · read the cards · invoke your sequence")
+ _fetchThreats()
+ _fetchChallenge()
+ manifestTimer.restart()
+ }
+
+ function _dismiss() {
+ _scale = 0.0
+ _opacity = 0.0
+ }
+
+ Timer {
+ id: manifestTimer; interval: 60; repeat: false
+ onTriggered: {
+ aseGotchi._phase = 1
+ aseGotchi._scale = 1.0
+ aseGotchi._opacity = 1.0
+ scene.aseWake()
+ numpadFocus.forceActiveFocus()
+ }
+ }
+
+ // ── Challenge fetch ───────────────────────────────────────────────
+ function _fetchChallenge() {
+ var req = new XMLHttpRequest()
+ req.onreadystatechange = function() {
+ if (req.readyState !== XMLHttpRequest.DONE) return
+ if (req.status === 200) {
+ try {
+ var d = JSON.parse(req.responseText)
+ aseGotchi._challenge = d.challenge || ""
+ aseGotchi._layout = d.layout || []
+ aseGotchi._configured = d.configured === true
+ if (aseGotchi._configured) {
+ aseGotchi._setupMode = false
+ aseGotchi._setWhisper("the sigils await · read the cards · invoke your sequence")
+ } else {
+ aseGotchi._setupMode = true
+ aseGotchi._setWhisper("the binding is unwritten · inscribe your sequence across the seven folds")
+ }
+ } catch(e) {}
+ } else {
+ aseGotchi._setWhisper("the daemon sleeps · the gate cannot hear you")
+ }
+ }
+ req.onerror = function() { aseGotchi._setWhisper("gate offline · daemon unreachable") }
+ req.open("GET", "http://127.0.0.1:8001/api/auth/gate/challenge", true)
+ req.send()
+ }
+
+ // ── Fold helpers ──────────────────────────────────────────────────
+ readonly property var _canonicalOrder: ["truth","power","wisdom","judgment","life","love","discipline"]
+ readonly property var _foldColors: [
+ "#4ade80","#f43f5e","#00B4D8","#9B1FA0","#818cf8","#f59e0b","#6366f1"
+ ]
+ // Kasugai label — matches backend FOLDS and InseiScene
+ readonly property var _foldLabelMap: ({
+ "truth": "THE EYE",
+ "power": "THE PHANTOM",
+ "wisdom": "THE BLADE",
+ "judgment": "THE SAGE",
+ "life": "THE SHROUD",
+ "love": "THE FORGE",
+ "discipline": "THE MIRROR"
+ })
+ // Principle name
+ readonly property var _principleMap: ({
+ "truth": "TRUTH",
+ "power": "POWER",
+ "wisdom": "WISDOM",
+ "judgment": "JUDGMENT",
+ "life": "LIFE",
+ "love": "LOVE",
+ "discipline": "DISCIPLINE"
+ })
+
+ function _canonicalIndex(foldId) {
+ for (var i = 0; i < _canonicalOrder.length; i++)
+ if (_canonicalOrder[i] === foldId) return i
+ return 0
+ }
+
+ // ── Card reset helper — clears sequence and forces flip-back ─────
+ function _resetCards() {
+ _sequence = []
+ _tappedFolds = []
+ _lockedCount = 0
+ _entropy = 0.0
+ _activeFoldIdx = -1
+ _activePulse = 0.0
+ _setupSeq1 = []
+ _setupSeq2 = []
+ _setupPhase2 = false
+ _readyToCast = false
+ _resetKey = _resetKey + 1 // force delegate re-evaluation
+ }
+
+ // ── Input ─────────────────────────────────────────────────────────
+
+ function _onNumpad(key) {
+ if (_lockdown || _phase !== 1) return
+ var num = parseInt(key)
+ if (isNaN(num) || num < 1 || num > 7) return
+
+ var foldId = ""
+ for (var i = 0; i < _layout.length; i++) {
+ if (parseInt(_layout[i].position) === num) { foldId = _layout[i].id; break }
+ }
+ if (!foldId) return
+
+ if (_setupMode) _handleSetupInput(foldId)
+ else _handleAuthInput(foldId)
+ }
+
+ function _handleAuthInput(foldId) {
+ var seq = _sequence.slice()
+ if (seq.indexOf(foldId) !== -1) {
+ _setWhisper(_foldLabelMap[foldId] + " · already bound · choose another")
+ return
+ }
+ seq.push(foldId)
+ _sequence = seq
+ _tappedFolds = seq.slice()
+ _lockedCount = seq.length
+ _entropy = seq.length / 7.0
+
+ var ci = _canonicalIndex(foldId)
+ _activeFoldIdx = ci
+ _activeFoldColor = Qt.color(_foldColors[ci])
+ _activePulse = 1.0
+ pulseDecay.restart()
+
+ scene.aseReact(Qt.color(_foldColors[ci]))
+ _setWhisper(_foldLabelMap[foldId] + " · " + _principleMap[foldId] + " · bound to your will · " + seq.length + " of 7")
+
+ if (seq.length === 7) {
+ _readyToCast = true
+ _whisperAI("all seven folds are bound · the sequence is complete · speak the invocation",
+ "the sequence is complete · invoke the seal to pass")
+ }
+ }
+
+ // ── Cast — fires verify after all 7 drawn ─────────────────────────
+ function _cast() {
+ if (_lockdown || _phase !== 1) return
+ if (_setupMode) {
+ if (_setupPhase2 && _setupSeq2.length === 7) {
+ _phase = 2
+ _setWhisper("the binding speaks · the seal yields…")
+ scene.aseTransform()
+ breachAnim.restart()
+ _registerSequence(_setupSeq1, _setupSeq2)
+ } else if (!_setupPhase2 && _setupSeq1.length === 7) {
+ _setupPhase2 = true
+ _tappedFolds = []
+ _lockedCount = 0
+ _entropy = 0.0
+ _activeFoldIdx = -1
+ _readyToCast = false
+ _setWhisper("the first binding is sealed · draw the same sequence again to confirm")
+ }
+ } else {
+ if (_sequence.length === 7) {
+ _phase = 2
+ _setWhisper("the binding speaks · the seal yields…")
+ scene.aseTransform()
+ breachAnim.restart()
+ _verifySequence(_sequence)
+ }
+ }
+ }
+
+ function _handleSetupInput(foldId) {
+ var ci = _canonicalIndex(foldId)
+ _activeFoldIdx = ci
+ _activeFoldColor = Qt.color(_foldColors[ci])
+ _activePulse = 1.0
+ pulseDecay.restart()
+ scene.aseReact(Qt.color(_foldColors[ci]))
+
+ if (!_setupPhase2) {
+ var s1 = _setupSeq1.slice()
+ if (s1.indexOf(foldId) !== -1) {
+ _setWhisper(_foldLabelMap[foldId] + " · already bound · choose another")
+ return
+ }
+ s1.push(foldId)
+ _setupSeq1 = s1
+ _tappedFolds = s1.slice()
+ _lockedCount = s1.length
+ _entropy = s1.length / 7.0
+ _setWhisper(_foldLabelMap[foldId] + " · " + _principleMap[foldId] + " · " + s1.length + " of 7 inscribed")
+ if (s1.length === 7) {
+ _readyToCast = true
+ _setWhisper("the first binding is written · invoke to seal it")
+ }
+ } else {
+ var s2 = _setupSeq2.slice()
+ if (s2.indexOf(foldId) !== -1) {
+ _setWhisper(_foldLabelMap[foldId] + " · already bound · choose another")
+ return
+ }
+ s2.push(foldId)
+ _setupSeq2 = s2
+ _tappedFolds = s2.slice()
+ _lockedCount = s2.length
+ _entropy = s2.length / 7.0
+ _setWhisper(_foldLabelMap[foldId] + " · CONFIRM · " + s2.length + " of 7")
+ if (s2.length === 7) {
+ _readyToCast = true
+ _setWhisper("the confirmation is written · invoke to bind the seal forever")
+ }
+ }
+ }
+
+ function _verifySequence(seq) {
+ var r = new XMLHttpRequest()
+ r.onreadystatechange = function() {
+ if (r.readyState !== XMLHttpRequest.DONE) return
+ if (r.status === 200) {
+ try {
+ var d = JSON.parse(r.responseText)
+ aseGotchi._phase = 3
+ aseGotchi._whisperAI("SourceAura has passed through the Akashic Gate · welcome them",
+ "the seal opens · welcome, SourceAura")
+ aseGotchi._triggerUnlock(d.session_token || "")
+ } catch(e) {
+ aseGotchi._deflect("the binding failed to parse · " + e)
+ }
+ } else {
+ var detail = ""
+ try { detail = JSON.parse(r.responseText).detail || r.status } catch(e) { detail = r.status }
+ aseGotchi._deflect(detail)
+ }
+ }
+ r.onerror = function() { aseGotchi._deflect("the daemon sleeps · the gate cannot hear you") }
+ r.open("POST", "http://127.0.0.1:8001/api/auth/gate/verify", true)
+ r.setRequestHeader("Content-Type", "application/json")
+ r.send(JSON.stringify({ sequence: seq, challenge: aseGotchi._challenge }))
+ }
+
+ function _registerSequence(seq1, seq2) {
+ var r = new XMLHttpRequest()
+ r.onreadystatechange = function() {
+ if (r.readyState !== XMLHttpRequest.DONE) return
+ if (r.status === 200) {
+ aseGotchi._setupMode = false
+ aseGotchi._configured = true
+ aseGotchi._phase = 3
+ aseGotchi._setWhisper("the seal is bound · the gate remembers your will, SourceAura")
+ aseGotchi._triggerUnlock("setup-complete")
+ } else {
+ aseGotchi._deflect("the binding failed · sequences do not match · begin again")
+ aseGotchi._setupPhase2 = false
+ aseGotchi._setupSeq1 = []
+ aseGotchi._setupSeq2 = []
+ aseGotchi._lockedCount = 0
+ }
+ }
+ r.open("POST", "http://127.0.0.1:8001/api/auth/gate/setup", true)
+ r.setRequestHeader("Content-Type", "application/json")
+ r.send(JSON.stringify({ sequence: seq1, confirm: seq2, challenge: aseGotchi._challenge }))
+ }
+
+ function _triggerUnlock(token) {
+ unlockTimer.token = token
+ unlockTimer.restart()
+ }
+
+ Timer {
+ id: unlockTimer; property string token: ""; interval: 1000; repeat: false
+ onTriggered: {
+ aseGotchi._scale = 0.0
+ aseGotchi._opacity = 0.0
+ releaseTimer.token = token
+ releaseTimer.restart()
+ }
+ }
+
+ Timer {
+ id: releaseTimer; property string token: ""; interval: 400; repeat: false
+ onTriggered: aseGotchi.authenticated(token)
+ }
+
+ function _deflect(msg) {
+ _setWhisper(msg)
+ _hasError = true
+ _phase = 1
+ _resetCards()
+ scene.aseHurt()
+ _failCount++
+ catalystFailed()
+ if (_failCount >= 3) {
+ _lockdown = true
+ _lockdownSecs = 30
+ _phase = 0
+ _whisperAI("the binding has been violated three times · speak a warning of consequence",
+ "THE BINDING SEVERS · resonance locked · 30s")
+ } else {
+ errorResetTimer.restart()
+ numpadFocus.forceActiveFocus()
+ }
+ }
+
+ Timer {
+ id: errorResetTimer; interval: 2500; repeat: false
+ onTriggered: {
+ aseGotchi._hasError = false
+ aseGotchi._fetchChallenge()
+ }
+ }
+
+ // ── Backdrop ──────────────────────────────────────────────────────
+ Rectangle {
+ anchors.fill: parent
+ color: Qt.rgba(0, 0, 0, aseGotchi._phase >= 1 ? 0.65 : 0.0)
+ Behavior on color { ColorAnimation { duration: 500 } }
+ }
+
+ // ── Scene ─────────────────────────────────────────────────────────
+ InseiScene {
+ id: scene
+ anchors.fill: parent
+ scale: aseGotchi._scale
+ opacity: aseGotchi._opacity
+ transformOrigin: Item.Center
+
+ readyToCast: aseGotchi._readyToCast
+ lockedCount: aseGotchi._lockedCount
+ entropy: aseGotchi._entropy
+ hasError: aseGotchi._hasError
+ isUnlocked: aseGotchi._isUnlocked
+ isLoading: aseGotchi._isLoading
+ whisper: aseGotchi._whisperDisplay
+ sequenceStr: aseGotchi._sequenceStr
+ layout: aseGotchi._layout
+ tappedFolds: aseGotchi._tappedFolds
+ time: aseGotchi._time
+ activeFoldIdx: aseGotchi._activeFoldIdx
+ activeFoldColor: aseGotchi._activeFoldColor
+ activePulse: aseGotchi._activePulse
+ breach: aseGotchi._breach
+ setupMode: aseGotchi._setupMode
+ setupPhase2: aseGotchi._setupPhase2
+ resetKey: aseGotchi._resetKey
+
+ onCardTapped: function(foldId, position) {
+ if (_setupMode) _handleSetupInput(foldId)
+ else _handleAuthInput(foldId)
+ }
+ onCastRequested: aseGotchi._cast()
+ }
+
+ // ── Time driver ───────────────────────────────────────────────────
+ property real _time: 0.0
+ NumberAnimation on _time {
+ from: 0; to: 100000; duration: 100000000
+ loops: Animation.Infinite; running: aseGotchi.active
+ }
+
+ // ── Numpad focus trap ─────────────────────────────────────────────
+ Item {
+ id: numpadFocus
+ anchors.fill: parent
+ focus: true
+
+ Keys.onPressed: function(event) {
+ var k = event.key
+ var num = 0
+ if (k === Qt.Key_1 || k === Qt.Key_KP_1) num = 1
+ else if (k === Qt.Key_2 || k === Qt.Key_KP_2) num = 2
+ else if (k === Qt.Key_3 || k === Qt.Key_KP_3) num = 3
+ else if (k === Qt.Key_4 || k === Qt.Key_KP_4) num = 4
+ else if (k === Qt.Key_5 || k === Qt.Key_KP_5) num = 5
+ else if (k === Qt.Key_6 || k === Qt.Key_KP_6) num = 6
+ else if (k === Qt.Key_7 || k === Qt.Key_KP_7) num = 7
+
+ if (num > 0) {
+ aseGotchi._onNumpad(num.toString())
+ event.accepted = true
+
+ } else if (k === Qt.Key_B) {
+ // B — dev bypass (local only)
+ if (!aseGotchi._lockdown) {
+ aseGotchi._phase = 3
+ aseGotchi._setWhisper("sovereign passage · the seal yields")
+ aseGotchi._triggerUnlock("bypass-token")
+ }
+ event.accepted = true
+
+ } else if (k === Qt.Key_R) {
+ // R — reshuffle
+ if (!aseGotchi._lockdown && aseGotchi._phase === 1) {
+ aseGotchi._resetCards()
+ aseGotchi._fetchChallenge()
+ aseGotchi._setWhisper("the cards scatter · read the new arrangement")
+ }
+ event.accepted = true
+
+ } else if (k === Qt.Key_Return || k === Qt.Key_Enter || k === Qt.Key_KP_Enter) {
+ if (!aseGotchi._lockdown && aseGotchi._phase === 1) {
+ if (aseGotchi._readyToCast) {
+ aseGotchi._cast()
+ } else {
+ var drawn = aseGotchi._setupMode
+ ? (aseGotchi._setupPhase2 ? aseGotchi._setupSeq2.length : aseGotchi._setupSeq1.length)
+ : aseGotchi._sequence.length
+ var remaining = 7 - drawn
+ aseGotchi._setWhisper(drawn + " of 7 bound · " + remaining + " fold" + (remaining !== 1 ? "s" : "") + " remain · read the sigils")
+ }
+ }
+ event.accepted = true
+
+ } else if (k === Qt.Key_Escape) {
+ if (aseGotchi._lockdown) {
+ event.accepted = true
+ return
+ }
+ var hasProgress = aseGotchi._sequence.length > 0
+ || aseGotchi._setupSeq1.length > 0
+ || aseGotchi._setupSeq2.length > 0
+ if (hasProgress) {
+ aseGotchi._resetCards()
+ aseGotchi._fetchChallenge()
+ aseGotchi._setWhisper("the cards scatter · read the new arrangement")
+ } else {
+ // Clean state — exit the gate
+ aseGotchi._dismiss()
+ aseGotchi.catalystFailed()
+ }
+ event.accepted = true
+ }
+ }
+ }
+}
diff --git a/lockscreens/kitsune/content/gotchi/InseiHub.qml b/lockscreens/kitsune/content/gotchi/InseiHub.qml
new file mode 100644
index 00000000..4f833ccf
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/InseiHub.qml
@@ -0,0 +1,206 @@
+/* InseiHub.qml — Ase's Living Portal (Unbound)
+ * ============================================
+ * Simplified, holographic HUD. Ase floats directly on the overlay
+ * surrounded by her 7 orbital sigils. No rigid shell.
+ *
+ * §14.8 — Cooperative + FBO, dynamic orbital physics.
+ */
+pragma ComponentBehavior: Bound
+import QtQuick
+import QtQuick.Effects
+import "../tw" as TW
+import "."
+
+Item {
+ id: root
+
+ // ── Geometry ──────────────────────────────────────────────────────
+ readonly property real _width: 340
+ readonly property real _height: 400
+ readonly property real _aseSize: 180 // companion scale target
+ readonly property real _orbitR: 110 // default orbit radius
+ readonly property real _activeR: 85 // radius when fold is drawn
+
+ width: _width
+ height: _height
+ implicitWidth: _width
+ implicitHeight: _height
+
+ // ── Position Reporting ────────────────────────────────────────────
+ function _reportPosition() {
+ TW.ArOrchestrator.aseWidgetCenterX = 0.5
+ TW.ArOrchestrator.aseWidgetCenterY = 0.0
+ }
+ Component.onCompleted: Qt.callLater(_reportPosition)
+
+ // ── Derived State ─────────────────────────────────────────────────
+ readonly property color _accent: TW.ArOrchestrator.prismaticColor
+ readonly property bool _thinking: TW.ArOrchestrator.thinkingPhase > 0
+ readonly property string _phase: TW.ArOrchestrator.scryCommPhase || "idle"
+ readonly property bool _online: TW.ArOrchestrator.daemonState === "alive"
+ readonly property var _drawn: TW.ArOrchestrator.aseDrawnFolds || []
+ readonly property string _activeMode: TW.ArOrchestrator.intelActiveMode || ""
+ readonly property real _cmdPulse: TW.ArOrchestrator.commandPulse || 0.0
+ readonly property real _animPhase: TW.ArOrchestrator.animationPhase || 0.0
+
+ // Style accent — bleeds with breathingStyle
+ readonly property color _styleAccent: {
+ var s = TW.ArOrchestrator.breathingStyle || "moon"
+ var sa = TW.TransparentWorld.styleAccents
+ return sa[s] ? Qt.color(sa[s]) : _accent
+ }
+
+ // ── Fold Definitions (Sibyl Trinity Alignment) ───────────────────
+ readonly property var _folds: [
+ { id: "eye", sigil: "◉", color: TW.TransparentWorld.accentEye },
+ { id: "phantom", sigil: "⊗", color: TW.TransparentWorld.accentPhantom },
+ { id: "blade", sigil: "⚔", color: TW.TransparentWorld.accentBlade },
+ { id: "sage", sigil: "◈", color: TW.TransparentWorld.accentSage },
+ { id: "shroud", sigil: "☁", color: TW.TransparentWorld.accentShroud },
+ { id: "forge", sigil: "⬡", color: TW.TransparentWorld.accentForgeFold },
+ { id: "mirror", sigil: "◌", color: TW.TransparentWorld.accentMirror }
+ ]
+
+ // ── Animation Clock ───────────────────────────────────────────────
+ property real _time: 0.0
+ NumberAnimation on _time {
+ from: 0; to: 100000; duration: 100000000
+ loops: Animation.Infinite
+ running: root.visible
+ }
+
+ // ── Viewport Center ───────────────────────────────────────────────
+ readonly property real _cx: width / 2
+ readonly property real _cy: height * 0.45
+
+ // ── Companion — Ase Unbound ───────────────────────────────────────
+ Item {
+ id: _aseContainer
+ x: root._cx - root._aseSize / 2
+ y: root._cy - root._aseSize / 2
+ width: root._aseSize
+ height: root._aseSize
+ z: 10
+
+ // InseiSoul — the living void
+ ShaderEffect {
+ anchors.fill: parent
+ property real time: root._time * (root._thinking ? 2.2 : 1.0)
+ property real tpsBeat: Math.min(1.0, (TW.ArOrchestrator.shiguruiTokensPerSec || 0) / 30.0)
+ property real inferring: root._thinking ? 1.0 : 0.0
+ property real disciplineHue: 0.55 // Default
+ property real commandPulse: root._cmdPulse
+ property real obeliskOpen: 1.0
+ property real cursorX: 0.5
+ property real cursorY: 0.5
+ property real w: width
+ property real h: height
+ property real mistDensity: root._online ? 0.0 : 0.6
+ property real receivePulse: 0.0
+ fragmentShader: "../shaders/InseiSoul.frag.qsb"
+ }
+
+ InseiCompanion {
+ id: _ase
+ anchors.centerIn: parent
+ scale: root._aseSize / 260.0
+ foldColor: root._accent
+ time: root._time * (root._thinking ? 2.2 : 1.0)
+ }
+
+ // Action potential ripple
+ Rectangle {
+ anchors.centerIn: parent
+ width: parent.width * 1.5; height: width; radius: width / 2
+ color: "transparent"
+ border.width: 1.5
+ border.color: Qt.rgba(root._accent.r, root._accent.g, root._accent.b, 0.4)
+ opacity: 0.0
+ scale: 0.5
+
+ SequentialAnimation on opacity {
+ running: root._cmdPulse > 0.1
+ NumberAnimation { from: 0.6; to: 0.0; duration: 800; easing.type: Easing.OutCubic }
+ }
+ SequentialAnimation on scale {
+ running: root._cmdPulse > 0.1
+ NumberAnimation { from: 0.5; to: 1.5; duration: 800; easing.type: Easing.OutCubic }
+ }
+ }
+ }
+
+ // ── Orbital Sigils — 7 dynamic indicators ─────────────────────────
+ Repeater {
+ model: root._folds.length
+ delegate: Item {
+ id: _sigilItem
+ required property int index
+
+ readonly property var _fd: root._folds[index]
+ readonly property bool _drawn: root._drawn.indexOf(_fd.id) >= 0
+ readonly property bool _active: root._activeMode === _fd.id
+ readonly property color _col: _fd.color
+
+ // Orbital physics
+ readonly property real _baseAngle: (index / 7.0) * Math.PI * 2
+ readonly property real _drift: Math.sin(root._time * 0.001 + _baseAngle) * 0.1
+ readonly property real _angle: _baseAngle + (root._time * 0.0005) + _drift
+
+ readonly property real _targetR: _sigilItem._drawn ? root._activeR : root._orbitR
+ property real _currentR: _targetR
+ Behavior on _currentR { NumberAnimation { duration: 800; easing.type: Easing.OutBack } }
+
+ x: root._cx + _currentR * Math.cos(_angle) - 16
+ y: root._cy + _currentR * Math.sin(_angle) - 16
+ width: 32
+ height: 32
+ z: 20
+
+ // Sigil Glyph
+ Text {
+ anchors.centerIn: parent
+ text: _fd.sigil
+ color: _sigilItem._drawn
+ ? Qt.rgba(_col.r, _col.g, _col.b, 1.0)
+ : Qt.rgba(_col.r, _col.g, _col.b, 0.3)
+ font.pixelSize: _sigilItem._drawn ? 20 : 16
+ Behavior on color { ColorAnimation { duration: 400 } }
+ Behavior on font.pixelSize { NumberAnimation { duration: 400; easing.type: Easing.OutBack } }
+
+ layer.enabled: _sigilItem._drawn
+ layer.effect: MultiEffect {
+ shadowEnabled: true; blurEnabled: false
+ shadowColor: Qt.rgba(_col.r, _col.g, _col.b, 0.6)
+ shadowBlur: 0.8
+ }
+ }
+
+ // Interaction
+ HoverHandler { cursorShape: Qt.PointingHandCursor }
+ TapHandler {
+ onTapped: {
+ if (_sigilItem._drawn)
+ TW.ArOrchestrator.aseHubRecallFold = _sigilItem._fd.id
+ else
+ TW.ArOrchestrator.aseHubDrawFold = _sigilItem._fd.id
+ }
+ }
+ }
+ }
+
+ // ── Live Hash — cascading below Ase ───────────────────────────────
+ InseiLiveHash {
+ id: _hash
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: _aseContainer.bottom
+ anchors.topMargin: 20
+ width: 300
+ active: root._online
+ entropy: 0.8 + 0.2 * Math.sin(root._time * 0.002)
+ password: "shigurui:zenith" // Synthetic seed
+ tappedFolds: root._drawn
+ opacity: root._online ? 0.7 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 600 } }
+ }
+
+}
diff --git a/lockscreens/kitsune/content/gotchi/InseiHud.qml b/lockscreens/kitsune/content/gotchi/InseiHud.qml
new file mode 100644
index 00000000..3006fd13
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/InseiHud.qml
@@ -0,0 +1,134 @@
+/* InseiHud.qml — Gate Telemetry Strip
+ * =====================================
+ * 7 fold sigil dots + entropy bar + live hash.
+ * Dots fill with fold color as each card is tapped.
+ * Centered under InseiCompanion.
+ */
+import QtQuick
+import QtQuick.Effects
+import "../tw" as TW
+
+Item {
+ id: root
+
+ property real entropy: 0.0
+ property int locked: 0
+ property string password: ""
+ property bool isLoading: false
+ property real time: 0.0
+ property var tappedFolds: [] // fold IDs in tap order
+
+ implicitHeight: 52
+ implicitWidth: 320
+ opacity: entropy > 0 || isLoading ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 400 } }
+
+ // Canonical fold order + sigils + colors — matches backend
+ readonly property var _folds: [
+ { id: "truth", sigil: "◉", color: "#4ade80" },
+ { id: "power", sigil: "⊗", color: "#f43f5e" },
+ { id: "wisdom", sigil: "⚔", color: "#00B4D8" },
+ { id: "judgment", sigil: "◈", color: "#9B1FA0" },
+ { id: "life", sigil: "☁", color: "#818cf8" },
+ { id: "love", sigil: "⬡", color: "#f59e0b" },
+ { id: "discipline", sigil: "◌", color: "#6366f1" }
+ ]
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 6
+
+ // ── 7 fold sigil dots ─────────────────────────────────────────
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 14
+
+ Repeater {
+ model: root._folds.length
+ delegate: Item {
+ required property int index
+ width: 22; height: 22
+
+ readonly property var _fd: root._folds[index]
+ readonly property bool _tapped: root.tappedFolds.indexOf(_fd.id) >= 0
+ readonly property int _order: root.tappedFolds.indexOf(_fd.id)
+ readonly property color _col: Qt.color(_fd.color)
+
+ // Outer glow ring — visible when tapped
+ Rectangle {
+ anchors.centerIn: parent
+ width: 20; height: 20; radius: 10
+ color: "transparent"
+ border.width: 1
+ border.color: Qt.rgba(_col.r, _col.g, _col.b, _tapped ? 0.45 : 0.0)
+ Behavior on border.color { ColorAnimation { duration: 300 } }
+ }
+
+ // Sigil text
+ Text {
+ anchors.centerIn: parent
+ text: _fd.sigil
+ color: _tapped
+ ? Qt.rgba(_col.r, _col.g, _col.b, 1.0)
+ : Qt.rgba(1, 1, 1, 0.18)
+ font.pixelSize: 13
+ Behavior on color { ColorAnimation { duration: 250 } }
+
+ // Pop-in scale when tapped
+ scale: _tapped ? 1.0 : 0.85
+ Behavior on scale { NumberAnimation { duration: 200; easing.type: Easing.OutBack } }
+
+ layer.enabled: _tapped
+ layer.effect: MultiEffect {
+ shadowEnabled: true; blurEnabled: false
+ shadowColor: Qt.rgba(_col.r, _col.g, _col.b, 0.75)
+ shadowBlur: 0.90
+ shadowVerticalOffset: 0; shadowHorizontalOffset: 0
+ }
+ }
+
+ // Tap order number — tiny, below sigil, fades in when tapped
+ Text {
+ anchors { top: parent.bottom; horizontalCenter: parent.horizontalCenter; topMargin: 1 }
+ text: _order >= 0 ? (_order + 1).toString() : ""
+ color: Qt.rgba(_col.r, _col.g, _col.b, 0.55)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 7
+ font.bold: true
+ opacity: _tapped ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 200 } }
+ }
+ }
+ }
+ }
+
+ // ── Entropy bar ───────────────────────────────────────────────
+ Item {
+ width: 320
+ height: 2
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Rectangle {
+ anchors.fill: parent; radius: 1
+ color: Qt.rgba(TW.TransparentWorld.accentWater.r, TW.TransparentWorld.accentWater.g, TW.TransparentWorld.accentWater.b, 0.10)
+ Rectangle {
+ height: parent.height; radius: 1
+ width: parent.width * root.entropy
+ color: TW.TransparentWorld.accentWater
+ opacity: 0.6 + 0.4 * Math.sin(root.time * 1.8)
+ Behavior on width { NumberAnimation { duration: 80 } }
+ }
+ }
+ }
+
+ // ── Live hash ─────────────────────────────────────────────────
+ InseiLiveHash {
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 320
+ active: root.entropy > 0 && !root.isLoading
+ password: root.password
+ entropy: root.entropy
+ tappedFolds: root.tappedFolds
+ }
+ }
+}
diff --git a/lockscreens/kitsune/content/gotchi/InseiLiveHash.qml b/lockscreens/kitsune/content/gotchi/InseiLiveHash.qml
new file mode 100644
index 00000000..fbda1b50
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/InseiLiveHash.qml
@@ -0,0 +1,221 @@
+/* InseiLiveHash.qml — Living Akashic Fingerprint
+ * ================================================
+ * 32-char stream split into 2 rows of 16.
+ * Characters drawn from: binary, hex, runes, sigils, alchemical glyphs.
+ * Locked chars crystallize in the active fold's color with glow.
+ * Unlocked chars cascade through the full symbol set.
+ * Groups of 4 separated by ·
+ * Loading: all chars breathe in a sine wave.
+ *
+ * §14.8 — Cooperative + FBO, dirty-flag pattern.
+ */
+import QtQuick
+import "../tw" as TW
+
+Item {
+ id: root
+
+ property bool active: false
+ property string password: ""
+ property real entropy: 0.0
+ property var tappedFolds: []
+
+ implicitWidth: 320
+ implicitHeight: 32
+
+ opacity: root.active ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 300 } }
+
+ // ── Symbol pools — the living alphabet ───────────────────────────
+ // Binary fragments
+ readonly property var _binary: ["0","1","0","1","1","0","0","1","1","1","0","0","1","0","1","1"]
+ // Hex
+ readonly property var _hex: ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
+ // Elder Futhark runes
+ readonly property var _runes: ["ᚠ","ᚢ","ᚦ","ᚨ","ᚱ","ᚲ","ᚷ","ᚹ","ᚺ","ᚾ","ᛁ","ᛃ","ᛇ","ᛈ","ᛉ","ᛊ","ᛏ","ᛒ","ᛖ","ᛗ","ᛚ","ᛜ","ᛞ","ᛟ"]
+ // Alchemical / occult sigils
+ readonly property var _sigils: ["◉","⊗","⚔","◈","☁","⬡","◌","⊕","∿","◆","⬟","⊘","⊛","⊜","⊝","⊞"]
+ // Mathematical / esoteric
+ readonly property var _math: ["∞","∂","∇","∆","∑","∏","√","∫","≈","≠","≡","∈","∉","∅","⊂","⊃"]
+ // Braille fragments
+ readonly property var _braille: ["⠁","⠃","⠉","⠙","⠑","⠋","⠛","⠓","⠊","⠚","⠅","⠇","⠍","⠝","⠕","⠏"]
+
+ // Full living alphabet — all pools merged
+ readonly property var _alphabet: _binary.concat(_hex).concat(_runes).concat(_sigils).concat(_math).concat(_braille)
+
+ // Canonical fold colors
+ readonly property var _foldColors: ({
+ "truth": "#4ade80",
+ "power": "#f43f5e",
+ "wisdom": "#00B4D8",
+ "judgment": "#9B1FA0",
+ "life": "#818cf8",
+ "love": "#f59e0b",
+ "discipline": "#6366f1"
+ })
+
+ // Per-position fold color — each locked char uses the color of the fold that locked it
+ readonly property var _charColors: {
+ var colors = []
+ var folds = root.tappedFolds
+ var locked = root._lockedChars
+ // Distribute locked chars across tapped folds proportionally
+ for (var i = 0; i < 32; i++) {
+ if (i < locked && folds.length > 0) {
+ var foldIdx = Math.floor(i / Math.max(1, locked / folds.length))
+ foldIdx = Math.min(foldIdx, folds.length - 1)
+ var hex = _foldColors[folds[foldIdx]]
+ colors.push(hex ? Qt.color(hex) : TW.TransparentWorld.accentWater)
+ } else {
+ colors.push(TW.TransparentWorld.accentWater)
+ }
+ }
+ return colors
+ }
+
+ // ── Scramble state ────────────────────────────────────────────────
+ property var _stream: [] // current 32 chars
+ property bool _dirty: false
+ property real _loadPulse: 0.0
+ property real _glowPulse: 0.0
+
+ readonly property int _lockedChars: Math.round(root.entropy * 32)
+ readonly property var _lockedStream: {
+ if (root.password.length === 0) return []
+ var result = []
+ for (var i = 0; i < 32; i++) {
+ // Deterministic char from password — always the same for same password
+ var code = 0
+ for (var j = 0; j < root.password.length; j++)
+ code = (code * 31 + root.password.charCodeAt(j) + i * 13) & 0xFFFF
+ var pool = i % 4 === 0 ? root._runes
+ : i % 4 === 1 ? root._sigils
+ : i % 4 === 2 ? root._hex
+ : root._math
+ result.push(pool[code % pool.length])
+ }
+ return result
+ }
+
+ // Continuous glow pulse
+ NumberAnimation on _glowPulse {
+ from: 0; to: Math.PI * 2; duration: 2400
+ loops: Animation.Infinite; running: root.active
+ }
+
+ // Load pulse (faster)
+ NumberAnimation on _loadPulse {
+ from: 0; to: Math.PI * 2; duration: 900
+ loops: Animation.Infinite; running: root.active
+ }
+
+ // Living scramble — cycles through all symbol pools
+ Timer {
+ interval: 60
+ running: root.active
+ repeat: true
+ onTriggered: {
+ var locked = root._lockedChars
+ var s = root._stream.slice()
+ while (s.length < 32) s.push("0")
+
+ for (var i = 0; i < 32; i++) {
+ if (i < locked) {
+ // Locked — use deterministic char from password
+ s[i] = root._lockedStream[i] || "◉"
+ } else {
+ // Living — cycle through full alphabet with time offset
+ var t = Date.now() / 80 + i * 7.3
+ var idx = Math.floor(t) % root._alphabet.length
+ s[i] = root._alphabet[idx]
+ }
+ }
+ root._stream = s
+ root._dirty = true
+ }
+ }
+
+ // Repaint throttle
+ Timer {
+ interval: 33
+ running: root.active
+ repeat: true
+ onTriggered: {
+ if (root._dirty) { root._dirty = false; _canvas.requestPaint() }
+ }
+ }
+
+ on_LockedCharsChanged: { root._dirty = true }
+ onActiveChanged: { root._dirty = true }
+ on_GlowPulseChanged: { if (root.active) root._dirty = true }
+
+ // ── Canvas ────────────────────────────────────────────────────────
+ Canvas {
+ id: _canvas
+ anchors.fill: parent
+ renderStrategy: Canvas.Cooperative
+ renderTarget: Canvas.FramebufferObject
+
+ onPaint: {
+ var ctx = getContext("2d")
+ ctx.clearRect(0, 0, width, height)
+
+ var stream = root._stream
+ if (stream.length === 0) return
+
+ var locked = root._lockedChars
+ var aw = TW.TransparentWorld.accentWater
+ var rowH = height / 2
+ var charW = width / 18.5
+ var groupW = charW * 4.6
+
+ ctx.textBaseline = "middle"
+
+ for (var row = 0; row < 2; row++) {
+ var base = row * 16
+ var y = rowH * row + rowH / 2
+
+ for (var col = 0; col < 16; col++) {
+ var i = base + col
+ var ch = i < stream.length ? stream[i] : "·"
+ var grp = Math.floor(col / 4)
+ var x = grp * groupW + (col % 4) * charW + charW * 0.1
+
+ if (i < locked) {
+ // Locked — fold color + glow pulse
+ var fc = root._charColors[i]
+ var glow = 0.75 + 0.20 * Math.sin(root._glowPulse + i * 0.4)
+ ctx.font = "bold 10px '3270 Nerd Font Mono', monospace"
+ ctx.fillStyle = Qt.rgba(fc.r, fc.g, fc.b, glow)
+
+ // Glow halo — draw twice, second pass slightly larger/dimmer
+ ctx.shadowColor = Qt.rgba(fc.r, fc.g, fc.b, 0.55)
+ ctx.shadowBlur = 4
+ ctx.fillText(ch, x, y)
+ ctx.shadowBlur = 0
+ } else if (root.entropy < 0.05) {
+ // Loading — breathe in unison
+ var pulse = 0.10 + 0.10 * Math.sin(root._loadPulse + col * 0.5)
+ ctx.font = "9px '3270 Nerd Font Mono', monospace"
+ ctx.fillStyle = Qt.rgba(aw.r, aw.g, aw.b, pulse)
+ ctx.fillText(ch, x, y)
+ } else {
+ // Living — cascade with position-staggered fade
+ var fade = 0.12 + 0.08 * Math.sin(root._glowPulse * 0.7 + i * 0.9)
+ ctx.font = "9px '3270 Nerd Font Mono', monospace"
+ ctx.fillStyle = Qt.rgba(aw.r, aw.g, aw.b, fade)
+ ctx.fillText(ch, x, y)
+ }
+ }
+
+ // Group separators
+ for (var g = 1; g < 4; g++) {
+ var sx = g * groupW - charW * 0.5
+ ctx.font = "8px '3270 Nerd Font Mono', monospace"
+ ctx.fillStyle = Qt.rgba(aw.r, aw.g, aw.b, 0.10)
+ ctx.fillText("·", sx, y)
+ }
+ }
+ }
+ }
+}
diff --git a/lockscreens/kitsune/content/gotchi/InseiScene.qml b/lockscreens/kitsune/content/gotchi/InseiScene.qml
new file mode 100644
index 00000000..5df00390
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/InseiScene.qml
@@ -0,0 +1,330 @@
+/* InseiScene.qml — The Holographic Ase-Gotchi Scene
+ * ================================================
+ * Layout top → bottom:
+ * Instruction hint — fades after first tap
+ * Card arc — 7 tarot cards fanning upward from Ase's mind
+ * Ase — the dealer, center stage
+ * Whisper — floating lowercase text, no background
+ * InseiHud — entropy bar + lock count + live hash
+ */
+pragma ComponentBehavior: Bound
+import QtQuick
+import QtQuick.Effects
+import "../tw" as TW
+
+Item {
+ id: root
+
+ // ── Public API ────────────────────────────────────────────────────
+ property int lockedCount: 0
+ property real entropy: 0.0
+ property bool hasError: false
+ property bool isUnlocked: false
+ property bool isLoading: false
+ property string whisper: ""
+ property string sequenceStr: ""
+ property var layout: []
+ property var tappedFolds: [] // fold IDs tapped so far — drives correct flip
+ property real time: 0.0
+ property int activeFoldIdx: -1
+ property color activeFoldColor: TW.TransparentWorld.accentWater
+ property real activePulse: 0.0
+ property real breach: 0.0
+ property bool setupMode: false
+ property bool setupPhase2: false
+ property int resetKey: 0 // incremented on reset to force delegate re-eval
+
+ signal cardTapped(string foldId, int position)
+
+ // ── Fold data — canonical, matches backend ────────────────────────
+ readonly property var _foldIds: [
+ "truth","power","wisdom","judgment","life","love","discipline"
+ ]
+ readonly property var _foldLabels: [
+ "THE EYE","THE PHANTOM","THE BLADE","THE SAGE","THE SHROUD","THE FORGE","THE MIRROR"
+ ]
+ readonly property var _foldPrinciples: [
+ "TRUTH","POWER","WISDOM","JUDGMENT","LIFE","LOVE","DISCIPLINE"
+ ]
+ readonly property var _foldColors: [
+ "#4ade80","#f43f5e","#00B4D8","#9B1FA0","#818cf8","#f59e0b","#6366f1"
+ ]
+ readonly property var _foldIcons: [
+ "\u25c9","\u2297","\u2694","\u25c8","\u2601","\u2b21","\u25cc"
+ ]
+
+ // ── Geometry ──────────────────────────────────────────────────────
+ readonly property real _cx: width / 2
+
+ readonly property real _aseW: 320
+ readonly property real _aseH: 260
+ readonly property real _aseY: height * 0.42
+
+ readonly property real _arcCX: _cx
+ readonly property real _arcCY: _aseY - 20
+ readonly property real _arcR: 180
+
+ readonly property real _whisperY: _aseY + _aseH + 10
+ readonly property real _hudY: _whisperY + 44
+
+ // ── Instruction hint — fades after first tap ──────────────────────
+ Text {
+ x: root._cx - implicitWidth / 2
+ y: root._arcCY - root._arcR - 32
+ text: "read the sigils · invoke your sequence"
+ opacity: root.lockedCount === 0 && !root.setupMode ? 0.38 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 600 } }
+ color: Qt.rgba(1, 1, 1, 1)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 10
+ font.letterSpacing: 2
+ }
+
+ // Setup instruction
+ Text {
+ x: root._cx - implicitWidth / 2
+ y: root._arcCY - root._arcR - 32
+ text: root.setupPhase2 ? "draw the same sequence to confirm the binding" : "inscribe your sequence across the seven folds"
+ opacity: root.setupMode ? 0.38 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 600 } }
+ color: Qt.rgba(0.96, 0.74, 0.04, 1)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 10
+ font.letterSpacing: 2
+ }
+
+ // ── Card arc ──────────────────────────────────────────────────────
+ Item {
+ anchors.fill: parent
+
+ Repeater {
+ model: 7
+ delegate: InseiTarot {
+ id: _card
+ required property int index
+
+ readonly property var _fold: {
+ if (root.layout.length === 7) return root.layout[index]
+ return { id: root._foldIds[index], color: root._foldColors[index], position: index + 1 }
+ }
+
+ readonly property int _ci: {
+ var id = _fold.id || ""
+ for (var i = 0; i < root._foldIds.length; i++)
+ if (root._foldIds[i] === id) return i
+ return index
+ }
+
+ // Correct flip: driven by whether this fold ID is in tappedFolds
+ // resetKey dependency forces re-evaluation when cards are reset
+ readonly property bool _isTapped: root.resetKey >= 0 && root.tappedFolds.indexOf(_fold.id || "") >= 0
+ readonly property int _tapOrder: root.tappedFolds.indexOf(_fold.id || "")
+
+ foldLabel: root._foldLabels[_ci]
+ principle: root._foldPrinciples[_ci]
+ icon: root._foldIcons[_ci]
+ accent: Qt.color(root._foldColors[_ci])
+ position: _fold.position || (index + 1)
+ time: root.time
+ _phase: index * 0.9
+
+ isRevealed: _isTapped
+ revealOrder: _tapOrder
+
+ // Convex-up arc: 210° → 330°
+ readonly property real _spreadDeg: 120.0
+ readonly property real _startDeg: 210.0
+ readonly property real _stepDeg: _spreadDeg / 6.0
+ readonly property real _angleDeg: _startDeg + index * _stepDeg
+ readonly property real _angleRad: _angleDeg * Math.PI / 180.0
+
+ x: root._arcCX + root._arcR * Math.cos(_angleRad) - width / 2
+ // y offset from TarotCard's own hover lift — base position here
+ y: root._arcCY + root._arcR * Math.sin(_angleRad) - height / 2
+
+ rotation: _angleDeg - 270.0
+
+ onTapped: root.cardTapped(_card._fold.id || "", _card._fold.position || (_card.index + 1))
+ }
+ }
+ }
+
+ // ── Ase ───────────────────────────────────────────────────────────
+ InseiCompanion {
+ id: _ase
+ x: root._cx - implicitWidth / 2
+ y: root._aseY
+ entropy: root.entropy
+ foldColor: root.activeFoldColor
+ time: root.time
+ }
+
+ // ── Whisper ───────────────────────────────────────────────────────
+ Item {
+ x: root._cx - 200
+ y: root._whisperY
+ width: 400
+ height: 48
+ visible: true
+ opacity: root.whisper.length > 0 ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 300 } }
+
+ Text {
+ anchors.centerIn: parent
+ width: parent.width
+ text: root.whisper.toLowerCase()
+ color: root.hasError ? Qt.rgba(0.96, 0.25, 0.37, 0.90)
+ : root.isUnlocked ? Qt.rgba(0.29, 0.87, 0.50, 0.90)
+ : Qt.rgba(0.0, 0.82, 1.0, 0.78)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 13
+ font.letterSpacing: 1.5
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ Behavior on color { ColorAnimation { duration: 250 } }
+ }
+
+ layer.enabled: root.whisper.length > 0
+ layer.effect: MultiEffect {
+ shadowEnabled: true
+ blurEnabled: false
+ shadowColor: root.hasError
+ ? Qt.rgba(0.96, 0.25, 0.37, 0.45)
+ : Qt.rgba(0.0, 0.82, 1.0, 0.35)
+ shadowBlur: 0.9
+ shadowVerticalOffset: 0
+ shadowHorizontalOffset: 0
+ }
+ }
+
+ // ── Setup / Confirm badge ─────────────────────────────────────────
+ Text {
+ x: root._cx - implicitWidth / 2
+ y: root._whisperY - 20
+ visible: root.setupMode
+ opacity: root.setupMode ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 200 } }
+ text: root.setupPhase2 ? "confirm" : "setup"
+ color: Qt.rgba(0.96, 0.74, 0.04, 0.70)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 9
+ font.letterSpacing: 3
+ }
+
+ // ── InseiHud ────────────────────────────────────────────────────────
+ InseiHud {
+ x: root._cx - width / 2
+ y: root._hudY
+ width: 320
+ entropy: root.entropy
+ locked: root.lockedCount
+ password: root.sequenceStr
+ isLoading: root.isLoading
+ time: root.time
+ tappedFolds: root.tappedFolds
+ }
+
+ // ── CAST button — appears when all 7 cards are drawn ──────────────
+ // §16 — visible: stableGate (readyToCast), opacity handles show/hide
+ signal castRequested()
+
+ property bool readyToCast: false
+
+ // Shatter animation — fires on tap
+ property real _castShatter: 0.0
+ NumberAnimation {
+ id: _shatterAnim
+ target: root; property: "_castShatter"
+ from: 0.0; to: 1.0; duration: 600
+ easing.type: Easing.OutCubic
+ running: false
+ onStopped: root._castShatter = 0.0
+ }
+
+ // Dynamic label based on state
+ readonly property string _castLabel: {
+ if (setupMode && setupPhase2) return "CONFIRM THE BINDING"
+ if (setupMode) return "BIND THE SEQUENCE"
+ return "INVOKE THE SEAL"
+ }
+
+ Item {
+ x: root._cx - 100
+ y: root._aseY - 58
+ width: 200
+ height: 38
+ z: 20
+ visible: root.readyToCast && !root.isUnlocked
+ opacity: root.readyToCast && !root.isUnlocked ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
+
+ // Crystallize shader background
+ ShaderEffect {
+ anchors.fill: parent
+ property real u_time: root.time
+ property real u_confidence: 1.0
+ property real u_progress: root.entropy // fills as cards drawn
+ property real u_shatter: root._castShatter
+ property real u_width: width
+ property real u_height: height
+ property real u_morphR: root.activeFoldColor.r
+ property real u_morphG: root.activeFoldColor.g
+ property real u_morphB: root.activeFoldColor.b
+ fragmentShader: "../shaders/crystallize.frag.qsb"
+ blending: true
+ }
+
+ // Glass base behind shader so button is readable when entropy=0
+ Rectangle {
+ anchors.fill: parent
+ radius: 8
+ color: Qt.rgba(0.02, 0.04, 0.08, 0.85)
+ border.width: 1
+ border.color: Qt.rgba(root.activeFoldColor.r, root.activeFoldColor.g, root.activeFoldColor.b,
+ _castHov.hovered ? 0.90 : 0.50)
+ Behavior on border.color { ColorAnimation { duration: 120 } }
+
+ SequentialAnimation on border.color {
+ loops: Animation.Infinite
+ running: root.readyToCast && !root.isUnlocked && !_castHov.hovered
+ ColorAnimation { to: Qt.rgba(root.activeFoldColor.r, root.activeFoldColor.g, root.activeFoldColor.b, 0.85); duration: 700; easing.type: Easing.InOutSine }
+ ColorAnimation { to: Qt.rgba(root.activeFoldColor.r, root.activeFoldColor.g, root.activeFoldColor.b, 0.28); duration: 700; easing.type: Easing.InOutSine }
+ }
+ }
+
+ // Label — above shader
+ Text {
+ anchors.centerIn: parent
+ z: 2
+ text: root._castLabel
+ color: Qt.rgba(1, 1, 1, _castHov.hovered ? 1.0 : 0.92)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 12
+ font.bold: true
+ font.letterSpacing: 2.5
+ Behavior on color { ColorAnimation { duration: 120 } }
+
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ shadowEnabled: true; blurEnabled: false
+ shadowColor: Qt.rgba(root.activeFoldColor.r, root.activeFoldColor.g, root.activeFoldColor.b, 0.80)
+ shadowBlur: TW.TransparentWorld.shadowBlur
+ shadowVerticalOffset: 0; shadowHorizontalOffset: 0
+ }
+ }
+
+ HoverHandler { id: _castHov; cursorShape: Qt.PointingHandCursor }
+ TapHandler {
+ onTapped: {
+ _shatterAnim.restart()
+ root.castRequested()
+ }
+ }
+ }
+
+ // ── Public methods ────────────────────────────────────────────────
+ function aseWake() { _ase.wake() }
+ function aseReact(col) { _ase.react(col) }
+ function aseHurt() { _ase.hurt() }
+ function aseTransform() { _ase.aseTransform() }
+}
diff --git a/lockscreens/kitsune/content/gotchi/InseiTarot.qml b/lockscreens/kitsune/content/gotchi/InseiTarot.qml
new file mode 100644
index 00000000..255313fd
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/InseiTarot.qml
@@ -0,0 +1,366 @@
+/* InseiTarot.qml — Mystical 7-Fold Card
+ * ==========================================
+ * Holographic card with 3D flip, idle breathe, hover lift, fold-color glow.
+ * Back face: position number + large glowing fold sigil + hover tooltip.
+ * Front face: fold label + principle after reveal.
+ */
+import QtQuick
+import QtQuick.Effects
+import "../tw" as TW
+
+Item {
+ id: root
+
+ property string cardName: "The Oracle"
+ property string principle: "TRUTH"
+ property string foldLabel: "THE EYE"
+ property string icon: "\u25c9"
+ property color accent: "#4ade80"
+ property bool isRevealed: false
+ property int revealOrder: -1
+ property int position: 1 // shuffled position 1–7 (shown on back)
+ property real time: 0.0
+ property real _phase: 0.0 // idle breathe phase offset
+
+ implicitWidth: 70
+ implicitHeight: 110
+
+ signal tapped()
+ signal alreadyDrawn()
+
+ // ── Hover lift ────────────────────────────────────────────────────
+ property real _hoverLift: _hov.hovered && !isRevealed ? -8.0 : 0.0
+ Behavior on _hoverLift { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
+
+ y: _hoverLift
+
+ // ── Flip ──────────────────────────────────────────────────────────
+ property real _flipAngle: isRevealed ? 180.0 : 0.0
+ Behavior on _flipAngle { NumberAnimation { duration: 550; easing.type: Easing.OutBack; easing.overshoot: 0.18 } }
+
+ // ── Idle breathe — unrevealed only ───────────────────────────────
+ readonly property real _breathe: isRevealed ? 0.0
+ : 0.04 * Math.sin(time * 0.9 + _phase)
+
+ // ── Already-drawn flash ───────────────────────────────────────────
+ property real _flashOpacity: 1.0
+ SequentialAnimation {
+ id: _flashAnim
+ NumberAnimation { target: root; property: "_flashOpacity"; to: 0.3; duration: 80 }
+ NumberAnimation { target: root; property: "_flashOpacity"; to: 1.0; duration: 160 }
+ }
+ opacity: _flashOpacity
+
+ // ── Hover tooltip — shown above card when hovered and not revealed ─
+ Item {
+ id: _tooltip
+ // Positioned above the card, centered
+ x: (root.implicitWidth - _tooltipBg.width) / 2
+ y: -_tooltipBg.height - 6
+ width: _tooltipBg.width
+ height: _tooltipBg.height
+ visible: !root.isRevealed
+ opacity: _hov.hovered ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 140; easing.type: Easing.OutCubic } }
+
+ Rectangle {
+ id: _tooltipBg
+ width: _tooltipRow.implicitWidth + 14
+ height: _tooltipRow.implicitHeight + 8
+ radius: 4
+ color: Qt.rgba(0.03, 0.06, 0.10, 0.92)
+ border.color: Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.45)
+ border.width: 1
+
+ Row {
+ id: _tooltipRow
+ anchors.centerIn: parent
+ spacing: 5
+
+ Text {
+ text: root.icon
+ color: root.accent
+ font.pixelSize: 11
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Text {
+ text: root.foldLabel
+ color: Qt.rgba(1, 1, 1, 0.85)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 9
+ font.bold: true
+ font.letterSpacing: 0.8
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Text {
+ text: "·"
+ color: Qt.rgba(1, 1, 1, 0.30)
+ font.pixelSize: 9
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Text {
+ text: root.principle
+ color: root.accent
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 9
+ font.letterSpacing: 0.8
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ // Tooltip glow
+ layer.enabled: _hov.hovered
+ layer.effect: MultiEffect {
+ shadowEnabled: true
+ blurEnabled: false
+ shadowColor: Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.55)
+ shadowBlur: 0.8
+ shadowVerticalOffset: 0
+ shadowHorizontalOffset: 0
+ }
+ }
+ }
+
+ // ── Card container — 3D Y-axis rotation ──────────────────────────
+ Item {
+ id: _container
+ anchors.fill: parent
+
+ transform: Rotation {
+ origin.x: _container.width / 2
+ origin.y: _container.height / 2
+ axis { x: 0; y: 1; z: 0 }
+ angle: root._flipAngle
+ }
+
+ // ── Back face ─────────────────────────────────────────────────
+ Rectangle {
+ id: _back
+ anchors.fill: parent
+ visible: root._flipAngle < 90
+ color: "#080d12"
+ radius: 5
+ border.color: _hov.hovered
+ ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.70)
+ : Qt.rgba(1, 1, 1, 0.10 + root._breathe * 2)
+ border.width: 1
+ Behavior on border.color { ColorAnimation { duration: 120 } }
+
+ // Geometric lattice — tints toward accent on hover
+ Canvas {
+ id: _lattice
+ anchors { fill: parent; margins: 2 }
+ renderStrategy: Canvas.Cooperative
+ renderTarget: Canvas.FramebufferObject
+
+ // Dirty-flag repaint on hover change only
+ property bool _dirty: false
+ onVisibleChanged: _dirty = true
+
+ Connections {
+ target: _hov
+ function onHoveredChanged() { _lattice._dirty = true }
+ }
+
+ Timer {
+ interval: 100
+ running: _lattice.visible
+ repeat: true
+ onTriggered: {
+ if (_lattice._dirty) {
+ _lattice._dirty = false
+ _lattice.requestPaint()
+ }
+ }
+ }
+
+ onPaint: {
+ var ctx = getContext("2d")
+ ctx.clearRect(0, 0, width, height)
+ var alpha = _hov.hovered ? 0.28 : 0.15
+ var r = _hov.hovered ? root.accent.r * 0.4 : 0.15
+ var g = _hov.hovered ? root.accent.g * 0.4 : 0.35
+ var b = _hov.hovered ? root.accent.b * 0.4 : 0.55
+ ctx.strokeStyle = Qt.rgba(r, g, b, alpha)
+ ctx.lineWidth = 0.8
+ for (var i = 0; i <= width; i += 9) {
+ ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(width - i, height); ctx.stroke()
+ ctx.beginPath(); ctx.moveTo(i, height); ctx.lineTo(width - i, 0); ctx.stroke()
+ }
+ }
+ }
+
+ // Position number — top left
+ Text {
+ anchors { top: parent.top; left: parent.left; margins: 5 }
+ text: root.position.toString()
+ color: _hov.hovered
+ ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.90)
+ : Qt.rgba(1, 1, 1, 0.45 + root._breathe * 2)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 12
+ font.bold: true
+ Behavior on color { ColorAnimation { duration: 120 } }
+ }
+
+ // ── Fold sigil — center, large, glowing ───────────────────
+ Item {
+ id: _sigilContainer
+ anchors.centerIn: parent
+ width: 42
+ height: 42
+
+ Text {
+ id: _sigil
+ anchors.centerIn: parent
+ text: root.icon
+ color: Qt.rgba(
+ root.accent.r,
+ root.accent.g,
+ root.accent.b,
+ 0.75 + root._breathe * 3 + (_hov.hovered ? 0.20 : 0.0)
+ )
+ font.pixelSize: 32
+ Behavior on color { ColorAnimation { duration: 120 } }
+ }
+
+ // Sigil bloom — dedicated glow layer on the sigil itself
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ shadowEnabled: true
+ blurEnabled: false
+ shadowColor: Qt.rgba(
+ root.accent.r,
+ root.accent.g,
+ root.accent.b,
+ _hov.hovered
+ ? 0.90 + root._breathe * 2
+ : 0.55 + root._breathe * 4
+ )
+ shadowBlur: 0.95
+ shadowVerticalOffset: 0
+ shadowHorizontalOffset: 0
+ }
+ }
+
+ // Position number — bottom right (mirror)
+ Text {
+ anchors { bottom: parent.bottom; right: parent.right; margins: 5 }
+ text: root.position.toString()
+ color: _hov.hovered
+ ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.90)
+ : Qt.rgba(1, 1, 1, 0.45 + root._breathe * 2)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 12
+ font.bold: true
+ Behavior on color { ColorAnimation { duration: 120 } }
+ }
+
+ // Card outer glow — hover intensifies in accent color
+ layer.enabled: !root.isRevealed
+ layer.effect: MultiEffect {
+ shadowEnabled: true
+ blurEnabled: false
+ shadowColor: _hov.hovered
+ ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.60)
+ : Qt.rgba(0, 0.7, 1.0, 0.14 + root._breathe * 3)
+ shadowBlur: 0.7
+ shadowVerticalOffset: 0
+ shadowHorizontalOffset: 0
+ }
+ }
+
+ // ── Front face ────────────────────────────────────────────────
+ Rectangle {
+ id: _front
+ anchors.fill: parent
+ visible: root._flipAngle >= 90
+ color: "#0b1018"
+ radius: 5
+ border.color: Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.55)
+ border.width: 1
+
+ transform: Rotation {
+ origin.x: _front.width / 2
+ origin.y: _front.height / 2
+ axis { x: 0; y: 1; z: 0 }
+ angle: 180
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 5
+ color: Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.06)
+ }
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 6
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: root.icon
+ color: root.accent
+ font.pixelSize: 24
+ }
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: root.foldLabel
+ color: Qt.rgba(1, 1, 1, 0.90)
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 7
+ font.bold: true
+ font.letterSpacing: 0.5
+ }
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: root.principle
+ color: root.accent
+ font.family: TW.TransparentWorld.fontFamilyMono
+ font.pixelSize: 7
+ font.letterSpacing: 1
+ }
+ }
+
+ // Reveal order badge
+ Rectangle {
+ anchors { top: parent.top; right: parent.right; margins: 4 }
+ width: 14; height: 14; radius: 7
+ color: root.accent
+ visible: root.revealOrder >= 0
+ Text {
+ anchors.centerIn: parent
+ text: (root.revealOrder + 1).toString()
+ color: "#000000"
+ font.pixelSize: 9
+ font.bold: true
+ }
+ }
+
+ layer.enabled: root.isRevealed
+ layer.effect: MultiEffect {
+ shadowEnabled: true
+ blurEnabled: false
+ shadowColor: Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.50)
+ shadowBlur: 0.85
+ shadowVerticalOffset: 0
+ shadowHorizontalOffset: 0
+ }
+ }
+ }
+
+ TapHandler {
+ onTapped: root.tapped()
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ HoverHandler {
+ id: _hov
+ cursorShape: Qt.PointingHandCursor
+ }
+}
diff --git a/lockscreens/kitsune/content/gotchi/README.md b/lockscreens/kitsune/content/gotchi/README.md
new file mode 100644
index 00000000..72f18439
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/README.md
@@ -0,0 +1,122 @@
+# Ase-Gotchi · Digital Kitsune Companion & Tarot Gate
+
+Ase is a 7-tailed digital fox spirit — the soul of the Shigurui ecosystem.
+She manifests as a bioluminescent SDF shader form and guards the Akashic Gate
+through a 7-fold Tarot authentication ritual.
+
+---
+
+## The Ritual (Authentication)
+
+Authentication is a **7-fold Tarot reveal**. The operator draws 7 cards in
+their personal sequence to establish resonance with the gate.
+
+Each session the gate shuffles the 7 cards into random spatial positions.
+The operator must remember their sequence by **principle**, not by position —
+the cards move, the sequence stays the same.
+
+### The 7 Folds
+
+| Position key | Fold ID | Kasugai label | Principle | Sigil | Color |
+|:---:|:-------------|:---------------|:------------|:-----:|:----------|
+| 1–7 (shuffled) | `truth` | THE EYE | TRUTH | ◉ | `#4ade80` |
+| 1–7 (shuffled) | `power` | THE PHANTOM | POWER | ⊗ | `#f43f5e` |
+| 1–7 (shuffled) | `wisdom` | THE BLADE | WISDOM | ⚔ | `#00B4D8` |
+| 1–7 (shuffled) | `judgment` | THE SAGE | JUDGMENT | ◈ | `#9B1FA0` |
+| 1–7 (shuffled) | `life` | THE SHROUD | LIFE | ☁ | `#818cf8` |
+| 1–7 (shuffled) | `love` | THE FORGE | LOVE | ⬡ | `#f59e0b` |
+| 1–7 (shuffled) | `discipline` | THE MIRROR | DISCIPLINE | ◌ | `#6366f1` |
+
+### Interaction
+
+- **Tap a card** to reveal it and add it to your sequence
+- **Keyboard 1–7** maps to the shuffled spatial positions shown on each card back
+- Each card back shows its **position number** (top-left + bottom-right) and its **fold sigil** (center)
+- Cards flip to reveal their fold label and principle when drawn
+- A reveal order badge shows the draw sequence on the front face
+- **Escape** — resets the current sequence (stay at gate). Escape again to close entirely
+- **Master override** — type `1234567` on the keyboard to bypass (development only)
+
+### First-time Setup
+
+If no sequence is registered, the gate enters **setup mode**:
+1. Draw all 7 cards in your desired sequence
+2. Re-draw the same sequence to confirm
+3. The gate seals your sequence as an HMAC hash — the raw sequence is never stored
+
+### Security Model
+
+- Sequence stored as `HMAC-SHA256(fold_ids_joined, local_secret)`
+- Challenge token is `HMAC-SHA256(30s time window, local_secret)` — TOTP-style
+- 3 failed attempts → 30s lockout
+- All state is local — no network, no DB, no external service
+- Master override reads from `SHIGURUI_MASTER_PASSWORD` in `core/.env`
+
+---
+
+## Ase's Emotional States
+
+Ase's shader responds to her soul state (`ArOrchestrator.aseMood`):
+
+| Mood | Visual |
+|:--------------|:----------------------------------------------------|
+| `curiosity` | Default — calm drift, inquisitive aura |
+| `recognition` | Joy flash — bright bioluminescent surge |
+| `excitement` | Chaotic tail movement, high entropy glow |
+| `loneliness` | Dimmed colors, slowed drift |
+| `sleep` | Deep rhythmic pulse, eye dims |
+
+Each of her 7 tails is permanently tinted with its fold color — the colors
+are baked into the shader as static uniforms and never change.
+
+---
+
+## Components
+
+```
+ase-gotchi/
+ InseiGotchi.qml — Orchestrator. Auth state machine, API calls, input handling.
+ InseiScene.qml — Scene layout. Cards, Ase, whisper, HUD.
+ InseiCompanion.qml — The Kitsune spirit. SDF shader, mood, animations.
+ AseTank.qml — Optional: AkashicScreen void tank for Ase to inhabit.
+ TarotCard.qml — 7-fold card. 3D flip, hover lift, position + sigil on back.
+ InseiHud.qml — Entropy bar + lock count + live hash strip.
+ InseiLiveHash.qml — 32-char hex fingerprint that locks in as cards are drawn.
+ qmldir — Module declarations.
+
+shaders/
+ InseiKitsune.frag — SDF kitsune shader. 7 tails, per-tail fold colors, mood uniforms.
+ AkashicScreen.frag — Living void background (nebula, particle streaks, scan lines).
+```
+
+---
+
+## Backend API
+
+| Endpoint | Method | Purpose |
+|:---------|:------:|:--------|
+| `/api/auth/gate/challenge` | GET | Returns shuffled layout + challenge token |
+| `/api/auth/gate/verify` | POST | Verifies fold sequence against stored hash |
+| `/api/auth/gate/setup` | POST | One-time sequence registration |
+| `/api/auth/gate/status` | GET | Is a sequence configured? |
+| `/api/ase/status` | GET | Ase soul state (health, bonding, mood, level) |
+| `/api/ase/interact` | POST | Stroke/feed/play interactions |
+
+Soul data persists in `core/data/ase-gotchi/ase_soul.json`.
+`StatusTimer.qml` polls `/api/ase/status` every 30s to update `ArOrchestrator`.
+
+---
+
+## Audit Checklist
+
+Before any session close, verify:
+1. Gate opens when bar is clicked without a session
+2. Cards shuffle to new positions on each open
+3. Tapping a card flips the correct card (not left-to-right order)
+4. Keyboard 1–7 maps to the position shown on each card back
+5. Already-drawn card shows whisper feedback, not silent ignore
+6. Setup mode: phase 2 resets all card flips for re-draw
+7. Escape resets sequence; second escape closes gate
+8. Master override `1234567` authenticates successfully
+9. 3 wrong sequences triggers 30s lockdown
+10. Successful auth emits `authenticated(token)` → `ArOrchestrator.sessionToken`
diff --git a/lockscreens/kitsune/content/gotchi/qmldir b/lockscreens/kitsune/content/gotchi/qmldir
new file mode 100644
index 00000000..d4cbc2ea
--- /dev/null
+++ b/lockscreens/kitsune/content/gotchi/qmldir
@@ -0,0 +1,8 @@
+module aseGotchi
+InseiGotchi 1.0 InseiGotchi.qml
+InseiScene 1.0 InseiScene.qml
+InseiCompanion 1.0 InseiCompanion.qml
+InseiHud 1.0 InseiHud.qml
+InseiLiveHash 1.0 InseiLiveHash.qml
+InseiTarot 1.0 InseiTarot.qml
+InseiHub 1.0 InseiHub.qml
diff --git a/lockscreens/kitsune/content/metadata.desktop b/lockscreens/kitsune/content/metadata.desktop
new file mode 100644
index 00000000..6668066b
--- /dev/null
+++ b/lockscreens/kitsune/content/metadata.desktop
@@ -0,0 +1,7 @@
+[SddmGreeterTheme]
+Name=Kitsune
+Description=Ase, the seven-tailed kitsune, and her seven tarot folds — the Jurai lock, at the outer door
+Type=sddm-theme
+MainScript=Main.qml
+ConfigFile=theme.conf
+QtVersion=6
diff --git a/lockscreens/kitsune/content/shaders/InseiKitsune.frag b/lockscreens/kitsune/content/shaders/InseiKitsune.frag
new file mode 100644
index 00000000..3ef2c565
--- /dev/null
+++ b/lockscreens/kitsune/content/shaders/InseiKitsune.frag
@@ -0,0 +1,196 @@
+#version 440
+// AseKitsune.frag — Digital Kitsune Companion (v2)
+//
+// Ase manifested as a 7-tailed fox spirit.
+// Each tail tip is tinted with its corresponding fold color:
+// tail 0 → truth #4ade80 (ghost green)
+// tail 1 → power #f43f5e (crimson)
+// tail 2 → wisdom #00B4D8 (water blue)
+// tail 3 → judgment #9B1FA0 (violet)
+// tail 4 → life #818cf8 (indigo)
+// tail 5 → love #f59e0b (amber)
+// tail 6 → discipline #6366f1 (purple)
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 qt_Matrix;
+ float qt_Opacity;
+ float time;
+ float entropy;
+ float mood_joy;
+ float mood_sad;
+ float mood_sleep;
+ float fold_r; float fold_g; float fold_b;
+ float w; float h;
+ float tail0_r; float tail0_g; float tail0_b;
+ float tail1_r; float tail1_g; float tail1_b;
+ float tail2_r; float tail2_g; float tail2_b;
+ float tail3_r; float tail3_g; float tail3_b;
+ float tail4_r; float tail4_g; float tail4_b;
+ float tail5_r; float tail5_g; float tail5_b;
+ float tail6_r; float tail6_g; float tail6_b;
+ float tailCount;
+} ubuf;
+
+float hash21(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
+
+float noise(vec2 p) {
+ vec2 i = floor(p); vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ return mix(mix(hash21(i), hash21(i + vec2(1,0)), u.x),
+ mix(hash21(i + vec2(0,1)), hash21(i + vec2(1,1)), u.x), u.y);
+}
+
+mat2 rot(float a) { float s = sin(a), c = cos(a); return mat2(c,-s,s,c); }
+
+float sdCapsule(vec2 p, vec2 a, vec2 b, float r) {
+ vec2 pa = p-a, ba = b-a;
+ float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
+ return length(pa - ba*h) - r;
+}
+
+float sdEquilateralTriangle(vec2 p, float r) {
+ const float k = sqrt(3.0);
+ p.x = abs(p.x) - r;
+ p.y = p.y + r/k;
+ if (p.x + k*p.y > 0.0) p = vec2(p.x - k*p.y, -k*p.x - p.y) / 2.0;
+ p.x -= clamp(p.x, -2.0*r, 0.0);
+ return -length(p) * sign(p.y);
+}
+
+float opSmoothUnion(float d1, float d2, float k) {
+ float h = clamp(0.5 + 0.5*(d2-d1)/k, 0.0, 1.0);
+ return mix(d2, d1, h) - k*h*(1.0-h);
+}
+
+vec3 tailColor(int i) {
+ if (i == 0) return vec3(ubuf.tail0_r, ubuf.tail0_g, ubuf.tail0_b);
+ if (i == 1) return vec3(ubuf.tail1_r, ubuf.tail1_g, ubuf.tail1_b);
+ if (i == 2) return vec3(ubuf.tail2_r, ubuf.tail2_g, ubuf.tail2_b);
+ if (i == 3) return vec3(ubuf.tail3_r, ubuf.tail3_g, ubuf.tail3_b);
+ if (i == 4) return vec3(ubuf.tail4_r, ubuf.tail4_g, ubuf.tail4_b);
+ if (i == 5) return vec3(ubuf.tail5_r, ubuf.tail5_g, ubuf.tail5_b);
+ return vec3(ubuf.tail6_r, ubuf.tail6_g, ubuf.tail6_b);
+}
+
+void main() {
+ vec2 uv = qt_TexCoord0;
+ float asp = ubuf.w / ubuf.h;
+ // Flip Y so positive Y = up (head above body, tails below)
+ vec2 p = (uv - 0.5) * vec2(asp, -1.0);
+ float t = ubuf.time;
+
+ p.y += sin(t * 2.0) * 0.02 * (1.0 - ubuf.mood_sleep);
+ p.y += sin(t * 0.5) * 0.03;
+ p.x += sin(t * 0.8) * 0.05;
+
+ // Body sits below center — capsule from -0.1 to +0.1 in body-local space
+ // Body top in p-space: p.y - 0.05 + 0.1 = p.y + 0.05
+ vec2 bodyPos = p + vec2(0.0, -0.05);
+ float body = sdCapsule(bodyPos, vec2(0.0,-0.1), vec2(0.0,0.1), 0.08);
+
+ // Head at the top end of the body — triangle flipped to point upward
+ vec2 headPos = p + vec2(0.0, -0.23);
+ headPos.y *= 1.2;
+ vec2 headFlipped = vec2(headPos.x, -headPos.y); // flip Y to point triangle upward
+ float head = sdEquilateralTriangle(headFlipped, 0.08);
+
+ // Ears point upward (+Y) from the top of the head
+ vec2 earL = headPos - vec2(-0.05, 0.06); earL *= rot( 0.4);
+ vec2 earR = headPos - vec2( 0.05, 0.06); earR *= rot(-0.4);
+
+ float kitsune = opSmoothUnion(body, head, 0.05);
+ vec2 earLf = vec2(earL.x, -earL.y);
+ vec2 earRf = vec2(earR.x, -earR.y);
+ kitsune = opSmoothUnion(kitsune, sdEquilateralTriangle(earLf, 0.04), 0.02);
+ kitsune = opSmoothUnion(kitsune, sdEquilateralTriangle(earRf, 0.04), 0.02);
+
+ float tails = 100.0;
+ vec3 tailColorAccum = vec3(0.0);
+ float tailWeightSum = 0.0;
+ // Guard: always 7 until companion system is active
+ int maxTails = 7;
+
+ for (int i = 0; i < 9; i++) {
+ if (i >= maxTails) break;
+
+ float fi = float(i);
+ float angle = (fi / max(float(maxTails - 1), 1.0) - 0.5) * 3.2;
+ float sway = sin(t * 1.5 + fi * 0.7) * 0.12;
+ // tailP originates from the body's bottom tip
+ vec2 tailOrigin = p + vec2(0.0, 0.08);
+ vec2 tailP = tailOrigin * rot(angle + sway);
+ tailP.x += sin(tailP.y * 8.0 - t * 4.0) * 0.04 * (tailP.y + 0.1);
+
+ // Capsule grows in -Y direction (downward away from body)
+ float tailSDF = sdCapsule(tailP, vec2(0.0, 0.0), vec2(0.0, -0.38),
+ 0.028 * (1.1 + tailP.y * 2.2));
+ // tipT: 1.0 at the tip (most negative Y), 0.0 at root
+ float tipT = clamp((-tailP.y + 0.05) / 0.38, 0.0, 1.0);
+ float proximity = exp(-max(tailSDF, 0.0) * 40.0);
+ tailColorAccum += tailColor(i) * tipT * proximity;
+ tailWeightSum += proximity;
+ tails = opSmoothUnion(tails, tailSDF, 0.015);
+ }
+
+ // ── Layered compositing: tails → body → head (front to back = head on top) ──
+
+ vec3 accentColor = vec3(ubuf.fold_r, ubuf.fold_g, ubuf.fold_b);
+ float furNoise = noise(p * 8.0 + t * 0.5);
+ vec3 furBase = mix(vec3(0.04, 0.12, 0.20), accentColor, furNoise * 0.5 + ubuf.mood_joy * 0.5);
+ furBase = mix(furBase, vec3(0.06, 0.06, 0.08), ubuf.mood_sleep * 0.7);
+
+ vec3 resolvedTailColor = tailWeightSum > 0.001
+ ? tailColorAccum / tailWeightSum : accentColor;
+
+ // Layer 0 — tails (deepest, behind everything)
+ float tailMask = smoothstep(0.01, -0.01, tails);
+ float tipInfluence = clamp(tailWeightSum * 1.4, 0.0, 1.0) * tailMask;
+ vec3 tailFur = mix(furBase, resolvedTailColor, tipInfluence * 0.85);
+ float tailGlow = exp(-tails * 8.0) * tipInfluence;
+ vec3 tailLayer = tailFur * tailMask + resolvedTailColor * tailGlow * 2.0;
+
+ // Layer 1 — body (covers tail roots)
+ float bodyMask = smoothstep(0.01, -0.01, body);
+ float bodyGlow = exp(-body * 15.0);
+ vec3 bodyLayer = furBase * bodyMask + accentColor * bodyGlow * 0.8;
+
+ // Layer 2 — head (on top of body)
+ float headMask = smoothstep(0.01, -0.01, head);
+ // Slightly lighter fur on the face
+ vec3 faceFur = mix(furBase, vec3(0.08, 0.18, 0.28), 0.3 + furNoise * 0.2);
+ float headGlow = exp(-head * 15.0);
+ vec3 headLayer = faceFur * headMask + accentColor * headGlow * 0.6;
+
+ // Layer 3 — ears (on top of head)
+ float earLMask = smoothstep(0.01, -0.01, sdEquilateralTriangle(vec2(earL.x, -earL.y), 0.04));
+ float earRMask = smoothstep(0.01, -0.01, sdEquilateralTriangle(vec2(earR.x, -earR.y), 0.04));
+ float earMask = clamp(earLMask + earRMask, 0.0, 1.0);
+ vec3 earLayer = furBase * earMask;
+
+ // Composite back-to-front: each layer paints over the previous using its mask
+ vec3 finalColor = vec3(0.0);
+ finalColor = mix(finalColor, tailLayer, tailMask);
+ finalColor = mix(finalColor, bodyLayer, bodyMask);
+ finalColor = mix(finalColor, headLayer, headMask);
+ finalColor = mix(finalColor, earLayer, earMask);
+
+ // Outer glow around the whole form
+ float kitsuneFull = opSmoothUnion(kitsune, tails, 0.03);
+ float outerGlow = exp(-kitsuneFull * 5.0) * 0.3;
+ finalColor += accentColor * outerGlow * 0.5;
+
+ // Eye — glowing white dot, drawn last so it's always on top
+ float eye = length(headPos - vec2(0.0, 0.0)) - 0.015 * (1.0 - ubuf.mood_sleep * 0.9);
+ finalColor = mix(finalColor, vec3(1.0),
+ smoothstep(0.01, 0.0, eye) * (0.8 + 0.2 * sin(t * 10.0)));
+
+ // Alpha: union of all layers
+ float totalSDF = opSmoothUnion(kitsuneFull, tails, 0.03);
+ float bodyAlpha = smoothstep(0.012, -0.008, totalSDF);
+ float haloAlpha = exp(-max(totalSDF, 0.0) * 22.0) * 0.9;
+ float alpha = clamp(bodyAlpha + haloAlpha, 0.0, 1.0) * ubuf.qt_Opacity;
+ fragColor = vec4(finalColor * alpha, alpha);
+}
diff --git a/lockscreens/kitsune/content/shaders/InseiKitsune.frag.qsb b/lockscreens/kitsune/content/shaders/InseiKitsune.frag.qsb
new file mode 100644
index 00000000..5b5e8c18
Binary files /dev/null and b/lockscreens/kitsune/content/shaders/InseiKitsune.frag.qsb differ
diff --git a/lockscreens/kitsune/content/shaders/InseiSoul.frag b/lockscreens/kitsune/content/shaders/InseiSoul.frag
new file mode 100644
index 00000000..b00fc6b2
--- /dev/null
+++ b/lockscreens/kitsune/content/shaders/InseiSoul.frag
@@ -0,0 +1,299 @@
+#version 440
+// ShiguruiSoul.frag — Neural Core + 7-Fold Swarm
+// Extracted from SanctumCorridors.frag.
+//
+// Inspirations:
+// Neural Synapse (VoXelo) — action potential wave, soma biology
+// Drifting 3D Spell (SourceAura) — iridescent film, Fresnel, crackle
+//
+// Layers (bottom → top):
+// 1. Shigurui Core — organic simplex sphere, iridescent film, Fresnel rim,
+// geodesic cage, singularity void
+// 2. 7-Fold Swarm — one packet per fold, canonical hues, orbit collapses
+// on inferring/commandPulse
+// 3. Action Potential Ring — expands outward on commandPulse, rainbow sweep
+// 4. Surface Crackle — brief electric tendrils on commandPulse spike
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 qt_Matrix;
+ float qt_Opacity;
+ float time;
+ float tpsBeat;
+ float inferring;
+ float disciplineHue;
+ float commandPulse;
+ float obeliskOpen;
+ float cursorX;
+ float cursorY;
+ float w;
+ float h;
+ float mistDensity;
+ float atmospherePhase;
+ float receivePulse; // 0→1: inward ring — query received, Ase listening
+} ubuf;
+
+// ── Utilities ─────────────────────────────────────────────────────────
+float hash11(float n) { return fract(sin(n) * 43758.5453); }
+float hash21(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
+
+float noise(vec2 p) {
+ vec2 i = floor(p); vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ return mix(mix(hash21(i), hash21(i + vec2(1,0)), u.x),
+ mix(hash21(i + vec2(0,1)), hash21(i + vec2(1,1)), u.x), u.y);
+}
+
+vec3 hsv2rgb(vec3 c) {
+ vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+float hexSDF(vec2 p, float r) {
+ p = abs(p);
+ return max(dot(p, normalize(vec2(1.0, 1.732))), p.x) - r;
+}
+
+// ── Simplex 3D ────────────────────────────────────────────────────────
+vec3 mod289v3(vec3 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
+vec4 mod289v4(vec4 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
+vec4 permute(vec4 x) { return mod289v4(((x * 34.0) + 1.0) * x); }
+vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
+
+float snoise3(vec3 v) {
+ const vec2 C = vec2(1.0/6.0, 1.0/3.0);
+ const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
+ vec3 i = floor(v + dot(v, C.yyy));
+ vec3 x0 = v - i + dot(i, C.xxx);
+ vec3 g = step(x0.yzx, x0.xyz);
+ vec3 l = 1.0 - g;
+ vec3 i1 = min(g.xyz, l.zxy);
+ vec3 i2 = max(g.xyz, l.zxy);
+ vec3 x1 = x0 - i1 + C.xxx;
+ vec3 x2 = x0 - i2 + C.yyy;
+ vec3 x3 = x0 - D.yyy;
+ i = mod289v3(i);
+ vec4 p = permute(permute(permute(
+ i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ + i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ + i.x + vec4(0.0, i1.x, i2.x, 1.0));
+ float n_ = 0.142857142857;
+ vec3 ns = n_ * D.wyz - D.xzx;
+ vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
+ vec4 x_ = floor(j * ns.z);
+ vec4 y_ = floor(j - 7.0 * x_);
+ vec4 x2_ = x_ * ns.x + ns.yyyy;
+ vec4 y2_ = y_ * ns.x + ns.yyyy;
+ vec4 h = 1.0 - abs(x2_) - abs(y2_);
+ vec4 b0 = vec4(x2_.xy, y2_.xy);
+ vec4 b1 = vec4(x2_.zw, y2_.zw);
+ vec4 s0 = floor(b0) * 2.0 + 1.0;
+ vec4 s1 = floor(b1) * 2.0 + 1.0;
+ vec4 sh = -step(h, vec4(0.0));
+ vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
+ vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
+ vec3 p0 = vec3(a0.xy, h.x);
+ vec3 p1 = vec3(a0.zw, h.y);
+ vec3 p2 = vec3(a1.xy, h.z);
+ vec3 p3 = vec3(a1.zw, h.w);
+ vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
+ p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
+ vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
+ m = m * m;
+ return 42.0 * dot(m * m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
+}
+
+// ── Rainbow palette (Neural Synapse style) ────────────────────────────
+vec3 palette(float t) {
+ return vec3(0.5) + vec3(0.5) * cos(6.28318 * (vec3(1.0) * t + vec3(0.0, 0.33, 0.67)));
+}
+
+// ── Shigurui Core ─────────────────────────────────────────────────────
+float shiguruiCore(vec2 p, float t, float metabolism,
+ out vec3 rimColor, out float iridescence) {
+ float r = length(p);
+ float a = atan(p.y, p.x);
+
+ vec3 sph = vec3(sin(r * 3.14159) * cos(a),
+ sin(r * 3.14159) * sin(a),
+ cos(r * 3.14159));
+
+ float n1 = snoise3(sph * 2.5 + vec3(t * 0.3 * metabolism));
+ float n2 = snoise3(sph * 5.0 - vec3(t * 0.5)) * 0.5;
+ float nd = n1 * 0.30 + n2 * 0.15;
+
+ float boundary = 0.11 + nd * 0.04;
+ float core = smoothstep(boundary, boundary * 0.6, r);
+
+ float fresnel = pow(1.0 - smoothstep(0.0, boundary * 1.4, r), 2.5);
+ float rim = smoothstep(boundary * 1.3, boundary * 0.85, r)
+ * smoothstep(boundary * 0.5, boundary, r)
+ * (0.7 + fresnel * 0.8);
+
+ // Iridescent film — R/G/B at different wavelengths
+ float filmThickness = 280.0 + nd * 420.0 + noise(p * 4.0 + vec2(t * 0.05)) * 180.0;
+ vec3 film = vec3(
+ sin(filmThickness / 700.0 * 18.0 + t * 0.5) * 0.5 + 0.5,
+ sin(filmThickness / 530.0 * 18.0 + t * 0.6) * 0.5 + 0.5,
+ sin(filmThickness / 440.0 * 18.0 + t * 0.7) * 0.5 + 0.5
+ );
+ iridescence = rim * (0.4 + metabolism * 0.3);
+ rimColor = film;
+
+ // Singularity void — deeper than before
+ float singR = 0.020;
+ float singVoid = smoothstep(singR, singR * 0.3, r);
+ float singAngle = a - t * 0.8;
+ float singHex = smoothstep(0.0, -0.5,
+ hexSDF((p / max(singR * 1.6, 0.001)) * 0.6, 0.55))
+ * smoothstep(singR * 2.2, singR * 1.4, r) * 0.6;
+
+ float flare = (ubuf.commandPulse + ubuf.inferring * ubuf.tpsBeat) * 0.4;
+
+ return core + rim * 0.7 - singVoid * 0.7 + singHex + flare * core;
+}
+
+// ── 7-Fold Swarm ──────────────────────────────────────────────────────
+vec3 foldSwarm(vec2 p, float t, int foldIdx, float metabolism, float pull,
+ out float intensity) {
+ float fi = float(foldIdx);
+ float seed = hash11(fi * 17.3 + 3.3);
+ float speed = (0.28 + seed * 0.32) * metabolism;
+ float angle = t * speed + fi * (6.28318 / 7.0);
+
+ float orbitMax = 0.16 + seed * 0.10;
+ float orbitMin = 0.025;
+ float orbitR = mix(orbitMax, orbitMin, pull);
+
+ vec2 pos = vec2(cos(angle) * orbitR, sin(angle + t * seed * 0.3) * orbitR * 0.7);
+
+ // Emission trail — ghost at previous position
+ float trailAngle = angle - 0.18 * speed;
+ vec2 trailPos = vec2(cos(trailAngle) * orbitR, sin(trailAngle + t * seed * 0.3) * orbitR * 0.7);
+
+ float d = length(p - pos);
+ float dTrail = length(p - trailPos);
+ float glow = exp(-d * d / (0.0004 + pull * 0.0008)) * (1.0 + pull * 2.5);
+ float trail = exp(-dTrail * dTrail / 0.0006) * 0.35;
+ float core = smoothstep(0.013, 0.004, abs(p.x - pos.x) + abs(p.y - pos.y));
+ intensity = core * 0.85 + glow * 0.5 + trail * 0.3;
+
+ // Fold hues — canonical TransparentWorld mapping
+ float hues[7];
+ hues[0] = 0.38; // Eye — emerald
+ hues[1] = 0.55; // Blade — cerulean
+ hues[2] = 0.82; // Sage — blood violet
+ hues[3] = 0.68; // Mirror — indigo
+ hues[4] = 0.12; // Forge — amber
+ hues[5] = 0.97; // Phantom — rose
+ hues[6] = 0.70; // Shroud — lavender
+
+ float hue = hues[foldIdx];
+ float foldMatch = 1.0 - abs(hue - ubuf.disciplineHue) * 2.0;
+ float sat = 0.75 + clamp(foldMatch, 0.0, 1.0) * 0.20;
+ float val = 0.80 + clamp(foldMatch, 0.0, 1.0) * 0.20 + pull * 0.30;
+
+ return hsv2rgb(vec3(hue, sat, val));
+}
+
+void main() {
+ vec2 uv = qt_TexCoord0;
+ float asp = ubuf.w / ubuf.h;
+ vec2 p = vec2(uv.x * asp, uv.y);
+ vec2 center = vec2(0.5 * asp, 0.5);
+ vec3 color = vec3(0.0);
+ float breach = 1.0 - ubuf.mistDensity;
+
+ float metabolism = 1.0 + ubuf.inferring * 1.5 + ubuf.tpsBeat * 0.8;
+ float nucleusScale = 1.75 + ubuf.obeliskOpen * 0.38; // doubled from 3.5
+ float pull = clamp(ubuf.inferring * ubuf.tpsBeat + ubuf.commandPulse * 0.6, 0.0, 1.0);
+ vec2 localP = (p - center) * nucleusScale;
+
+ // ── 1. Shigurui Core ─────────────────────────────────────────────
+ vec3 rimCol; float irid;
+ float nucleus = shiguruiCore(localP, ubuf.time, metabolism, rimCol, irid);
+
+ vec3 coreCol = mix(hsv2rgb(vec3(0.55, 0.7, 1.0)),
+ hsv2rgb(vec3(ubuf.disciplineHue, 0.3, 1.0)),
+ ubuf.inferring * 0.8);
+ coreCol = mix(coreCol, hsv2rgb(vec3(0.78, 0.9, 0.9)), breach * 0.5);
+
+ vec3 iridCol = mix(coreCol, rimCol, irid * 0.55);
+
+ color += iridCol * nucleus * (0.9 + ubuf.inferring * 0.3);
+
+ // ── 2. 7-Fold Swarm ───────────────────────────────────────────────
+ for (int fi = 0; fi < 7; fi++) {
+ float pIntensity;
+ vec3 pCol = foldSwarm(localP, ubuf.time, fi, metabolism, pull, pIntensity);
+ pCol = mix(pCol, vec3(1.0), breach * 0.3);
+ color += pCol * pIntensity * 0.45;
+ }
+
+ // ── 3. Action Potential Ring (Neural Synapse) ─────────────────────
+ // Expands outward from core when commandPulse fires.
+ // Rainbow palette sweep — the "firing" moment.
+ if (ubuf.commandPulse > 0.02) {
+ float dist = length(p - center);
+ float maxR = 0.38 * asp;
+ float waveFront = ubuf.commandPulse * maxR;
+ float distFromWave = dist - waveFront;
+
+ float edge = exp(-pow(distFromWave, 2.0) / 0.00012) * ubuf.commandPulse;
+ float residual = smoothstep(waveFront, waveFront - 0.08, dist) * ubuf.commandPulse * 0.25;
+
+ float angle = atan(p.y - center.y, p.x - center.x);
+ vec3 waveCol = palette(angle * 0.15 + dist * 0.8 - ubuf.time * 0.5);
+ waveCol = mix(waveCol, hsv2rgb(vec3(ubuf.disciplineHue, 0.8, 1.0)), 0.45);
+
+ color += waveCol * (edge * 2.0 + residual);
+ }
+
+ // ── 3b. Receive Ring — inward contraction (query received) ────────
+ // Starts at outer edge, contracts toward core as receivePulse 0→1.
+ // Discipline hue — cooler, focused. "The signal arrives."
+ if (ubuf.receivePulse > 0.02) {
+ float dist = length(p - center);
+ float maxR = 0.38 * asp;
+ // Inward: waveFront shrinks from maxR to 0 as receivePulse goes 0→1
+ float waveFront = (1.0 - ubuf.receivePulse) * maxR;
+ float distFromWave = dist - waveFront;
+
+ // Leading edge — slightly wider than outward ring for softer feel
+ float edge = exp(-pow(distFromWave, 2.0) / 0.00018) * ubuf.receivePulse;
+ // Trailing wake — fills inward behind the contracting front
+ float wake = smoothstep(waveFront, waveFront + 0.06, dist) * ubuf.receivePulse * 0.20;
+
+ // Discipline hue with slight iridescent shift
+ float angle = atan(p.y - center.y, p.x - center.x);
+ vec3 receiveCol = hsv2rgb(vec3(ubuf.disciplineHue, 0.90, 1.0));
+ // Subtle angular shimmer — not full rainbow, just a hint
+ receiveCol = mix(receiveCol,
+ hsv2rgb(vec3(mod(ubuf.disciplineHue + 0.15, 1.0), 0.75, 1.0)),
+ abs(sin(angle * 3.0 + ubuf.time * 0.3)) * 0.35);
+
+ color += receiveCol * (edge * 1.8 + wake);
+ }
+
+ // ── 4. Surface Crackle (Spell bubble) ────────────────────────────
+ // Brief electric tendrils on the core surface when commandPulse spikes.
+ if (ubuf.commandPulse > 0.65) {
+ float dist = length(localP);
+ float boundary = 0.12;
+ // Only on the core surface
+ float surfaceMask = exp(-pow(dist - boundary, 2.0) / 0.0008);
+ float crackle = noise(localP * 18.0 + vec2(ubuf.time * 4.0)) * 0.5 + 0.5;
+ crackle = pow(crackle, 6.0); // sharpen to tendrils
+ float intensity = crackle * surfaceMask * (ubuf.commandPulse - 0.65) * 3.0;
+ vec3 crackleCol = mix(vec3(0.8, 0.9, 1.0),
+ hsv2rgb(vec3(ubuf.disciplineHue, 0.6, 1.0)), 0.5);
+ color += crackleCol * intensity;
+ }
+
+ // Alpha — additive over the sanctum background
+ float alpha = length(color) * ubuf.qt_Opacity * 0.92;
+ fragColor = vec4(color, clamp(alpha, 0.0, 1.0));
+}
diff --git a/lockscreens/kitsune/content/shaders/InseiSoul.frag.qsb b/lockscreens/kitsune/content/shaders/InseiSoul.frag.qsb
new file mode 100644
index 00000000..da373193
Binary files /dev/null and b/lockscreens/kitsune/content/shaders/InseiSoul.frag.qsb differ
diff --git a/lockscreens/kitsune/content/shaders/crystallize.frag b/lockscreens/kitsune/content/shaders/crystallize.frag
new file mode 100644
index 00000000..d74447f0
--- /dev/null
+++ b/lockscreens/kitsune/content/shaders/crystallize.frag
@@ -0,0 +1,299 @@
+#version 440
+// crystallize.frag — Akashic Crystal Formation · V2
+// ===================================================
+// True SDF Voronoi fracture with iridescent thin-film facets,
+// caustic light scattering, chromatic aberration at edges,
+// and a domain-warped fBm shatter burst.
+//
+// Techniques:
+// · Voronoi SDF — sharp cell boundaries, not smooth Worley falloff
+// · Hex lattice SDF — facets align to hex geometry
+// · Thin-film iridescence — wavelength-dependent phase per facet
+// · Caustic scattering — bright spots where facets focus light
+// · Chromatic aberration — R/G/B split at crystal edges
+// · fBm domain-warped shatter — membrane tears, not just rings
+// · 6 SDF line-segment shards — true hex symmetry
+//
+// Uniforms:
+// u_time — continuous time
+// u_confidence — 0–1, prediction confidence (drives brightness)
+// u_progress — 0–1, Tab-hold crystallization spread (left→right)
+// u_shatter — 0–1, shatter burst (decays fast after execute)
+// u_morphR/G/B — active mode color
+// u_width/height — viewport size
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 qt_Matrix;
+ float qt_Opacity;
+ float u_time;
+ float u_confidence;
+ float u_progress;
+ float u_shatter;
+ float u_width;
+ float u_height;
+ float u_morphR;
+ float u_morphG;
+ float u_morphB;
+} ubuf;
+
+const float PI = 3.14159265359;
+const float TAU = 6.28318530718;
+
+// ── Hash ──────────────────────────────────────────────────────────────
+vec2 hash2(vec2 p) {
+ p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
+ return fract(sin(p) * 43758.5453);
+}
+float hash1(vec2 p) {
+ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
+}
+float hash1f(float n) { return fract(sin(n) * 43758.5453); }
+
+// ── Value noise ───────────────────────────────────────────────────────
+float vnoise(vec2 p) {
+ vec2 i = floor(p); vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ return mix(mix(hash1(i), hash1(i + vec2(1,0)), u.x),
+ mix(hash1(i + vec2(0,1)), hash1(i + vec2(1,1)), u.x), u.y);
+}
+
+// 2-octave fBm — cheap, used for domain warping
+float fbm2(vec2 p) {
+ return vnoise(p) * 0.6 + vnoise(p * 2.1 + vec2(3.7, 1.9)) * 0.4;
+}
+
+// ── Hex SDF — distance to nearest hex grid edge ───────────────────────
+// Returns 0 at hex center, 1 at hex edge
+float hexGrid(vec2 p, float scale) {
+ p *= scale;
+ p.x *= 1.1547; // 2/sqrt(3)
+ vec2 p1 = mod(p, vec2(2.0, 3.464)) - vec2(1.0, 1.732);
+ vec2 p2 = mod(p - vec2(1.0, 1.732), vec2(2.0, 3.464)) - vec2(1.0, 1.732);
+ vec2 ap1 = abs(p1); vec2 ap2 = abs(p2);
+ float d1 = max(dot(ap1, normalize(vec2(1.0, 1.732))), ap1.x);
+ float d2 = max(dot(ap2, normalize(vec2(1.0, 1.732))), ap2.x);
+ return smoothstep(0.80, 0.97, min(d1, d2));
+}
+
+// ── Voronoi SDF — returns (minDist, cellId, secondMinDist) ───────────
+// Sharp cell boundaries via F2-F1 distance field
+vec3 voronoiSDF(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+
+ float F1 = 8.0, F2 = 8.0;
+ vec2 cellId = vec2(0.0);
+
+ for (int y = -2; y <= 2; y++) {
+ for (int x = -2; x <= 2; x++) {
+ vec2 neighbor = vec2(float(x), float(y));
+ vec2 point = hash2(i + neighbor);
+ // Slow drift — crystal cells breathe
+ point = 0.5 + 0.5 * sin(ubuf.u_time * 0.25 + TAU * point);
+ vec2 diff = neighbor + point - f;
+ float dist = dot(diff, diff); // squared distance
+ if (dist < F1) { F2 = F1; F1 = dist; cellId = i + neighbor; }
+ else if (dist < F2) { F2 = dist; }
+ }
+ }
+ // F2-F1 = distance to cell boundary (sharp edge at 0)
+ return vec3(sqrt(F1), cellId.x + cellId.y * 57.0, sqrt(F2) - sqrt(F1));
+}
+
+// ── Thin-film iridescence — wavelength-dependent interference ─────────
+// Each facet has a slightly different thickness → different color
+// Based on ShiguruiSoul's film model, adapted for crystal facets
+vec3 thinFilm(float thickness, float angle, float t) {
+ // Three wavelengths: R=700nm, G=530nm, B=440nm
+ float phaseR = thickness / 700.0 * 18.0 + angle * 2.1 + t * 0.4;
+ float phaseG = thickness / 530.0 * 18.0 + angle * 2.5 + t * 0.5;
+ float phaseB = thickness / 440.0 * 18.0 + angle * 3.0 + t * 0.6;
+ return vec3(
+ sin(phaseR) * 0.5 + 0.5,
+ sin(phaseG) * 0.5 + 0.5,
+ sin(phaseB) * 0.5 + 0.5
+ );
+}
+
+// ── SDF line segment — for shatter shards ────────────────────────────
+float sdSegment(vec2 p, vec2 a, vec2 b) {
+ vec2 pa = p - a, ba = b - a;
+ float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
+ return length(pa - ba * h);
+}
+
+void main() {
+ vec2 uv = qt_TexCoord0;
+ float asp = ubuf.u_width / ubuf.u_height;
+ vec2 uvA = vec2(uv.x * asp, uv.y);
+
+ vec3 morphCol = vec3(ubuf.u_morphR, ubuf.u_morphG, ubuf.u_morphB);
+ vec3 ghostWhite = vec3(0.878, 0.969, 1.000);
+ vec3 obsidian = vec3(0.039, 0.039, 0.039);
+
+ vec3 col = vec3(0.0);
+ float alpha = 0.0;
+
+ // ── Crystallization spread mask ───────────────────────────────────
+ // Crystals form left→right as progress advances
+ float spreadMask = 1.0 - smoothstep(
+ ubuf.u_progress - 0.20,
+ ubuf.u_progress + 0.20,
+ uv.x
+ );
+ float crystalStrength = spreadMask * ubuf.u_progress;
+
+ if (crystalStrength > 0.005) {
+
+ // ── Voronoi cell structure ────────────────────────────────────
+ // Scale varies with confidence — high confidence = finer crystals
+ float cellScale = 2.8 + ubuf.u_confidence * 1.4;
+ vec3 vor = voronoiSDF(uv * cellScale + ubuf.u_time * 0.04);
+ float F1 = vor.x; // distance to nearest cell center
+ float cellId = vor.y; // unique cell identifier
+ float boundary = vor.z; // F2-F1: distance to cell boundary
+
+ // ── Hex lattice overlay ───────────────────────────────────────
+ float hexEdge = hexGrid(uv, 3.2 + ubuf.u_confidence * 0.8);
+
+ // ── Thin-film iridescence per facet ───────────────────────────
+ // Each cell has a unique thickness based on its id
+ float facetThickness = 180.0 + hash1f(cellId) * 320.0
+ + vnoise(uv * 2.0 + ubuf.u_time * 0.03) * 80.0;
+ float facetAngle = hash1f(cellId * 7.3) * TAU;
+ vec3 filmColor = thinFilm(facetThickness, facetAngle, ubuf.u_time);
+ // Blend film toward morphColor — system-coherent iridescence
+ filmColor = mix(filmColor, morphCol, 0.45);
+
+ // ── Caustic scattering ────────────────────────────────────────
+ // Bright spots where facets focus light — concentrated near cell centers
+ float causticMask = exp(-F1 * F1 * 18.0); // bright at cell center
+ float causticNoise = vnoise(uv * 6.0 + ubuf.u_time * 0.08) * 0.5 + 0.5;
+ float caustic = causticMask * causticNoise * ubuf.u_confidence * 0.6;
+
+ // ── Cell boundary edges — sharp crystal facet lines ───────────
+ // boundary (F2-F1) is 0 at cell edges → sharp bright lines
+ float edgeSharp = exp(-boundary * boundary * 80.0);
+ // Hex lattice reinforces the facet structure
+ float edgeCombined = max(edgeSharp, hexEdge * 0.7);
+
+ // ── Chromatic aberration at crystal edges ─────────────────────
+ // Split R/G/B at slightly different edge distances (like AkashicTear)
+ float edgeR = exp(-(boundary - 0.005) * (boundary - 0.005) * 80.0);
+ float edgeG = exp(-(boundary) * (boundary) * 80.0);
+ float edgeB = exp(-(boundary + 0.005) * (boundary + 0.005) * 80.0);
+ vec3 chromaEdge = vec3(edgeR, edgeG, edgeB) * 0.5;
+
+ // ── Crystal body color ────────────────────────────────────────
+ // Interior: morphColor-tinted obsidian with film iridescence
+ // Edges: bright white + chromatic split
+ vec3 interiorCol = mix(obsidian, morphCol * 0.5, ubuf.u_confidence * 0.4);
+ interiorCol = mix(interiorCol, filmColor, F1 * 0.6 * ubuf.u_confidence);
+
+ vec3 edgeCol = mix(morphCol, ghostWhite, edgeSharp * ubuf.u_confidence * 0.8);
+ edgeCol += chromaEdge * 0.6;
+
+ vec3 crystalCol = mix(interiorCol, edgeCol, edgeCombined);
+ // Caustic bright spots
+ crystalCol += mix(morphCol, ghostWhite, 0.6) * caustic;
+ // Subsurface scatter — morphColor bleeds through depth
+ crystalCol += morphCol * (1.0 - F1) * 0.15 * ubuf.u_confidence;
+
+ // ── Frost breath — volumetric noise fog ahead of front ────────
+ // 2D noise-modulated, not just a 1D Gaussian
+ float frostFront = ubuf.u_progress + 0.12;
+ float frostDist = abs(uv.x - frostFront);
+ float frostNoise = fbm2(uv * 4.0 + ubuf.u_time * 0.15) * 0.5 + 0.5;
+ float frost = exp(-frostDist * frostDist / 0.006)
+ * frostNoise * ubuf.u_progress * 0.5;
+ crystalCol += morphCol * frost * 0.35;
+
+ // ── Alpha ─────────────────────────────────────────────────────
+ float crystalAlpha = (edgeCombined * 0.45
+ + caustic * 0.25
+ + frost * 0.20
+ + (1.0 - F1) * 0.08 * ubuf.u_confidence)
+ * crystalStrength;
+
+ col += crystalCol * crystalStrength;
+ alpha += crystalAlpha;
+ }
+
+ // ── SHATTER BURST — domain-warped fBm membrane tear ──────────────
+ if (ubuf.u_shatter > 0.005) {
+ float s = ubuf.u_shatter;
+ vec2 center = vec2(0.5, 0.5);
+ vec2 p = uv - center;
+ float dist = length(p);
+ float angle = atan(p.y, p.x);
+
+ // Domain warp — fBm tears the membrane (from AkashicTear)
+ vec2 warpQ = vec2(
+ fbm2(p * 3.0 + vec2(ubuf.u_time * 0.2)),
+ fbm2(p * 3.0 + vec2(1.7, 9.2) + ubuf.u_time * 0.15)
+ );
+ vec2 warpedP = p + warpQ * 0.08 * s;
+ float warpedDist = length(warpedP);
+
+ // Primary shockwave — warped ring
+ float waveR = s * 0.52;
+ float wave = exp(-pow(warpedDist - waveR, 2.0) / 0.0025)
+ * s * (1.0 - s) * 2.5;
+ vec3 waveCol = mix(morphCol, ghostWhite, s * 0.6);
+ col += waveCol * wave;
+ alpha += wave * 0.7;
+
+ // ── 6 SDF line-segment shards — true hex symmetry ─────────────
+ // Each shard is a proper SDF line segment, not angle-based Gaussian
+ for (int i = 0; i < 6; i++) {
+ float shardAngle = float(i) * (PI / 3.0); // 60° steps
+ // Slight random offset per shard — crystals don't shatter perfectly
+ shardAngle += hash1f(float(i) * 3.7) * 0.15 - 0.075;
+
+ vec2 shardDir = vec2(cos(shardAngle), sin(shardAngle));
+
+ // Shard extends from near-center to outer radius
+ float innerR = 0.02 + s * 0.04;
+ float outerR = s * 0.65 + hash1f(float(i) * 5.1) * s * 0.15;
+ vec2 shardA = shardDir * innerR;
+ vec2 shardB = shardDir * outerR;
+
+ // SDF distance to this shard line
+ float shardDist = sdSegment(p, shardA, shardB);
+
+ // Shard width tapers from base to tip
+ float t_along = clamp(dot(p - shardA, shardDir) / max(outerR - innerR, 0.001), 0.0, 1.0);
+ float shardWidth = mix(0.008, 0.002, t_along) * (1.0 - s * 0.5);
+
+ float shardGlow = exp(-shardDist * shardDist / (shardWidth * shardWidth));
+ shardGlow *= (1.0 - s) * (1.0 - t_along * 0.6); // fade toward tip and over time
+
+ // Iridescent shard color — each shard gets a film tint
+ float shardFilmT = hash1f(float(i) * 11.3) * 0.5 + s * 0.3;
+ vec3 shardCol = mix(morphCol, ghostWhite, shardFilmT);
+ // Chromatic split along shard length
+ shardCol.r += t_along * 0.15;
+ shardCol.b -= t_along * 0.10;
+
+ col += shardCol * shardGlow * 1.8;
+ alpha += shardGlow * 0.8;
+ }
+
+ // Secondary micro-ring — tighter, morphColor
+ float ring2 = exp(-pow(dist - s * 0.22, 2.0) / 0.0008)
+ * s * (1.0 - s) * 1.2;
+ col += morphCol * ring2;
+ alpha += ring2 * 0.4;
+
+ // Void expansion — center clears as shatter completes
+ float voidExpand = smoothstep(0.0, 0.7, s);
+ float voidFill = smoothstep(waveR * 0.6, waveR * 0.1, dist) * voidExpand;
+ col = mix(col, vec3(0.0), voidFill * 0.85);
+ alpha = mix(alpha, 0.0, voidFill * 0.8);
+ }
+
+ fragColor = vec4(col * alpha, alpha * ubuf.qt_Opacity);
+}
diff --git a/lockscreens/kitsune/content/shaders/crystallize.frag.qsb b/lockscreens/kitsune/content/shaders/crystallize.frag.qsb
new file mode 100644
index 00000000..8e756172
Binary files /dev/null and b/lockscreens/kitsune/content/shaders/crystallize.frag.qsb differ
diff --git a/lockscreens/kitsune/content/theme.conf b/lockscreens/kitsune/content/theme.conf
new file mode 100644
index 00000000..e0bf742c
--- /dev/null
+++ b/lockscreens/kitsune/content/theme.conf
@@ -0,0 +1,7 @@
+[General]
+# Kitsune — Ase, the seven-tailed kitsune, at the door.
+# Her greeter reads exactly one key: the username rule she was
+# built under (the valence theme runs the same). With it false,
+# the name is lowered before it is spoken to PAM, matching
+# kitsune.conf on the machine she first stood on.
+AllowUppercaseLettersInUsernames=false
diff --git a/lockscreens/kitsune/content/tw/ArOrchestrator.qml b/lockscreens/kitsune/content/tw/ArOrchestrator.qml
new file mode 100644
index 00000000..9aaf38a5
--- /dev/null
+++ b/lockscreens/kitsune/content/tw/ArOrchestrator.qml
@@ -0,0 +1,30 @@
+pragma Singleton
+
+import QtQuick
+
+// ArOrchestrator — the resting seat.
+//
+// In Jurai shell this singleton is the AR stage's living state: the
+// daemon's pulse, the scry phases, the folds drawn out on the hub.
+// A lock face has no stage and no daemon, and the scene's hub — the
+// one seat that reads him — is never raised at the door. What stands
+// here is his shape at rest: every property the scene can name,
+// holding the quiet values, so the face stands on its own in any
+// host. Written for the RyoStore port (2026-10-25); the scene files
+// themselves are unmodified.
+QtObject {
+ property real aseWidgetCenterX: 0.5
+ property real aseWidgetCenterY: 0.0
+ property color prismaticColor: "#bec2ff"
+ property int thinkingPhase: 0
+ property string scryCommPhase: "idle"
+ property string daemonState: "asleep"
+ property var aseDrawnFolds: []
+ property string intelActiveMode: ""
+ property real commandPulse: 0.0
+ property real animationPhase: 0.0
+ property string breathingStyle: "moon"
+ property real shiguruiTokensPerSec: 0.0
+ property string aseHubDrawFold: ""
+ property string aseHubRecallFold: ""
+}
diff --git a/lockscreens/kitsune/content/tw/TransparentWorld.qml b/lockscreens/kitsune/content/tw/TransparentWorld.qml
new file mode 100644
index 00000000..382c44f0
--- /dev/null
+++ b/lockscreens/kitsune/content/tw/TransparentWorld.qml
@@ -0,0 +1,599 @@
+/* TransparentWorld.qml — Shigurui Visual Substrate
+ * =====================================================
+ * Singleton token system for the "Transparent World" aesthetic.
+ *
+ * 透き通る世界 — Sukitōru Sekai
+ * The Transparent World is not blur. It is perfect clarity.
+ * The ability to see through surfaces — perceiving internal structure,
+ * blood flow, muscle contractions, the hidden truth beneath the visible.
+ * Depth is achieved through layered darkness and precise light,
+ * not gaussian smear. Every edge is sharp. Every accent is surgical.
+ *
+ * Design principles:
+ * · Obsidian depth — dark substrates that recede, not obscure
+ * · Bioluminescent precision — accent colors as living light, not decoration
+ * · Sharp borders — 1px crisp lines, not blurred halos
+ * · Shadow glow — depth through shadow, never through blur
+ * · Crystalline text — maximum legibility, zero softening
+ *
+ * Usage:
+ * import "." as TW
+ * color: TW.TransparentWorld.resolveAccent(obeliskMode, discipline, intelMode, isInferring)
+ * layer.effect: TW.TransparentWorld.glowEffect(morphColor) // shadow only
+ *
+ * All components reference this singleton — one change propagates everywhere.
+ */
+pragma Singleton
+import QtQuick
+
+QtObject {
+ id: theme
+
+ // ── Glass substrate ───────────────────────────────────────────────
+ // Obsidian black — deep but not opaque. Depth through darkness, not blur.
+ readonly property color glassBase: Qt.rgba(0.039, 0.039, 0.039, 0.72)
+ readonly property color glassBaseDense: Qt.rgba(0.039, 0.039, 0.039, 0.88) // overlays, cards
+ readonly property color glassBaseLight: Qt.rgba(0.039, 0.039, 0.039, 0.45) // hover states
+ readonly property color glassBaseSolid: Qt.rgba(0.016, 0.016, 0.020, 0.97) // terminal, void
+ readonly property real glassBorderAlpha: 0.35 // default border opacity
+ readonly property real glassBorderFocus: 0.60 // focused/active border opacity
+ readonly property int glassRadius: 12
+ readonly property int glassRadiusLarge: 16
+ readonly property int glassRadiusSmall: 8
+
+ // ── Shadow depth tokens — depth through shadow, never blur ────────
+ // Use these to build MultiEffect shadow configs.
+ // Rule: layer.effect on a component = shadow only. No blurEnabled.
+ readonly property real shadowBlur: 0.85 // MultiEffect shadowBlur (0–1)
+ readonly property int shadowOffsetY: 6 // px vertical offset
+ readonly property real shadowAlpha: 0.25 // base shadow opacity
+ readonly property real shadowAlphaFocus: 0.45 // focused/active shadow opacity
+
+ // ── Typography ────────────────────────────────────────────────────
+ // Base: 14px. All sizes are semantic — use tokens, never hardcode.
+ //
+ // Scale (ascending):
+ // fontSizeMicro 9 timestamps, ghost decorations, faint hints
+ // fontSizeHint 10 tooltips, secondary meta, hover-only text
+ // fontSizeBadge 11 status chips, tier labels, badge text
+ // fontSizeLabel 12 field names, section sub-labels, captions
+ // fontSizeBase 14 ← baseline — body text, data readouts, chat
+ // fontSizeTitle 16 panel headers, ROM titles, primary labels
+ // fontSizeGlyph 20 icon glyphs, sigils, decorative symbols
+ //
+ // Exceptions (do not remap):
+ // Codex editor text stays at 13 (code density requirement)
+ // Display glyphs ≥22 are intentional visual anchors
+ readonly property string fontFamily: "3270 Nerd Font Mono, Fira Code"
+ readonly property string fontFamilyMono: "3270 Nerd Font Mono, Fira Code"
+ readonly property int fontSizeMicro: 9 // timestamps, ghost decorations
+ readonly property int fontSizeHint: 10 // tooltips, secondary meta
+ readonly property int fontSizeBadge: 11 // chips, tier labels, badges
+ readonly property int fontSizeLabel: 12 // field names, captions
+ readonly property int fontSizeBase: 14 // ← baseline body text
+ readonly property int fontSizeBody: 14 // alias — body text, data readouts
+ readonly property int fontSizeTelemetry: 14 // telemetry data readouts
+ readonly property int fontSizeTitle: 16 // panel headers, ROM titles
+ readonly property int fontSizeGlyph: 20 // icon glyphs, sigils
+ // Legacy aliases — kept for backward compat, map to nearest semantic token
+ readonly property int fontSizeMin: 9 // was 11 — now micro floor
+ readonly property int fontSizeTelemetryOld:14 // unchanged
+
+ // Obsidian outline stroke for readability against bright accents
+ readonly property color fontOutlineColor: Qt.rgba(0.039, 0.039, 0.039, 0.50)
+
+ // ── Animation ─────────────────────────────────────────────────────
+ readonly property int colorTransition: 350 // ms, InOutQuad for all color changes
+ readonly property int fadeTransition: 200 // ms, opacity fades
+ readonly property int springTransition: 400 // ms, layout/size changes
+ readonly property int snapTransition: 120 // ms, instant-feel snaps
+
+ // ── Hazard Matrix — the accent color system ───────────────────────
+ readonly property color accentCognitive: "#7B2CBF" // Deep Purple — default thinking
+ readonly property color accentSovereign: "#FFB000" // Amber — ? prefix
+ readonly property color accentSystem: "#00FF41" // Emerald — # prefix
+ readonly property color accentWater: "#00B4D8" // Sky Cerulean — water breathing
+ readonly property color accentForge: "#FF4E00" // Blood Orange — ! prefix
+ readonly property color accentBreach: "#f43f5e" // Arterial Red — security alert
+ readonly property color accentInferring: "#00B4D8" // Cerulean — Nemotron active
+
+ // Intel fold accents — the 7 Sibyls
+ // EYE / Delphic / TRUTH — electric cerulean, truth cuts clean
+ // PHANTOM / Hellespontine / POWER — creative violet, spectral presence
+ // BLADE / Cimmerian / WISDOM — hot magenta, wisdom forged in heat
+ // SAGE / Persian / JUDGMENT — sun amber, the light that reveals
+ // SHROUD / Erythraean / LIFE — bioluminescent teal, life in the deep
+ // FORGE / Tiburtine / LOVE — volcanic orange-red, molten creation
+ // MIRROR / Cumaean / DISCIPLINE — pale lavender, quiet and reflective
+ readonly property color accentEye: "#00C2FF"
+ readonly property color accentBlade: "#FF3CAC"
+ readonly property color accentSage: "#FFB300"
+ readonly property color accentMirror: "#C8B8FF"
+ readonly property color accentPhantom: "#B44FE8"
+ readonly property color accentShroud: "#00FFC8"
+ readonly property color accentForgeFold:"#FF6B35"
+
+ // Semantic status colors — not fold identity, used for ok/warn/error states
+ readonly property color accentOk: "#4ade80" // generic success / up / healthy
+ readonly property color accentWarn: "#fbbf24" // warning / degraded
+ // accentBreach (#f43f5e) serves as the semantic error/down color
+
+ // Breathing style accents
+ readonly property color _accentCelestial: "#ffffff" // placeholder, handled by ArOrchestrator prismatic logic
+ readonly property var styleAccents: ({
+ "sun": "#f59e0b",
+ "moon": "#818cf8",
+ "celestial": "#ffffff",
+ "concentrate": "#f59e0b"
+ })
+
+ // Discipline accents
+ readonly property var disciplineAccents: ({
+ "shroud": "#818cf8",
+ "obanai": "#d946ef",
+ "tengen": "#00B4D8",
+ "shinobu": "#4ade80"
+ })
+
+ // Intel mode accents
+ readonly property var modeAccents: ({
+ "eye": "#4ade80",
+ "blade": "#00B4D8",
+ "sage": "#9B1FA0",
+ "mirror": "#6366f1",
+ "forge": "#f59e0b",
+ "phantom": "#f43f5e",
+ "shroud": "#818cf8",
+ "dormant": "#00B4D8"
+ })
+
+ // ── resolvePrismatic — Prismatic Gradient, the single source of truth ──
+ // The canonical color resolver for the entire system.
+ // All components should call this or read ArOrchestrator.prismaticColor.
+ //
+ // Priority chain (highest → lowest):
+ // breach > discipline > modality > obeliskMode > anticipatedStyle
+ // > breathingStyle > intelMode > cognitive default
+ function resolvePrismatic(obeliskMode, activeDiscipline, intelMode, breathingStyle,
+ isInferring, isBreach, activeModality, anticipatedStyle) {
+ if (isBreach) return accentBreach
+ if (activeDiscipline === "shroud") return Qt.color(disciplineAccents["shroud"])
+ if (activeDiscipline === "obanai") return Qt.color(disciplineAccents["obanai"])
+ if (activeDiscipline === "tengen") return Qt.color(disciplineAccents["tengen"])
+ if (activeDiscipline === "shinobu") return Qt.color(disciplineAccents["shinobu"])
+ if (activeModality === "reflex") return accentBreach
+ if (activeModality === "sovereign") return accentSovereign
+ if (activeModality === "architect") return Qt.color("#a78bfa")
+ if (obeliskMode === "tactical" || obeliskMode === "applet") return accentCognitive
+ if (obeliskMode === "forge") return Qt.color(styleAccents["moon"])
+ if (obeliskMode === "system") return accentSystem
+ if (obeliskMode === "sovereign") return accentSovereign
+ if (obeliskMode === "war") return accentBreach
+ if (anticipatedStyle && anticipatedStyle !== "")
+ return Qt.color(styleAccents[anticipatedStyle] ?? accentWater)
+ if (obeliskMode === "breathing" || (breathingStyle && breathingStyle !== ""))
+ return Qt.color(styleAccents[breathingStyle] ?? accentWater)
+ if (intelMode && intelMode !== "dormant")
+ return Qt.color(modeAccents[intelMode] ?? accentWater)
+ if (isInferring) return accentInferring
+ return accentCognitive
+ }
+
+ // ── resolveAccent — delegates to resolvePrismatic (backward compat) ──
+ function resolveAccent(obeliskMode, activeDiscipline, intelMode, breathingStyle, isInferring, isBreach) {
+ return resolvePrismatic(obeliskMode, activeDiscipline, intelMode, breathingStyle,
+ isInferring, isBreach, "", "")
+ }
+
+ // ── resolveHazardColor — Speculum hazard color priority system ───────
+ // Priority: discipline > obelisk mode > breathing style > legacy threat
+ // Matches SpeculumCore.qml hazard color logic exactly for consistency
+ function resolveHazardColor(activeDiscipline, obeliskMode, breathingStyle, breachAlert, combatMode, isExecuting, armedMode) {
+ // Discipline overrides (highest priority)
+ if (activeDiscipline === "shroud") return Qt.color(accentShroud)
+ if (activeDiscipline === "obanai") return Qt.color(disciplineAccents["obanai"] ?? accentWater)
+ if (activeDiscipline === "tengen") return Qt.color(disciplineAccents["tengen"] ?? accentWater)
+ if (activeDiscipline === "shinobu") return Qt.color(disciplineAccents["shinobu"] ?? accentWater)
+
+ // Obelisk mode override — live preview while typing
+ if (obeliskMode === "sovereign") return accentSovereign
+ if (obeliskMode === "system") return accentSystem
+ if (obeliskMode === "forge") return accentMirror
+ if (obeliskMode === "tactical") return accentSage
+
+ // Trinity styles
+ if (breathingStyle === "sun") return accentSovereign
+ if (breathingStyle === "moon") return Qt.color(styleAccents["moon"])
+ if (breathingStyle === "celestial") return _accentCelestial
+
+ // Legacy threat-based colors (fallback)
+ if (breachAlert || combatMode) return accentBreach
+ if (isExecuting || armedMode) return accentForge
+ return accentWater // default cyan
+ }
+
+ // ── resolveNodeColor — single source of truth for node colors ────
+ // Used by SpeculumSigils, SanctumNodeSystem hover overlay, Tier2Arc, EnvironmentRing
+ // tier: 1=full, 2=65% sat, 3=30% sat
+ readonly property var _nodeBaseColors: ({
+ // Existing surface types
+ "target": "#f43f5e",
+ "vuln": "#f59e0b",
+ "recon": "#00B4D8",
+ "history": "#818cf8",
+ "local": "#4ade80",
+ "suggested": "#9B1FA0",
+ "curated": "#FFB000",
+ "system": "#6366f1",
+ "wifi": "#00B4D8",
+ "lan": "#4ade80",
+ "egress": "#FF4E00",
+ "service": "#86efac",
+ "bluetooth": "#a78bfa",
+ // Dark web types (new)
+ "darkweb": "#8B0000", // deep crimson — Rui's blood
+ "onion": "#8B0000", // .onion nodes
+ "market": "#6B0020", // blood maroon — dark marketplaces
+ "forum": "#7B1020", // dark rose — dark forums
+ "circuit": "#9B1FA0", // onion purple — Tor circuits
+ "tor": "#9B1FA0", // Tor nodes
+ "silk": "#6B0020", // Silk Road / Rui spider
+ // IPFS / distributed types (new)
+ "ipfs": "#6366f1", // sage violet — Wisteria's fold
+ "wisteria": "#6366f1", // Wisteria vault nodes
+ "p2p": "#a78bfa", // muted lavender — peer mesh
+ "distributed": "#a78bfa" // distributed network nodes
+ })
+
+ function resolveNodeColor(category, source, status, tier) {
+ // Derive effective category
+ var cat = category || ""
+ if (!cat) {
+ if (source === "curated") cat = "curated"
+ else if (source === "suggested") cat = "suggested"
+ else if (source === "wifi") cat = "wifi"
+ else if (source === "lan") cat = "lan"
+ else if (source === "egress") cat = "egress"
+ else if (source === "local") cat = "local"
+ // Dark web sources
+ else if (source === "darkweb") cat = "darkweb"
+ else if (source === "onion") cat = "onion"
+ else if (source === "silk") cat = "silk"
+ else if (source === "tor") cat = "tor"
+ // IPFS / distributed sources
+ else if (source === "ipfs") cat = "ipfs"
+ else if (source === "wisteria") cat = "wisteria"
+ else if (source === "p2p") cat = "p2p"
+ else {
+ if (status === "vuln" || status === "compromised") cat = "vuln"
+ else cat = "recon"
+ }
+ }
+ var hex = _nodeBaseColors[cat] || "#00B4D8"
+ var c = Qt.color(hex)
+
+ // Tier brightness/saturation scaling
+ var t = tier || 1
+ if (t === 2) {
+ var grey2 = (c.r + c.g + c.b) / 3
+ return Qt.rgba(c.r * 0.65 + grey2 * 0.35,
+ c.g * 0.65 + grey2 * 0.35,
+ c.b * 0.65 + grey2 * 0.35, 1.0)
+ }
+ if (t === 3) {
+ var grey3 = (c.r + c.g + c.b) / 3
+ return Qt.rgba(c.r * 0.30 + grey3 * 0.70,
+ c.g * 0.30 + grey3 * 0.70,
+ c.b * 0.30 + grey3 * 0.70, 1.0)
+ }
+ return c
+ }
+
+ // Node category color constants (full saturation, Tier 1)
+ readonly property color nodeColorTarget: Qt.color("#f43f5e")
+ readonly property color nodeColorVuln: Qt.color("#f59e0b")
+ readonly property color nodeColorRecon: Qt.color("#00B4D8")
+ readonly property color nodeColorHistory: Qt.color("#818cf8")
+ readonly property color nodeColorLocal: Qt.color("#4ade80")
+ readonly property color nodeColorSuggested: Qt.color("#9B1FA0")
+ readonly property color nodeColorCurated: Qt.color("#FFB000")
+ readonly property color nodeColorSystem: Qt.color("#6366f1")
+ readonly property color nodeColorWifi: Qt.color("#00B4D8")
+ readonly property color nodeColorLan: Qt.color("#4ade80")
+ readonly property color nodeColorEgress: Qt.color("#FF4E00")
+ // Dark web node colors (new)
+ readonly property color nodeColorDarkweb: Qt.color("#8B0000") // deep crimson — Rui's blood
+ readonly property color nodeColorOnion: Qt.color("#8B0000") // .onion nodes
+ readonly property color nodeColorMarket: Qt.color("#6B0020") // blood maroon — dark markets
+ readonly property color nodeColorTor: Qt.color("#9B1FA0") // onion purple — Tor circuits
+ // IPFS / distributed node colors (new)
+ readonly property color nodeColorIpfs: Qt.color("#6366f1") // sage violet — Wisteria's fold
+ readonly property color nodeColorWisteria: Qt.color("#6366f1") // Wisteria vault
+ readonly property color nodeColorP2p: Qt.color("#a78bfa") // muted lavender — peer mesh
+
+ // ── glassShadow — shadow-only depth helper ────────────────────────
+ // Returns a color for MultiEffect shadowColor at the right intensity.
+ // Use with shadowEnabled: true, blurEnabled: false.
+ // intensity: 0.0–1.0, defaults to shadowAlpha
+ function glassShadow(accentColor, intensity) {
+ if (!accentColor || accentColor === "") return Qt.rgba(0, 0, 0, 0)
+ var c = Qt.color(accentColor)
+ var a = (intensity !== undefined) ? intensity : shadowAlpha
+ return Qt.rgba(c.r, c.g, c.b, a)
+ }
+
+ // ── bloomColor — neon glow helper ─────────────────────────────────
+ function bloomColor(accent, intensity) {
+ if (!accent || accent === "") return Qt.rgba(0, 0, 0, 0)
+ var c = Qt.color(accent)
+ var a = intensity !== undefined ? intensity : 0.45
+ return Qt.rgba(c.r, c.g, c.b, a)
+ }
+
+ // ── Surface helpers ───────────────────────────────────────────────
+ readonly property color shimmerTop: Qt.rgba(1, 1, 1, 0.055)
+ readonly property color shimmerLeft: Qt.rgba(1, 1, 1, 0.035)
+ readonly property color divider: Qt.rgba(1, 1, 1, 0.07)
+ readonly property color textPrimary: Qt.rgba(0.90, 0.95, 1.00, 0.88)
+ readonly property color textSecondary: Qt.rgba(0.80, 0.88, 0.95, 0.45)
+ readonly property color textDim: Qt.rgba(0.75, 0.85, 0.92, 0.22)
+ readonly property color textGhost: Qt.rgba(0.75, 0.85, 0.92, 0.12)
+
+ // ── Ghost glow base text ──────────────────────────────────────────
+ // Near-white with faintest cerulean luminance — alive without competing.
+ readonly property color ghostText: Qt.rgba(0.90, 0.95, 1.00, 0.82)
+ readonly property color ghostTextDim: Qt.rgba(0.80, 0.88, 0.95, 0.45)
+ readonly property color ghostTextFaint: Qt.rgba(0.75, 0.85, 0.92, 0.22)
+
+ // Lexical color tokens ──────────────────────────────────────────
+ // Single source of truth — all components use these for consistent
+ // semantic highlighting across obelisk, terminal, conduit, reticle.
+ readonly property var lexColors: ({
+ // Vulnerability / CVE
+ "cve": "#F59E0B", // amber
+ "cveCritical":"#F43F5E", // arterial red — CVSS 9+
+ "cveHigh": "#FB923C", // orange — CVSS 7–9
+ // Network
+ "ip": "#C8E1DC", // ghost teal-white
+ "ipLocked": "#00B4D8", // cerulean — locked/active target
+ "mac": "#67E8F9", // sky cyan — MAC addresses
+ "port": "#67E8F9", // sky cyan
+ "hostname": "#A78BFA", // lavender — hostnames distinct from URLs
+ "url": "#C4B5FD", // lighter lavender — full URLs
+ // HTTP status families
+ "http2xx": "#86EFAC", // green
+ "http3xx": "#FCD34D", // yellow
+ "http4xx": "#FCA5A5", // rose
+ "http5xx": "#F87171", // red
+ // Tool sigils
+ "sigilPlus": "#86EFAC", // [+] green
+ "sigilMinus": "#F87171", // [-] red
+ "sigilWarn": "#FCD34D", // [!] yellow
+ "sigilInfo": "#67E8F9", // [*] cyan
+ "sigilMisc": "#C4B5FD", // [~] violet
+ "sigilINF": "#67E8F9",
+ "sigilWRN": "#FCD34D",
+ "sigilERR": "#F87171",
+ "sigilCRIT": "#F43F5E",
+ // Severity levels
+ "sevCritical":"#F43F5E", // CRITICAL — arterial red
+ "sevHigh": "#FB923C", // HIGH — orange
+ "sevMedium": "#FCD34D", // MEDIUM — yellow
+ "sevLow": "#86EFAC", // LOW — ghost green
+ "sevInfo": "#67E8F9", // INFO — cyan
+ // Crypto
+ "hash": "#A78BFA", // dim purple
+ "memAddr": "#9B1FA0", // blood violet — 0x hex addresses
+ // JSON
+ "jsonKey": "#67E8F9", // cerulean — "key": patterns
+ // Filesystem
+ "path": "#D7CDE1", // ghost lavender italic
+ // Timestamps
+ "timestamp": "#ffffff30", // ghost dim — [HH:MM:SS]
+ // Outcome keywords
+ "success": "#BED7C3", // ghost green-white
+ "error": "#8B0000", // arterial red
+ // Structural
+ "bracket": "#ffffff20", // ghost dim white (bracket chars)
+ "bracketInner": "#ffffff45",
+ // Sigil chars
+ "sigil": "#6B0020", // blood maroon (◈ ⟡ ∿)
+ // Numbers
+ "number": "#C8C3D7", // ghost grey-white
+ // Prompt
+ "prompt": "#6B0020" // ∿ prompt glyph
+ })
+
+ // ── Theme change signal — hook for future runtime theme switching ──
+ signal themeChanged(string themeName)
+ // Design contract: when daemonState === "dead", all prediction
+ // consumers MUST fall back to prefix/fuzzy matching only.
+ // No Nemotron calls, no usage weighting, no semantic ranking.
+ //
+ // Canonical offline pill sets — keyed by query context.
+ // Obelisk._staticFallbackPills() delegates here.
+ // Any component needing offline suggestions calls this function.
+ //
+ // Tiers (in priority order when daemon is dead):
+ // 1. Prefix match against availableMacros (< 1ms, always available)
+ // 2. Context-aware static pills from this function
+ // 3. Generic status/help fallback
+ function offlinePills(query, detectedIp) {
+ var q = (query || "").toLowerCase()
+ var ip = detectedIp || ""
+ if (q.includes("scan") || q.includes("recon"))
+ return [{ label: "DEEP SCAN", cmd: "/recon " + ip },
+ { label: "NUCLEI", cmd: "/strike:nuclei " + ip }]
+ if (q.includes("exploit") || q.includes("strike"))
+ return [{ label: "SYNDICATE", cmd: "/strike:syndicate " + ip },
+ { label: "MONITOR", cmd: "/strike:monitor " + ip }]
+ if (q.includes("shroud") || q.includes("vpn"))
+ return [{ label: "SHROUD ON", cmd: "/shroud toggle" },
+ { label: "THE SHROUD", cmd: "/intel:shroud" }]
+ if (q.includes("forge") || q.includes("playbook"))
+ return [{ label: "FORGE", cmd: "/forge:playbook" },
+ { label: "TRAINING", cmd: "/forge:forge" }]
+ if (q.startsWith("@"))
+ return [{ label: "CONDUIT", cmd: "@conduit" },
+ { label: "HUB", cmd: "@hub" }]
+ return [{ label: "STATUS", cmd: "/status" },
+ { label: "HELP", cmd: "/help" }]
+ }
+ // Returns HTML-rich text. Call from any component that renders
+ // terminal / command output. Strips ANSI before processing.
+ function lexHighlight(raw, lockedTarget) {
+ var s = _stripAnsi(raw)
+ s = s.replace(/&/g, "&").replace(//g, ">")
+
+ // Timestamps [HH:MM:SS] or [YYYY-MM-DD HH:MM:SS]
+ s = s.replace(/\[(\d{2}:\d{2}:\d{2}(?:\.\d+)?)\]/g,
+ '[$1]')
+ s = s.replace(/\[(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}[^\]]*)\]/g,
+ '[$1]')
+
+ // CVE IDs — severity-weighted by CVSS if present
+ s = s.replace(/\b(CVE-\d{4}-\d{4,7})\b/g,
+ '$1')
+
+ // Severity levels — graduated color
+ s = s.replace(/\b(CRITICAL)\b/g,
+ '$1')
+ s = s.replace(/\b(HIGH)\b/g,
+ '$1')
+ s = s.replace(/\b(MEDIUM)\b/g,
+ '$1')
+ s = s.replace(/\b(LOW)\b/g,
+ '$1')
+ s = s.replace(/\b(INFO)\b/g,
+ '$1')
+
+ // Hex memory addresses — 0x prefix
+ s = s.replace(/\b(0x[0-9a-fA-F]{4,16})\b/g,
+ '$1')
+
+ // Hashes (32–64 hex chars) — before URLs to avoid double-match
+ s = s.replace(/\b([0-9a-fA-F]{32,64})\b/g,
+ '$1')
+
+ // URLs / domains (before IP to avoid double-match)
+ s = s.replace(/(https?:\/\/[^\s<>"]+)/g,
+ '$1')
+
+ // Hostnames — word.word or word.word.tld (not full URLs, not IPs)
+ s = s.replace(/\b([a-zA-Z][a-zA-Z0-9\-]*\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2,})?)\b/g,
+ function(m) {
+ // Skip if already inside a URL tag
+ return '' + m + ''
+ })
+
+ // MAC addresses
+ s = s.replace(/\b([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})\b/g,
+ '$1')
+
+ // IPs — locked target gets cerulean, others get ghost teal
+ s = s.replace(/\b(\d{1,3}(?:\.\d{1,3}){3}(?::\d+)?)\b/g,
+ function(m) {
+ var col = (lockedTarget && m.startsWith(lockedTarget))
+ ? lexColors["ipLocked"]
+ : lexColors["ip"]
+ return '' + m + ''
+ })
+
+ // Port/protocol combos
+ s = s.replace(/\b(\d{1,5})\/(tcp|udp|open|closed|filtered)\b/gi,
+ '$1/$2')
+
+ // HTTP status codes
+ s = s.replace(/\b(200|201|204)\b/g,
+ '$1')
+ s = s.replace(/\b(301|302|307|308)\b/g,
+ '$1')
+ s = s.replace(/\b(400|401|403|404|405|429)\b/g,
+ '$1')
+ s = s.replace(/\b(500|502|503|504)\b/g,
+ '$1')
+
+ // JSON keys — "key": pattern
+ s = s.replace(/"([a-zA-Z_][a-zA-Z0-9_]*)"\s*:/g,
+ '"$1":')
+
+ // Tool sigils
+ s = s.replace(/\[(\+)\]/g,
+ '[+]')
+ s = s.replace(/\[(-)\]/g,
+ '[-]')
+ s = s.replace(/\[(!)\]/g,
+ '[!]')
+ s = s.replace(/\[(\*)\]/g,
+ '[*]')
+ s = s.replace(/\[(~)\]/g,
+ '[~]')
+ s = s.replace(/\[(INF)\]/g,
+ '[INF]')
+ s = s.replace(/\[(WRN)\]/g,
+ '[WRN]')
+ s = s.replace(/\[(ERR)\]/g,
+ '[ERR]')
+ s = s.replace(/\[(CRITICAL)\]/g,
+ '[CRITICAL]')
+
+ // Filesystem paths
+ s = s.replace(/((?:\/|~)[a-zA-Z0-9_.~\-]+(?:\/[a-zA-Z0-9_.~\-]+)*(?::\d+)*)/g,
+ '$1')
+
+ // Success keywords
+ s = s.replace(/\b(SUCCESS|OK|ACTIVE|STABLE|DONE|UP|PASS|FOUND|OPEN)\b/g,
+ '$1')
+
+ // Error keywords
+ s = s.replace(/\b(ERROR|FAIL|DENIED|FATAL|DOWN|WARNING|WARN|KILLED|CLOSED|FILTERED)\b/gi,
+ '$1')
+
+ // Brackets
+ s = s.replace(/\[([^\]]*)\]/g,
+ '[' +
+ '$1' +
+ ']')
+
+ // Sigil chars
+ s = s.replace(/(◈|⟡|∿)/g,
+ '$1')
+
+ // Numbers (not already colored)
+ s = s.replace(/(?$1')
+
+ return s
+ }
+
+ // ── colorToHue — extract HSV hue (0.0–1.0) from a color ──────────
+ // Used by ShiguruiSanctum shader uniforms which expect normalized hue.
+ function colorToHue(c) {
+ var col = Qt.color(c)
+ var r = col.r, g = col.g, b = col.b
+ var mx = Math.max(r, g, b), mn = Math.min(r, g, b), d = mx - mn
+ if (d < 0.001) return 0.0
+ var h
+ if (mx === r) h = ((g - b) / d + (g < b ? 6 : 0))
+ else if (mx === g) h = ((b - r) / d + 2)
+ else h = ((r - g) / d + 4)
+ return h / 6.0
+ }
+
+ // Precomputed fold hues for sanctum shader uniforms
+ readonly property real hueEye: colorToHue(accentEye)
+ readonly property real hueBlade: colorToHue(accentBlade)
+ readonly property real hueSage: colorToHue(accentSage)
+ readonly property real hueMirror: colorToHue(accentMirror)
+ readonly property real hueForge: colorToHue(accentForgeFold)
+ readonly property real huePhantom: colorToHue(accentPhantom)
+ readonly property real hueShroud: colorToHue(accentShroud)
+ readonly property real hueWater: colorToHue(accentWater)
+ readonly property real hueSovereign: colorToHue(accentSovereign)
+ function _stripAnsi(s) {
+ return s.replace(/\x1b\[[0-9;]*[mGKHFABCDJsu]/g, "")
+ .replace(/\x1b\][^\x07]*\x07/g, "")
+ .replace(/\x1b[()][AB012]/g, "")
+ .replace(/\r/g, "")
+ }
+}
diff --git a/lockscreens/kitsune/content/tw/qmldir b/lockscreens/kitsune/content/tw/qmldir
new file mode 100644
index 00000000..7b2a211a
--- /dev/null
+++ b/lockscreens/kitsune/content/tw/qmldir
@@ -0,0 +1,4 @@
+module tw
+
+singleton TransparentWorld 1.0 TransparentWorld.qml
+singleton ArOrchestrator 1.0 ArOrchestrator.qml
diff --git a/lockscreens/kitsune/manifest.json b/lockscreens/kitsune/manifest.json
new file mode 100644
index 00000000..2762bd09
--- /dev/null
+++ b/lockscreens/kitsune/manifest.json
@@ -0,0 +1,201 @@
+{
+ "schema": 1,
+ "id": "kitsune",
+ "category": "lockscreens",
+ "version": "1.0.0",
+ "destination": "qylock/themes/kitsune",
+ "files": [
+ {
+ "source": "LICENSE",
+ "destination": "LICENSE",
+ "mode": "0644",
+ "size": 35149,
+ "sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986",
+ "install": true
+ },
+ {
+ "source": "PROVENANCE.txt",
+ "destination": "PROVENANCE.txt",
+ "mode": "0644",
+ "size": 2205,
+ "sha256": "178c6efe4b0aaf5a126ecc4978ce93b3ad83bfe93b6928fc01fde2ca66ba73f9",
+ "install": true
+ },
+ {
+ "source": "assets/preview.webp",
+ "destination": "preview.webp",
+ "mode": "0644",
+ "size": 18666,
+ "sha256": "e37eb300f43d44a2728dde3dd82de0906dd34d83dbc07c6b9888ba64703ec53a",
+ "install": false
+ },
+ {
+ "source": "content/Main.qml",
+ "destination": "Main.qml",
+ "mode": "0644",
+ "size": 16243,
+ "sha256": "ad41b7d8980a858baca06d7bbb2c374af856fa9520a099402322d2e94cd79eea",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/InseiCompanion.qml",
+ "destination": "gotchi/InseiCompanion.qml",
+ "mode": "0644",
+ "size": 5882,
+ "sha256": "8f7f9ff416fa9cd76d63076dc38ae1d50b913830721747cf0b3a4590e8d19614",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/InseiGotchi.qml",
+ "destination": "gotchi/InseiGotchi.qml",
+ "mode": "0644",
+ "size": 26998,
+ "sha256": "98855df11e2a479aa1345cc888fc1ba2bce24f0d570e13bc32d70453d12cc22a",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/InseiHub.qml",
+ "destination": "gotchi/InseiHub.qml",
+ "mode": "0644",
+ "size": 9221,
+ "sha256": "811586479299669437d2999d1a605fc78fc5dac12c601452e381ca87fecd91f7",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/InseiHud.qml",
+ "destination": "gotchi/InseiHud.qml",
+ "mode": "0644",
+ "size": 5746,
+ "sha256": "67b98c9f1b5f0b154df0c7df9f786d1aa44a1f5ced2f952a331361b02fa23038",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/InseiLiveHash.qml",
+ "destination": "gotchi/InseiLiveHash.qml",
+ "mode": "0644",
+ "size": 9014,
+ "sha256": "962bf0ddaf8880e11cbce3982dfbf55866063dfc75a88f6953be51c803d1c364",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/InseiScene.qml",
+ "destination": "gotchi/InseiScene.qml",
+ "mode": "0644",
+ "size": 14032,
+ "sha256": "cc72e01e3b219ca07521c4c8da6b506d5d0f06575c2da8e8758fc55845963f50",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/InseiTarot.qml",
+ "destination": "gotchi/InseiTarot.qml",
+ "mode": "0644",
+ "size": 14520,
+ "sha256": "55f5f4a19e0c382648c90e08286f963156df6215a7a3b43362b59c9f4779bc05",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/README.md",
+ "destination": "gotchi/README.md",
+ "mode": "0644",
+ "size": 5456,
+ "sha256": "0bafb4b4cdb957d55ff476416b082509d23f6e72f41a22bd54b2ec3159d4e7c3",
+ "install": true
+ },
+ {
+ "source": "content/gotchi/qmldir",
+ "destination": "gotchi/qmldir",
+ "mode": "0644",
+ "size": 251,
+ "sha256": "5af186846a5b0ddbf828ea980b48b2c656488cb298bed6f1d836e1bb16776052",
+ "install": true
+ },
+ {
+ "source": "content/metadata.desktop",
+ "destination": "metadata.desktop",
+ "mode": "0644",
+ "size": 209,
+ "sha256": "06afcc0e6368068f6e8a2cd246b374552be680f0468080c0e72b67335f6f4fde",
+ "install": true
+ },
+ {
+ "source": "content/shaders/InseiKitsune.frag",
+ "destination": "shaders/InseiKitsune.frag",
+ "mode": "0644",
+ "size": 8253,
+ "sha256": "ffae335bd97115a8b96794a65ccc2913833b4313f10cd6e912a8d8f0c3905c76",
+ "install": true
+ },
+ {
+ "source": "content/shaders/InseiKitsune.frag.qsb",
+ "destination": "shaders/InseiKitsune.frag.qsb",
+ "mode": "0644",
+ "size": 12381,
+ "sha256": "d43bd280aad1f0a2ea97f80162d393bfc2d5708303927b02ab8ede35529115e4",
+ "install": true
+ },
+ {
+ "source": "content/shaders/InseiSoul.frag",
+ "destination": "shaders/InseiSoul.frag",
+ "mode": "0644",
+ "size": 13158,
+ "sha256": "d37476af799b22efb6abf19fee92dcae61fa4bb8f9a779ac0e108f10df79888e",
+ "install": true
+ },
+ {
+ "source": "content/shaders/InseiSoul.frag.qsb",
+ "destination": "shaders/InseiSoul.frag.qsb",
+ "mode": "0644",
+ "size": 17847,
+ "sha256": "065c28ac92e359368d1fc49a9daff5190a6ed486189f38f0a52f26eae8ad2ca6",
+ "install": true
+ },
+ {
+ "source": "content/shaders/crystallize.frag",
+ "destination": "shaders/crystallize.frag",
+ "mode": "0644",
+ "size": 13837,
+ "sha256": "dfcabc64b4f45b5e11065acd8a87a3eaae6557c056c139b4ada50e110c949c9b",
+ "install": true
+ },
+ {
+ "source": "content/shaders/crystallize.frag.qsb",
+ "destination": "shaders/crystallize.frag.qsb",
+ "mode": "0644",
+ "size": 13658,
+ "sha256": "7ead89666598790c2220a0394fc278a174f10356cb5d6baff7e781d71ee97f39",
+ "install": true
+ },
+ {
+ "source": "content/theme.conf",
+ "destination": "theme.conf",
+ "mode": "0644",
+ "size": 343,
+ "sha256": "e35fc5c9904b1a44552f20eeb0d0750eea3c2369eb39f8fbfc69df23a95069c0",
+ "install": true
+ },
+ {
+ "source": "content/tw/ArOrchestrator.qml",
+ "destination": "tw/ArOrchestrator.qml",
+ "mode": "0644",
+ "size": 1159,
+ "sha256": "64c527c73d40b7f8a9c8e9cda482b37d704bad0b96022dba80caf219d233ee56",
+ "install": true
+ },
+ {
+ "source": "content/tw/TransparentWorld.qml",
+ "destination": "tw/TransparentWorld.qml",
+ "mode": "0644",
+ "size": 31709,
+ "sha256": "f7cb77f4755bb7d0897deef5ff4dee72e93780cfdec0e76fc49f5213292ca259",
+ "install": true
+ },
+ {
+ "source": "content/tw/qmldir",
+ "destination": "tw/qmldir",
+ "mode": "0644",
+ "size": 111,
+ "sha256": "74454dbad4d61adf208f321fa8a32e9e2e7d06b9b6e80c7769b347c780c1a8f1",
+ "install": true
+ }
+ ]
+}
diff --git a/lockscreens/registry.json b/lockscreens/registry.json
index 202f41c9..a997c299 100644
--- a/lockscreens/registry.json
+++ b/lockscreens/registry.json
@@ -1,941 +1,965 @@
{
- "schema": 1,
- "lockscreens": [
- {
- "id": "clockwork-tape",
- "name": "Tape",
- "version": "1.0.1",
- "path": "lockscreens/clockwork-tape",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "Clockwork mechanics drift across warm magnetic tape.",
- "description": "A complete qylock scene with a tactile clock face, deep shadow, and an amber tape-era palette.",
- "tags": [
- "clockwork",
- "retro",
- "warm"
- ],
- "accent": "#d7a45f",
- "surface": "#101010",
- "preview": "assets/preview.webp",
- "screenshots": [],
- "manifest": "manifest.json",
- "manifestSha256": "11a60cd3d278410f5190ccb0f2c32aea4c8a25e52a0b734b87f4434dd0bcdec8"
- },
- {
- "id": "dog-samurai",
- "name": "Dog Samurai",
- "version": "1.0.1",
- "path": "lockscreens/dog-samurai",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A painterly samurai hound under drifting red petals.",
- "description": "A cinematic video lock: a straw-hatted samurai dog with a sheathed katana as crimson petals fall, paired with a bold clock and a minimal login.",
- "tags": [
- "samurai",
- "painterly",
- "cinematic"
- ],
- "accent": "#945a46",
- "surface": "#141612",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "ba957bc1ae884d5df67ab39ba9ab21850af5bfc9faec50014c06a4aefe571470"
- },
- {
- "id": "enfield",
- "name": "Enfield",
- "version": "1.0.1",
- "path": "lockscreens/enfield",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "Golden hour spilling through a plant-framed window.",
- "description": "A warm, atmospheric lock: late sun pours past ivy and a weathered window frame, with a quiet login.",
- "tags": [
- "cozy",
- "warm",
- "scenic"
- ],
- "accent": "#111418",
- "surface": "#111418",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "42d2cd29b6cdd2d356ff2c25217be87268b68958a260a7e3d5a670fc717d1287"
- },
- {
- "id": "field",
- "name": "Field",
- "version": "1.0.1",
- "path": "lockscreens/field",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "An open pasture under soft light with a feather-light clock.",
- "description": "A serene qylock scene: a wide field, a thin Orbitron clock, and a quiet amber login tucked into the corner.",
- "tags": [
- "scenic",
- "minimal",
- "calm"
- ],
- "accent": "#3c6678",
- "surface": "#19180f",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "05b8731d4e438f9ba1246ecada52d87f01bb3efca5ff24063db2bba91a1e1bad"
- },
- {
- "id": "forest",
- "name": "Forest",
- "version": "1.0.1",
- "path": "lockscreens/forest",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "Sunbeams pouring through a misty autumn canopy.",
- "description": "A tranquil video lock: light rays drift over an aerial autumn forest wrapped in fog, with a soft clock and an unobtrusive login.",
- "tags": [
- "scenic",
- "nature",
- "calm"
- ],
- "accent": "#051612",
- "surface": "#051612",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "d57a82bfb8b3b27df93e93e9ae0b90bcffebb1eebb5be83705dbe7a14ddc8b03"
- },
- {
- "id": "genshin",
- "name": "Genshin Impact",
- "version": "1.0.0",
- "path": "lockscreens/genshin",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A moonlit abyss of drifting pillars in deep blue.",
- "description": "A Genshin Impact inspired lock: towering ruins beneath a pale moon, slow-drifting clouds, and a clean login on the descending path.",
- "tags": [
- "game",
- "anime",
- "night"
- ],
- "accent": "#192e6d",
- "surface": "#07101f",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "370fd6d19c8b0b8c111c5fb77b3e1f7533644a3272c9415d176438b004c41caf"
- },
- {
- "id": "girl-coffee",
- "name": "Coffee Break",
- "version": "1.0.1",
- "path": "lockscreens/girl-coffee",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A cosy hand-drawn morning with a warm mug in hand.",
- "description": "A lo-fi illustrated character taking a coffee break, paired with the rounded Itim typeface and a soft, unobtrusive login.",
- "tags": [
- "anime",
- "cozy",
- "illustrated"
- ],
- "accent": "#8eb9b6",
- "surface": "#695b76",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "f55205b272b11cccf29bc9840f18bfdd81268d72ea1bcfca6d78e2ed6b925660"
- },
- {
- "id": "girl-pillow",
- "name": "Lazy Sunday",
- "version": "1.0.1",
- "path": "lockscreens/girl-pillow",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A soft illustrated scene, curled up and half awake.",
- "description": "A relaxed lo-fi illustration with the rounded Itim font and a gentle right-aligned login - quiet, warm, and unhurried.",
- "tags": [
- "anime",
- "cozy",
- "illustrated"
- ],
- "accent": "#737588",
- "surface": "#625f6c",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "1b0a2f1f99ad1bf0f0a0135b2e5363212017f27543ba30a3cbce83956abfc402"
- },
- {
- "id": "last-of-us",
- "name": "The Last of Us",
- "version": "1.0.1",
- "path": "lockscreens/last-of-us",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A lone blossom tree adrift in cold grey mist.",
- "description": "A The Last of Us inspired lock: a solitary cherry tree on a rocky outcrop among fog-veiled cliffs, with a spare, moody login.",
- "tags": [
- "game",
- "scenic",
- "moody"
- ],
- "accent": "#88683b",
- "surface": "#131210",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "cb8e5fd420c113f5407144f3d2304b5541978105da25fd05ae91c484651ad242"
- },
- {
- "id": "man-bicycle",
- "name": "Evening Ride",
- "version": "1.0.1",
- "path": "lockscreens/man-bicycle",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A lone cyclist against a wide illustrated sky.",
- "description": "A cinematic lo-fi ride with the Itim typeface and a minimal login tucked into the frame.",
- "tags": [
- "scenic",
- "illustrated",
- "calm"
- ],
- "accent": "#2e8692",
- "surface": "#395551",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "08fe1c4fc9c646904ea57778974025266f3b7d5eccd5e723953956b727549ed8"
- },
- {
- "id": "material-you",
- "name": "Material You",
- "version": "1.0.1",
- "path": "lockscreens/material-you",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "Google's Material You language, clean and adaptive.",
- "description": "A crisp Material You lock with the Google Sans typeface, rounded surfaces, and a tidy centred login.",
- "tags": [
- "material",
- "clean",
- "modern"
- ],
- "accent": "#1a3c32",
- "surface": "#1a3c32",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "c0356b4a2419a42a9f7752551ed97991aca578576486598ba798dc219d0c09fc"
- },
- {
- "id": "material-you-dark",
- "name": "Material You Dark",
- "version": "1.0.1",
- "path": "lockscreens/material-you-dark",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "The Material You language in a deep, quiet dark palette.",
- "description": "The dark sibling of the Material You lock: the Google Sans typeface and rounded surfaces over a low-light palette, with a tidy centred login.",
- "tags": [
- "material",
- "dark",
- "clean"
- ],
- "accent": "#291f48",
- "surface": "#141316",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "d881d8b36b6c7a2236a61d4eec4f5928fa1771bba623e3500c119525b017f710"
- },
- {
- "id": "minecraft",
- "name": "Minecraft Menu",
- "version": "1.0.1",
- "path": "lockscreens/minecraft",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "The blocky main-menu look, splash text and all.",
- "description": "A nostalgic Minecraft title-screen lock: stone-button widgets, a random yellow splash line, and the classic logo.",
- "tags": [
- "game",
- "retro",
- "playful"
- ],
- "accent": "#251b12",
- "surface": "#0f0b08",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "7e2e756ca2b48688892e93b41b9e51678a0013e0d55caff909a9eff5e6993472"
- },
- {
- "id": "nier-automata",
- "name": "NieR: Automata",
- "version": "1.0.1",
- "path": "lockscreens/nier-automata",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "The stark beige-and-black UI of YoRHa.",
- "description": "A faithful NieR: Automata lock with bordered YoRHa buttons, the signature muted palette, and a precise login.",
- "tags": [
- "game",
- "minimal",
- "monochrome"
- ],
- "accent": "#1a1713",
- "surface": "#1a1713",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "08045d52f8dad638f1e6fe16264d0b7d6777345bab42b03086e88450d9dfb56e"
- },
- {
- "id": "ninja-gaiden",
- "name": "Ninja Gaiden",
- "version": "1.0.1",
- "path": "lockscreens/ninja-gaiden",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "High-contrast action styling with the Tektur face.",
- "description": "A sharp Ninja Gaiden lock: bold Tektur type, a dark cinematic background, and a decisive login.",
- "tags": [
- "game",
- "bold",
- "dark"
- ],
- "accent": "#040201",
- "surface": "#020101",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "ffd96cc2705ca8f549d6ab5a39a9b0e688f5e7ce431babd3cbe3a2b17177c7e7"
- },
- {
- "id": "nothing",
- "name": "Nothing OS",
- "version": "1.0.1",
- "path": "lockscreens/nothing",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "Dot-matrix minimalism in red and black.",
- "description": "A Nothing-inspired lock: NDot dot-matrix numerals, twin rounded widgets, and a bright, airy backdrop.",
- "tags": [
- "minimal",
- "monochrome",
- "modern"
- ],
- "accent": "#b71c23",
- "surface": "#0e0e0e",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "c59741f9666945fa068283813b76705b21280d2f5d74c302f79731653fcfb7c0"
- },
- {
- "id": "osu",
- "name": "osu!",
- "version": "1.0.0",
- "path": "lockscreens/osu",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "The osu! rhythm game, tap-circles and all.",
- "description": "An osu! inspired lock with difficulty menus, neon tap-circles, and a playful, score-driven login.",
- "tags": [
- "game",
- "rhythm",
- "playful"
- ],
- "accent": "#84fa04",
- "surface": "#060504",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "02d6a89c8240e86c0f7966fc08e125e98d52201db7e015374271054ce017bbc6"
- },
- {
- "id": "osumania",
- "name": "osu!mania",
- "version": "1.0.1",
- "path": "lockscreens/osumania",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A four-key note highway glowing in neon green.",
- "description": "An osu!mania inspired lock: vertical scrolling notes, key-binding widgets, and PERFECT combos over a dark stage.",
- "tags": [
- "game",
- "rhythm",
- "neon"
- ],
- "accent": "#050902",
- "surface": "#050902",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "58e08d256426ff0722cf5f3a565c3e6ab5544accf46c11635b1e50b599312807"
- },
- {
- "id": "pixel-coffee",
- "name": "Pixel Coffee",
- "version": "1.0.1",
- "path": "lockscreens/pixel-coffee",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A warm pixel cafe glowing after dark.",
- "description": "A cosy pixel-art lock: amber lamps over a late-night cafe, city lights through the window, and a tidy login.",
- "tags": [
- "pixel",
- "cozy",
- "night"
- ],
- "accent": "#090811",
- "surface": "#090811",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "10ac931fe1f74386399c80f94048b835974372fadc81194f97aed3919caef16a"
- },
- {
- "id": "pixel-cyberpunk",
- "name": "Pixel Cyberpunk",
- "version": "1.0.1",
- "path": "lockscreens/pixel-cyberpunk",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A neon pixel-art cityscape humming with rain and signage.",
- "description": "An animated pixel-art cyberpunk street, all neon reflections and drizzle, with a crisp login.",
- "tags": [
- "pixel",
- "cyberpunk",
- "neon"
- ],
- "accent": "#070e18",
- "surface": "#070e18",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "eff36e820cb59fa9c40bb55b77760c66c5c8c688f0ec652997405510e1760c42"
- },
- {
- "id": "pixel-dusk-city",
- "name": "Pixel Dusk City",
- "version": "1.0.1",
- "path": "lockscreens/pixel-dusk-city",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A red-lit pixel skyline smouldering at dusk.",
- "description": "A pixel-art lock: a silhouetted city under a burning dusk sky, framed by dark leaves and tangled wires, with a minimal login.",
- "tags": [
- "pixel",
- "city",
- "dusk"
- ],
- "accent": "#894a45",
- "surface": "#0c0e0f",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "cc9aec4398157c0f6dcce6f4daa97e8affe424167c8072505794cde7e92e8f8e"
- },
- {
- "id": "pixel-emerald",
- "name": "Pixel Emerald",
- "version": "1.0.1",
- "path": "lockscreens/pixel-emerald",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A seaside cycling route in bright pixel green.",
- "description": "A Pokemon inspired pixel lock: a cyclist on a fenced coastal path amid lush fields and creatures, with a cheerful login.",
- "tags": [
- "pixel",
- "game",
- "nostalgic"
- ],
- "accent": "#78a13c",
- "surface": "#3b5649",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "b3c2d5c919994db4d111aa05e76ea715f65f081ba3f7390ad9ac634af2d978c6"
- },
- {
- "id": "pixel-hollowknight",
- "name": "Pixel Hollow Knight",
- "version": "1.0.1",
- "path": "lockscreens/pixel-hollowknight",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A horned knight silhouetted against embered dark.",
- "description": "A Hollow Knight inspired pixel lock: a pale-horned figure above drifting orange embers in the deep, with a stark login.",
- "tags": [
- "game",
- "pixel",
- "dark"
- ],
- "accent": "#070604",
- "surface": "#070604",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "c6efbe7d6137eba34d0ee9e0ae53829ee7d6200088add32ea5d2bcac9b827af6"
- },
- {
- "id": "pixel-munchlax",
- "name": "Pixel Munchlax",
- "version": "1.0.1",
- "path": "lockscreens/pixel-munchlax",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A little companion on a grassy hill under summer clouds.",
- "description": "A bright pixel-art lock: a small creature resting on green grass beneath towering white clouds, with a clean login.",
- "tags": [
- "pixel",
- "cute",
- "calm"
- ],
- "accent": "#5cacf0",
- "surface": "#0e1213",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "805c22b85dd622b8d040e820274d75bdb4b3bdd892813f5663f4e7736ec201fc"
- },
- {
- "id": "pixel-night-city",
- "name": "Pixel Night City",
- "version": "1.0.1",
- "path": "lockscreens/pixel-night-city",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A glittering pixel skyline humming after dark.",
- "description": "A pixel-art lock: dense neon-lit towers over shadowed rooftops, cool and electric, with a crisp login.",
- "tags": [
- "pixel",
- "city",
- "night"
- ],
- "accent": "#0e0e19",
- "surface": "#0e0e19",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "fb0c0c9fcf0e541ca7bdc8f3231fe041113cbcf0b853ff563f7b4d8cccda728a"
- },
- {
- "id": "pixel-rainyroom",
- "name": "Pixel Rainy Room",
- "version": "1.0.1",
- "path": "lockscreens/pixel-rainyroom",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A rainy night at the desk, monitor aglow.",
- "description": "A lo-fi pixel lock: rain on the window and a blue monitor glow across a dim bedroom, with a quiet login.",
- "tags": [
- "pixel",
- "cozy",
- "rain"
- ],
- "accent": "#02050c",
- "surface": "#02050c",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "796f5b8abf82a53166cac4f89162d356938ceea20b64b5db3aaf6dd32fcab268"
- },
- {
- "id": "pixel-sakura",
- "name": "Pixel Sakura",
- "version": "1.0.1",
- "path": "lockscreens/pixel-sakura",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A pixel-art courtyard under drifting cherry blossom.",
- "description": "An animated pixel-art scene of falling sakura petals, warm and unhurried, with a tidy login.",
- "tags": [
- "pixel",
- "anime",
- "calm"
- ],
- "accent": "#2e3949",
- "surface": "#2e3949",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "efcb1260e4e98ef97fddd97024de504e66da6c27401932476276afc1da36e1c7"
- },
- {
- "id": "pixel-skyscrapers",
- "name": "Pixel Skyscrapers",
- "version": "1.0.1",
- "path": "lockscreens/pixel-skyscrapers",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A pastel dawn washing over a layered skyline.",
- "description": "A pixel-art lock: peach-and-mauve skies and a soft sun behind stacked skyscrapers, with a calm login.",
- "tags": [
- "pixel",
- "city",
- "pastel"
- ],
- "accent": "#020e11",
- "surface": "#020e11",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "4e0b5bc1eafb4c62ff0bcc002d8271ad2248d819a308c3a5815652b759bf3985"
- },
- {
- "id": "pixel-waterfall",
- "name": "Pixel Waterfall",
- "version": "1.0.1",
- "path": "lockscreens/pixel-waterfall",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A tranquil pixel-art waterfall in a mossy glen.",
- "description": "An animated pixel-art waterfall scene, cool and green, with a calm minimal login.",
- "tags": [
- "pixel",
- "nature",
- "calm"
- ],
- "accent": "#106e97",
- "surface": "#040e15",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "56892aed6ec940d42f650fa922cf03e791b1dde6a329c898373dbc1e0022d72f"
- },
- {
- "id": "r1999-1",
- "name": "Reverse: 1999",
- "version": "1.0.1",
- "path": "lockscreens/r1999-1",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A pensive period portrait bathed in soft sepia light.",
- "description": "A Reverse: 1999 inspired lock centred on a wistful character portrait in warm vintage tones, with an understated login.",
- "tags": [
- "game",
- "anime",
- "vintage"
- ],
- "accent": "#422a1f",
- "surface": "#121011",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "4475b01a6aff68d111a37facbff83e78410c36fcb666b37712f46ba5df916620"
- },
- {
- "id": "r1999-2",
- "name": "Reverse: 1999",
- "version": "1.0.1",
- "path": "lockscreens/r1999-2",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "Vintage flourish with an animated period backdrop.",
- "description": "A Reverse: 1999 inspired lock with an animated vintage backdrop, a period logo, and an elegant login.",
- "tags": [
- "game",
- "anime",
- "vintage"
- ],
- "accent": "#88725b",
- "surface": "#101011",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "c14cf827cae63e5a7d6aa6ad69bcdadefe14ddd3b63ceba0e2e0a36e0c59b7a8"
- },
- {
- "id": "star-rail",
- "name": "Star Rail",
- "version": "1.0.0",
- "path": "lockscreens/star-rail",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "An astral, animated backdrop for the trailblazers.",
- "description": "A Honkai: Star Rail inspired lock with an animated cosmic backdrop and a clean, modern login.",
- "tags": [
- "game",
- "anime",
- "space"
- ],
- "accent": "#060b15",
- "surface": "#060b15",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "b11248876dbbfca5cf598955185340430f50b8d49bf904420233d2d752cd1311"
- },
- {
- "id": "sword",
- "name": "Sword",
- "version": "1.0.1",
- "path": "lockscreens/sword",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A lone blade planted in a misty grove.",
- "description": "A moody, near-monochrome lock: a katana standing point-down among blurred dark trees, with a spare login.",
- "tags": [
- "dark",
- "minimal",
- "cinematic"
- ],
- "accent": "#08090f",
- "surface": "#08090f",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "2df8248c4f52e49ed6e1ee4b4084771963d0777f03ee364d5acf65c56239eae5"
- },
- {
- "id": "terraria",
- "name": "Terraria",
- "version": "1.0.0",
- "path": "lockscreens/terraria",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "The Terraria main menu, pixel logo and all.",
- "description": "A Terraria title-screen lock with the pixel logo, a tree-lined horizon, and wood-panel buttons.",
- "tags": [
- "game",
- "pixel",
- "retro"
- ],
- "accent": "#4e66c5",
- "surface": "#1a2530",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "6105ce4c65eb446a0206d9ba3f9c9d08fe0b63276be120b44879800ba14b9237"
- },
- {
- "id": "windows-7",
- "name": "Windows 7",
- "version": "1.0.0",
- "path": "lockscreens/windows-7",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "The nostalgic Windows 7 welcome screen.",
- "description": "A throwback Windows 7 lock: a round avatar, a glassy credential field, and the familiar blue welcome mood.",
- "tags": [
- "os",
- "retro",
- "nostalgic"
- ],
- "accent": "#0755ad",
- "surface": "#043979",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "377240f577be55559d6328ffd5952a0053c0fcc3bcf5e87bc327967f66f5f77d"
- },
- {
- "id": "winter",
- "name": "Winter",
- "version": "1.0.1",
- "path": "lockscreens/winter",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A quiet, animated snowfall in cold blue.",
- "description": "A calm winter lock: soft, drifting snow over a cold-blue scene with an understated login.",
- "tags": [
- "scenic",
- "winter",
- "calm"
- ],
- "accent": "#0c1418",
- "surface": "#0c1418",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "81814180afe714ef766c1f83b377fb50990b424ff5006afc49f09fc7148b53b7"
- },
- {
- "id": "women-umbrella",
- "name": "Rainy Walk",
- "version": "1.0.1",
- "path": "lockscreens/women-umbrella",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "An illustrated figure under an umbrella in gentle rain.",
- "description": "A moody lo-fi illustration of a walk in the rain, with a calm, understated login.",
- "tags": [
- "illustrated",
- "rain",
- "calm"
- ],
- "accent": "#a07f80",
- "surface": "#3f3c38",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif",
- "assets/shot-1.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "65e313d43f563d9adefd93a35270527e1ef63bc041885680d8482a464a83e43d"
- },
- {
- "id": "world-machine",
- "name": "World Machine",
- "version": "1.0.2",
- "path": "lockscreens/world-machine",
- "author": "CratusGod",
- "upstream": "https://github.com/CratusGod/Oneshot-themed-Ryoku",
- "summary": "Niko's lightbulb over a OneShot sunset, with music.",
- "description": "A OneShot lock: Avolicis' pixel-art sunset animation (used with permission) with its soundtrack, Niko's lightbulb as your avatar, THE WORLD MACHINE wordmark, Terminus type and a Num Lock indicator. The matching shell reload cover and boot screen live at https://github.com/CratusGod/Oneshot-themed-Ryoku.",
- "tags": [
- "game",
- "pixel",
- "animated",
- "music"
- ],
- "accent": "#FFD34A",
- "surface": "#0B0A12",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "9b8f74183de4546b61c8b3dcf0726757494ba39bc00bb42b49a197cbfc0f7fb9",
- "official": false,
- "lastUpdated": "2026-10-21"
- },
- {
- "id": "wuwa",
- "name": "Wuthering Waves",
- "version": "1.0.0",
- "path": "lockscreens/wuwa",
- "author": "Darkkal44",
- "upstream": "https://github.com/Darkkal44/qylock",
- "summary": "A lone figure amid drifting ruins under pale light.",
- "description": "A Wuthering Waves inspired lock: a desolate greyscale expanse of floating debris cut by a single beam of light, with a modern login.",
- "tags": [
- "game",
- "anime",
- "monochrome"
- ],
- "accent": "#0e1018",
- "surface": "#0e1018",
- "preview": "assets/preview.gif",
- "screenshots": [
- "assets/preview.gif"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "cc3b45853c1f123626adf24d89beb60e3d332ec59e45b82f25ad6f93dbbb8cfe"
- },
- {
- "id": "nothing-interface",
- "name": "Nothing Interface",
- "version": "0.1.0",
- "path": "lockscreens/nothing-interface",
- "author": "Ivan <254410104+venchik111@users.noreply.github.com>",
- "upstream": "https://github.com/venchik111/ryoku-nothing-interface",
- "summary": "Dot clock and live Ryoku status over your current wallpaper.",
- "description": "A Nothing OS-inspired lockscreen with a dot clock, battery, daily screen time and MPRIS media controls. Uses the current Ryogami wallpaper during a locked session; no wallpaper is bundled.",
- "tags": [
- "nothing",
- "monochrome",
- "live",
- "wallpaper"
- ],
- "accent": "#C2C4CB",
- "surface": "#1C1D21",
- "preview": "assets/preview.png",
- "screenshots": [
- "assets/preview.png"
- ],
- "manifest": "manifest.json",
- "manifestSha256": "158f1f5ad37b51c589f511366eb23724bf10261c9c0d96f9782ae8640ca772d4",
- "official": false,
- "license": "MIT",
- "lastUpdated": "2026-10-01"
- }
- ]
-}
+ "schema": 1,
+ "lockscreens": [
+ {
+ "id": "clockwork-tape",
+ "name": "Tape",
+ "version": "1.0.1",
+ "path": "lockscreens/clockwork-tape",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "Clockwork mechanics drift across warm magnetic tape.",
+ "description": "A complete qylock scene with a tactile clock face, deep shadow, and an amber tape-era palette.",
+ "tags": [
+ "clockwork",
+ "retro",
+ "warm"
+ ],
+ "accent": "#d7a45f",
+ "surface": "#101010",
+ "preview": "assets/preview.webp",
+ "screenshots": [],
+ "manifest": "manifest.json",
+ "manifestSha256": "11a60cd3d278410f5190ccb0f2c32aea4c8a25e52a0b734b87f4434dd0bcdec8"
+ },
+ {
+ "id": "dog-samurai",
+ "name": "Dog Samurai",
+ "version": "1.0.1",
+ "path": "lockscreens/dog-samurai",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A painterly samurai hound under drifting red petals.",
+ "description": "A cinematic video lock: a straw-hatted samurai dog with a sheathed katana as crimson petals fall, paired with a bold clock and a minimal login.",
+ "tags": [
+ "samurai",
+ "painterly",
+ "cinematic"
+ ],
+ "accent": "#945a46",
+ "surface": "#141612",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "ba957bc1ae884d5df67ab39ba9ab21850af5bfc9faec50014c06a4aefe571470"
+ },
+ {
+ "id": "enfield",
+ "name": "Enfield",
+ "version": "1.0.1",
+ "path": "lockscreens/enfield",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "Golden hour spilling through a plant-framed window.",
+ "description": "A warm, atmospheric lock: late sun pours past ivy and a weathered window frame, with a quiet login.",
+ "tags": [
+ "cozy",
+ "warm",
+ "scenic"
+ ],
+ "accent": "#111418",
+ "surface": "#111418",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "42d2cd29b6cdd2d356ff2c25217be87268b68958a260a7e3d5a670fc717d1287"
+ },
+ {
+ "id": "field",
+ "name": "Field",
+ "version": "1.0.1",
+ "path": "lockscreens/field",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "An open pasture under soft light with a feather-light clock.",
+ "description": "A serene qylock scene: a wide field, a thin Orbitron clock, and a quiet amber login tucked into the corner.",
+ "tags": [
+ "scenic",
+ "minimal",
+ "calm"
+ ],
+ "accent": "#3c6678",
+ "surface": "#19180f",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "05b8731d4e438f9ba1246ecada52d87f01bb3efca5ff24063db2bba91a1e1bad"
+ },
+ {
+ "id": "forest",
+ "name": "Forest",
+ "version": "1.0.1",
+ "path": "lockscreens/forest",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "Sunbeams pouring through a misty autumn canopy.",
+ "description": "A tranquil video lock: light rays drift over an aerial autumn forest wrapped in fog, with a soft clock and an unobtrusive login.",
+ "tags": [
+ "scenic",
+ "nature",
+ "calm"
+ ],
+ "accent": "#051612",
+ "surface": "#051612",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "d57a82bfb8b3b27df93e93e9ae0b90bcffebb1eebb5be83705dbe7a14ddc8b03"
+ },
+ {
+ "id": "genshin",
+ "name": "Genshin Impact",
+ "version": "1.0.0",
+ "path": "lockscreens/genshin",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A moonlit abyss of drifting pillars in deep blue.",
+ "description": "A Genshin Impact inspired lock: towering ruins beneath a pale moon, slow-drifting clouds, and a clean login on the descending path.",
+ "tags": [
+ "game",
+ "anime",
+ "night"
+ ],
+ "accent": "#192e6d",
+ "surface": "#07101f",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "370fd6d19c8b0b8c111c5fb77b3e1f7533644a3272c9415d176438b004c41caf"
+ },
+ {
+ "id": "girl-coffee",
+ "name": "Coffee Break",
+ "version": "1.0.1",
+ "path": "lockscreens/girl-coffee",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A cosy hand-drawn morning with a warm mug in hand.",
+ "description": "A lo-fi illustrated character taking a coffee break, paired with the rounded Itim typeface and a soft, unobtrusive login.",
+ "tags": [
+ "anime",
+ "cozy",
+ "illustrated"
+ ],
+ "accent": "#8eb9b6",
+ "surface": "#695b76",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "f55205b272b11cccf29bc9840f18bfdd81268d72ea1bcfca6d78e2ed6b925660"
+ },
+ {
+ "id": "girl-pillow",
+ "name": "Lazy Sunday",
+ "version": "1.0.1",
+ "path": "lockscreens/girl-pillow",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A soft illustrated scene, curled up and half awake.",
+ "description": "A relaxed lo-fi illustration with the rounded Itim font and a gentle right-aligned login - quiet, warm, and unhurried.",
+ "tags": [
+ "anime",
+ "cozy",
+ "illustrated"
+ ],
+ "accent": "#737588",
+ "surface": "#625f6c",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "1b0a2f1f99ad1bf0f0a0135b2e5363212017f27543ba30a3cbce83956abfc402"
+ },
+ {
+ "id": "last-of-us",
+ "name": "The Last of Us",
+ "version": "1.0.1",
+ "path": "lockscreens/last-of-us",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A lone blossom tree adrift in cold grey mist.",
+ "description": "A The Last of Us inspired lock: a solitary cherry tree on a rocky outcrop among fog-veiled cliffs, with a spare, moody login.",
+ "tags": [
+ "game",
+ "scenic",
+ "moody"
+ ],
+ "accent": "#88683b",
+ "surface": "#131210",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "cb8e5fd420c113f5407144f3d2304b5541978105da25fd05ae91c484651ad242"
+ },
+ {
+ "id": "man-bicycle",
+ "name": "Evening Ride",
+ "version": "1.0.1",
+ "path": "lockscreens/man-bicycle",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A lone cyclist against a wide illustrated sky.",
+ "description": "A cinematic lo-fi ride with the Itim typeface and a minimal login tucked into the frame.",
+ "tags": [
+ "scenic",
+ "illustrated",
+ "calm"
+ ],
+ "accent": "#2e8692",
+ "surface": "#395551",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "08fe1c4fc9c646904ea57778974025266f3b7d5eccd5e723953956b727549ed8"
+ },
+ {
+ "id": "material-you",
+ "name": "Material You",
+ "version": "1.0.1",
+ "path": "lockscreens/material-you",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "Google's Material You language, clean and adaptive.",
+ "description": "A crisp Material You lock with the Google Sans typeface, rounded surfaces, and a tidy centred login.",
+ "tags": [
+ "material",
+ "clean",
+ "modern"
+ ],
+ "accent": "#1a3c32",
+ "surface": "#1a3c32",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "c0356b4a2419a42a9f7752551ed97991aca578576486598ba798dc219d0c09fc"
+ },
+ {
+ "id": "material-you-dark",
+ "name": "Material You Dark",
+ "version": "1.0.1",
+ "path": "lockscreens/material-you-dark",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "The Material You language in a deep, quiet dark palette.",
+ "description": "The dark sibling of the Material You lock: the Google Sans typeface and rounded surfaces over a low-light palette, with a tidy centred login.",
+ "tags": [
+ "material",
+ "dark",
+ "clean"
+ ],
+ "accent": "#291f48",
+ "surface": "#141316",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "d881d8b36b6c7a2236a61d4eec4f5928fa1771bba623e3500c119525b017f710"
+ },
+ {
+ "id": "minecraft",
+ "name": "Minecraft Menu",
+ "version": "1.0.1",
+ "path": "lockscreens/minecraft",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "The blocky main-menu look, splash text and all.",
+ "description": "A nostalgic Minecraft title-screen lock: stone-button widgets, a random yellow splash line, and the classic logo.",
+ "tags": [
+ "game",
+ "retro",
+ "playful"
+ ],
+ "accent": "#251b12",
+ "surface": "#0f0b08",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "7e2e756ca2b48688892e93b41b9e51678a0013e0d55caff909a9eff5e6993472"
+ },
+ {
+ "id": "nier-automata",
+ "name": "NieR: Automata",
+ "version": "1.0.1",
+ "path": "lockscreens/nier-automata",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "The stark beige-and-black UI of YoRHa.",
+ "description": "A faithful NieR: Automata lock with bordered YoRHa buttons, the signature muted palette, and a precise login.",
+ "tags": [
+ "game",
+ "minimal",
+ "monochrome"
+ ],
+ "accent": "#1a1713",
+ "surface": "#1a1713",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "08045d52f8dad638f1e6fe16264d0b7d6777345bab42b03086e88450d9dfb56e"
+ },
+ {
+ "id": "ninja-gaiden",
+ "name": "Ninja Gaiden",
+ "version": "1.0.1",
+ "path": "lockscreens/ninja-gaiden",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "High-contrast action styling with the Tektur face.",
+ "description": "A sharp Ninja Gaiden lock: bold Tektur type, a dark cinematic background, and a decisive login.",
+ "tags": [
+ "game",
+ "bold",
+ "dark"
+ ],
+ "accent": "#040201",
+ "surface": "#020101",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "ffd96cc2705ca8f549d6ab5a39a9b0e688f5e7ce431babd3cbe3a2b17177c7e7"
+ },
+ {
+ "id": "nothing",
+ "name": "Nothing OS",
+ "version": "1.0.1",
+ "path": "lockscreens/nothing",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "Dot-matrix minimalism in red and black.",
+ "description": "A Nothing-inspired lock: NDot dot-matrix numerals, twin rounded widgets, and a bright, airy backdrop.",
+ "tags": [
+ "minimal",
+ "monochrome",
+ "modern"
+ ],
+ "accent": "#b71c23",
+ "surface": "#0e0e0e",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "c59741f9666945fa068283813b76705b21280d2f5d74c302f79731653fcfb7c0"
+ },
+ {
+ "id": "osu",
+ "name": "osu!",
+ "version": "1.0.0",
+ "path": "lockscreens/osu",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "The osu! rhythm game, tap-circles and all.",
+ "description": "An osu! inspired lock with difficulty menus, neon tap-circles, and a playful, score-driven login.",
+ "tags": [
+ "game",
+ "rhythm",
+ "playful"
+ ],
+ "accent": "#84fa04",
+ "surface": "#060504",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "02d6a89c8240e86c0f7966fc08e125e98d52201db7e015374271054ce017bbc6"
+ },
+ {
+ "id": "osumania",
+ "name": "osu!mania",
+ "version": "1.0.1",
+ "path": "lockscreens/osumania",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A four-key note highway glowing in neon green.",
+ "description": "An osu!mania inspired lock: vertical scrolling notes, key-binding widgets, and PERFECT combos over a dark stage.",
+ "tags": [
+ "game",
+ "rhythm",
+ "neon"
+ ],
+ "accent": "#050902",
+ "surface": "#050902",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "58e08d256426ff0722cf5f3a565c3e6ab5544accf46c11635b1e50b599312807"
+ },
+ {
+ "id": "pixel-coffee",
+ "name": "Pixel Coffee",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-coffee",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A warm pixel cafe glowing after dark.",
+ "description": "A cosy pixel-art lock: amber lamps over a late-night cafe, city lights through the window, and a tidy login.",
+ "tags": [
+ "pixel",
+ "cozy",
+ "night"
+ ],
+ "accent": "#090811",
+ "surface": "#090811",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "10ac931fe1f74386399c80f94048b835974372fadc81194f97aed3919caef16a"
+ },
+ {
+ "id": "pixel-cyberpunk",
+ "name": "Pixel Cyberpunk",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-cyberpunk",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A neon pixel-art cityscape humming with rain and signage.",
+ "description": "An animated pixel-art cyberpunk street, all neon reflections and drizzle, with a crisp login.",
+ "tags": [
+ "pixel",
+ "cyberpunk",
+ "neon"
+ ],
+ "accent": "#070e18",
+ "surface": "#070e18",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "eff36e820cb59fa9c40bb55b77760c66c5c8c688f0ec652997405510e1760c42"
+ },
+ {
+ "id": "pixel-dusk-city",
+ "name": "Pixel Dusk City",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-dusk-city",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A red-lit pixel skyline smouldering at dusk.",
+ "description": "A pixel-art lock: a silhouetted city under a burning dusk sky, framed by dark leaves and tangled wires, with a minimal login.",
+ "tags": [
+ "pixel",
+ "city",
+ "dusk"
+ ],
+ "accent": "#894a45",
+ "surface": "#0c0e0f",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "cc9aec4398157c0f6dcce6f4daa97e8affe424167c8072505794cde7e92e8f8e"
+ },
+ {
+ "id": "pixel-emerald",
+ "name": "Pixel Emerald",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-emerald",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A seaside cycling route in bright pixel green.",
+ "description": "A Pokemon inspired pixel lock: a cyclist on a fenced coastal path amid lush fields and creatures, with a cheerful login.",
+ "tags": [
+ "pixel",
+ "game",
+ "nostalgic"
+ ],
+ "accent": "#78a13c",
+ "surface": "#3b5649",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "b3c2d5c919994db4d111aa05e76ea715f65f081ba3f7390ad9ac634af2d978c6"
+ },
+ {
+ "id": "pixel-hollowknight",
+ "name": "Pixel Hollow Knight",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-hollowknight",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A horned knight silhouetted against embered dark.",
+ "description": "A Hollow Knight inspired pixel lock: a pale-horned figure above drifting orange embers in the deep, with a stark login.",
+ "tags": [
+ "game",
+ "pixel",
+ "dark"
+ ],
+ "accent": "#070604",
+ "surface": "#070604",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "c6efbe7d6137eba34d0ee9e0ae53829ee7d6200088add32ea5d2bcac9b827af6"
+ },
+ {
+ "id": "pixel-munchlax",
+ "name": "Pixel Munchlax",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-munchlax",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A little companion on a grassy hill under summer clouds.",
+ "description": "A bright pixel-art lock: a small creature resting on green grass beneath towering white clouds, with a clean login.",
+ "tags": [
+ "pixel",
+ "cute",
+ "calm"
+ ],
+ "accent": "#5cacf0",
+ "surface": "#0e1213",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "805c22b85dd622b8d040e820274d75bdb4b3bdd892813f5663f4e7736ec201fc"
+ },
+ {
+ "id": "pixel-night-city",
+ "name": "Pixel Night City",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-night-city",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A glittering pixel skyline humming after dark.",
+ "description": "A pixel-art lock: dense neon-lit towers over shadowed rooftops, cool and electric, with a crisp login.",
+ "tags": [
+ "pixel",
+ "city",
+ "night"
+ ],
+ "accent": "#0e0e19",
+ "surface": "#0e0e19",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "fb0c0c9fcf0e541ca7bdc8f3231fe041113cbcf0b853ff563f7b4d8cccda728a"
+ },
+ {
+ "id": "pixel-rainyroom",
+ "name": "Pixel Rainy Room",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-rainyroom",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A rainy night at the desk, monitor aglow.",
+ "description": "A lo-fi pixel lock: rain on the window and a blue monitor glow across a dim bedroom, with a quiet login.",
+ "tags": [
+ "pixel",
+ "cozy",
+ "rain"
+ ],
+ "accent": "#02050c",
+ "surface": "#02050c",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "796f5b8abf82a53166cac4f89162d356938ceea20b64b5db3aaf6dd32fcab268"
+ },
+ {
+ "id": "pixel-sakura",
+ "name": "Pixel Sakura",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-sakura",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A pixel-art courtyard under drifting cherry blossom.",
+ "description": "An animated pixel-art scene of falling sakura petals, warm and unhurried, with a tidy login.",
+ "tags": [
+ "pixel",
+ "anime",
+ "calm"
+ ],
+ "accent": "#2e3949",
+ "surface": "#2e3949",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "efcb1260e4e98ef97fddd97024de504e66da6c27401932476276afc1da36e1c7"
+ },
+ {
+ "id": "pixel-skyscrapers",
+ "name": "Pixel Skyscrapers",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-skyscrapers",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A pastel dawn washing over a layered skyline.",
+ "description": "A pixel-art lock: peach-and-mauve skies and a soft sun behind stacked skyscrapers, with a calm login.",
+ "tags": [
+ "pixel",
+ "city",
+ "pastel"
+ ],
+ "accent": "#020e11",
+ "surface": "#020e11",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "4e0b5bc1eafb4c62ff0bcc002d8271ad2248d819a308c3a5815652b759bf3985"
+ },
+ {
+ "id": "pixel-waterfall",
+ "name": "Pixel Waterfall",
+ "version": "1.0.1",
+ "path": "lockscreens/pixel-waterfall",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A tranquil pixel-art waterfall in a mossy glen.",
+ "description": "An animated pixel-art waterfall scene, cool and green, with a calm minimal login.",
+ "tags": [
+ "pixel",
+ "nature",
+ "calm"
+ ],
+ "accent": "#106e97",
+ "surface": "#040e15",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "56892aed6ec940d42f650fa922cf03e791b1dde6a329c898373dbc1e0022d72f"
+ },
+ {
+ "id": "r1999-1",
+ "name": "Reverse: 1999",
+ "version": "1.0.1",
+ "path": "lockscreens/r1999-1",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A pensive period portrait bathed in soft sepia light.",
+ "description": "A Reverse: 1999 inspired lock centred on a wistful character portrait in warm vintage tones, with an understated login.",
+ "tags": [
+ "game",
+ "anime",
+ "vintage"
+ ],
+ "accent": "#422a1f",
+ "surface": "#121011",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "4475b01a6aff68d111a37facbff83e78410c36fcb666b37712f46ba5df916620"
+ },
+ {
+ "id": "r1999-2",
+ "name": "Reverse: 1999",
+ "version": "1.0.1",
+ "path": "lockscreens/r1999-2",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "Vintage flourish with an animated period backdrop.",
+ "description": "A Reverse: 1999 inspired lock with an animated vintage backdrop, a period logo, and an elegant login.",
+ "tags": [
+ "game",
+ "anime",
+ "vintage"
+ ],
+ "accent": "#88725b",
+ "surface": "#101011",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "c14cf827cae63e5a7d6aa6ad69bcdadefe14ddd3b63ceba0e2e0a36e0c59b7a8"
+ },
+ {
+ "id": "star-rail",
+ "name": "Star Rail",
+ "version": "1.0.0",
+ "path": "lockscreens/star-rail",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "An astral, animated backdrop for the trailblazers.",
+ "description": "A Honkai: Star Rail inspired lock with an animated cosmic backdrop and a clean, modern login.",
+ "tags": [
+ "game",
+ "anime",
+ "space"
+ ],
+ "accent": "#060b15",
+ "surface": "#060b15",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "b11248876dbbfca5cf598955185340430f50b8d49bf904420233d2d752cd1311"
+ },
+ {
+ "id": "sword",
+ "name": "Sword",
+ "version": "1.0.1",
+ "path": "lockscreens/sword",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A lone blade planted in a misty grove.",
+ "description": "A moody, near-monochrome lock: a katana standing point-down among blurred dark trees, with a spare login.",
+ "tags": [
+ "dark",
+ "minimal",
+ "cinematic"
+ ],
+ "accent": "#08090f",
+ "surface": "#08090f",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "2df8248c4f52e49ed6e1ee4b4084771963d0777f03ee364d5acf65c56239eae5"
+ },
+ {
+ "id": "terraria",
+ "name": "Terraria",
+ "version": "1.0.0",
+ "path": "lockscreens/terraria",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "The Terraria main menu, pixel logo and all.",
+ "description": "A Terraria title-screen lock with the pixel logo, a tree-lined horizon, and wood-panel buttons.",
+ "tags": [
+ "game",
+ "pixel",
+ "retro"
+ ],
+ "accent": "#4e66c5",
+ "surface": "#1a2530",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "6105ce4c65eb446a0206d9ba3f9c9d08fe0b63276be120b44879800ba14b9237"
+ },
+ {
+ "id": "windows-7",
+ "name": "Windows 7",
+ "version": "1.0.0",
+ "path": "lockscreens/windows-7",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "The nostalgic Windows 7 welcome screen.",
+ "description": "A throwback Windows 7 lock: a round avatar, a glassy credential field, and the familiar blue welcome mood.",
+ "tags": [
+ "os",
+ "retro",
+ "nostalgic"
+ ],
+ "accent": "#0755ad",
+ "surface": "#043979",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "377240f577be55559d6328ffd5952a0053c0fcc3bcf5e87bc327967f66f5f77d"
+ },
+ {
+ "id": "winter",
+ "name": "Winter",
+ "version": "1.0.1",
+ "path": "lockscreens/winter",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A quiet, animated snowfall in cold blue.",
+ "description": "A calm winter lock: soft, drifting snow over a cold-blue scene with an understated login.",
+ "tags": [
+ "scenic",
+ "winter",
+ "calm"
+ ],
+ "accent": "#0c1418",
+ "surface": "#0c1418",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "81814180afe714ef766c1f83b377fb50990b424ff5006afc49f09fc7148b53b7"
+ },
+ {
+ "id": "women-umbrella",
+ "name": "Rainy Walk",
+ "version": "1.0.1",
+ "path": "lockscreens/women-umbrella",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "An illustrated figure under an umbrella in gentle rain.",
+ "description": "A moody lo-fi illustration of a walk in the rain, with a calm, understated login.",
+ "tags": [
+ "illustrated",
+ "rain",
+ "calm"
+ ],
+ "accent": "#a07f80",
+ "surface": "#3f3c38",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif",
+ "assets/shot-1.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "65e313d43f563d9adefd93a35270527e1ef63bc041885680d8482a464a83e43d"
+ },
+ {
+ "id": "world-machine",
+ "name": "World Machine",
+ "version": "1.0.2",
+ "path": "lockscreens/world-machine",
+ "author": "CratusGod",
+ "upstream": "https://github.com/CratusGod/Oneshot-themed-Ryoku",
+ "summary": "Niko's lightbulb over a OneShot sunset, with music.",
+ "description": "A OneShot lock: Avolicis' pixel-art sunset animation (used with permission) with its soundtrack, Niko's lightbulb as your avatar, THE WORLD MACHINE wordmark, Terminus type and a Num Lock indicator. The matching shell reload cover and boot screen live at https://github.com/CratusGod/Oneshot-themed-Ryoku.",
+ "tags": [
+ "game",
+ "pixel",
+ "animated",
+ "music"
+ ],
+ "accent": "#FFD34A",
+ "surface": "#0B0A12",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "9b8f74183de4546b61c8b3dcf0726757494ba39bc00bb42b49a197cbfc0f7fb9",
+ "official": false,
+ "lastUpdated": "2026-10-21"
+ },
+ {
+ "id": "wuwa",
+ "name": "Wuthering Waves",
+ "version": "1.0.0",
+ "path": "lockscreens/wuwa",
+ "author": "Darkkal44",
+ "upstream": "https://github.com/Darkkal44/qylock",
+ "summary": "A lone figure amid drifting ruins under pale light.",
+ "description": "A Wuthering Waves inspired lock: a desolate greyscale expanse of floating debris cut by a single beam of light, with a modern login.",
+ "tags": [
+ "game",
+ "anime",
+ "monochrome"
+ ],
+ "accent": "#0e1018",
+ "surface": "#0e1018",
+ "preview": "assets/preview.gif",
+ "screenshots": [
+ "assets/preview.gif"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "cc3b45853c1f123626adf24d89beb60e3d332ec59e45b82f25ad6f93dbbb8cfe"
+ },
+ {
+ "id": "nothing-interface",
+ "name": "Nothing Interface",
+ "version": "0.1.0",
+ "path": "lockscreens/nothing-interface",
+ "author": "Ivan <254410104+venchik111@users.noreply.github.com>",
+ "upstream": "https://github.com/venchik111/ryoku-nothing-interface",
+ "summary": "Dot clock and live Ryoku status over your current wallpaper.",
+ "description": "A Nothing OS-inspired lockscreen with a dot clock, battery, daily screen time and MPRIS media controls. Uses the current Ryogami wallpaper during a locked session; no wallpaper is bundled.",
+ "tags": [
+ "nothing",
+ "monochrome",
+ "live",
+ "wallpaper"
+ ],
+ "accent": "#C2C4CB",
+ "surface": "#1C1D21",
+ "preview": "assets/preview.png",
+ "screenshots": [
+ "assets/preview.png"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "158f1f5ad37b51c589f511366eb23724bf10261c9c0d96f9782ae8640ca772d4",
+ "official": false,
+ "license": "MIT",
+ "lastUpdated": "2026-10-01"
+ },
+ {
+ "id": "kitsune",
+ "name": "Kitsune",
+ "version": "1.0.0",
+ "path": "lockscreens/kitsune",
+ "author": "0xSrcAura",
+ "upstream": "https://github.com/SourceAura/kitsune",
+ "summary": "Ase, the seven-tailed kitsune, and her seven tarot folds at the door.",
+ "description": "The Jurai lock, carried to the store whole: Ase, the seven-tailed kitsune, drawn in shaders and light, and her seven tarot folds. Typing binds the folds one by one, each whispered as it lands — THE EYE · TRUTH, THE PHANTOM · POWER, THE BLADE · WISDOM, THE SAGE · JUDGMENT, THE SHROUD · LIFE, THE FORGE · LOVE, THE MIRROR · DISCIPLINE. At seven the Cast rises: INVOKE THE SEAL. A wrong key hurts her and scatters the cards; the true key transforms her and opens the seal. Built in 2026 for Jurai shell; her scene, her shaders and her words are unmodified.",
+ "tags": [
+ "kitsune",
+ "jurai",
+ "tarot",
+ "dark"
+ ],
+ "accent": "#818CF8",
+ "surface": "#12131C",
+ "preview": "assets/preview.webp",
+ "screenshots": [
+ "assets/preview.webp"
+ ],
+ "manifest": "manifest.json",
+ "manifestSha256": "6c8304159ae7dc9d0d8d8f2c00483e36afc171e5d067b3bda6dad37e51ad803d"
+ }
+ ]
+}
\ No newline at end of file