diff options
| author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-03-22 19:52:42 -0400 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-03-22 19:52:42 -0400 |
| commit | 7ca5efbbc576f3c6c43493654b2a0ac040fd4f31 (patch) | |
| tree | fae7a4e06a14ff7f8d709e2f4d5b8b92b8784a37 | |
| parent | 5ae640dc91adff8dfb0827e2a3c3f6b54be7c612 (diff) | |
| parent | 6d1494a56c5d04e7bc1ee406810a5a53dea2229a (diff) | |
| download | SMAPI-7ca5efbbc576f3c6c43493654b2a0ac040fd4f31.tar.gz SMAPI-7ca5efbbc576f3c6c43493654b2a0ac040fd4f31.tar.bz2 SMAPI-7ca5efbbc576f3c6c43493654b2a0ac040fd4f31.zip | |
Merge branch 'develop' into stable
39 files changed, 1115 insertions, 538 deletions
diff --git a/build/common.targets b/build/common.targets index c1617375..b3211aba 100644 --- a/build/common.targets +++ b/build/common.targets @@ -4,7 +4,7 @@ <!--set properties --> <PropertyGroup> - <Version>3.3.2</Version> + <Version>3.4.0</Version> <Product>SMAPI</Product> <AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths> diff --git a/docs/README.md b/docs/README.md index 50478b52..546ee6b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -66,8 +66,8 @@ default | ✓ [fully translated](../src/SMAPI/i18n/default.json) Chinese | ✓ [fully translated](../src/SMAPI/i18n/zh.json) French | ✓ [fully translated](../src/SMAPI/i18n/fr.json) German | ✓ [fully translated](../src/SMAPI/i18n/de.json) -Hungarian | ❑ not translated -Italian | ❑ not translated +Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json) +Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json) Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json) Korean | ❑ not translated Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json) diff --git a/docs/release-notes.md b/docs/release-notes.md index 50c6f639..5a5e24d4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,29 @@ ← [README](README.md) # Release notes +## 3.4 +Released 22 March 2020 for Stardew Valley 1.4.1 or later. + +* For players: + * Fixed semi-transparency issues on Linux/Mac in recent versions of Mono (e.g. pink shadows). + * Fixed `player_add` command error if you have broken XNB mods. + * Removed invalid-location check now handled by the game. + * Updated translations. Thanks to Annosz (added Hungarian)! + +* For modders: + * Added support for flipped and rotated map tiles (in collaboration with Platonymous). + * Added support for `.tmx` maps using zlib compression (thanks to Platonymous!). + * Added `this.Monitor.LogOnce` method. + * Mods are no longer prevented from suppressing key presses in the chatbox. + +* For the web UI: + * Added option to upload files using a file picker. + * Optimized log parser for very long multi-line log messages. + * Fixed log parser not detecting folder path in recent versions of SMAPI. + +* For SMAPI developers: + * Added internal API to send custom input to the game/mods. This is mainly meant to support Virtual Keyboard on Android, but might be exposed as a public API in future versions. + ## 3.3.2 Released 22 February 2020 for Stardew Valley 1.4.1 or later. @@ -27,6 +50,10 @@ Released 22 February 2020 for Stardew Valley 1.4.1 or later. * Fixed warning on MacOS when you have no saves yet. * Reduced log messages. +* For the web UI: + * Updated the JSON validator and Content Patcher schema for `.tmx` support. + * The mod compatibility page now has a sticky table header. + * For modders: * Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer (in addition to remote computers). * Added `ExtendImage` method to content API when editing files to resize textures. @@ -37,10 +64,6 @@ Released 22 February 2020 for Stardew Valley 1.4.1 or later. * Updated dependencies (including Mono.Cecil 0.11.1 → 0.11.2). * Fixed dialogue propagation clearing marriage dialogue. -* For the web UI: - * Updated the JSON validator and Content Patcher schema for `.tmx` support. - * The mod compatibility page now has a sticky table header. - * For SMAPI/tool developers: * Improved support for four-part versions to support SMAPI on Android. * The SMAPI log now prefixes the OS name with `Android` on Android. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 08dd8eed..6a17213c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; using StardewValley; using StardewValley.Menus; @@ -59,13 +60,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return this.TryCreate(ItemType.Flooring, id, () => new Wallpaper(id, isFloor: true) { Category = SObject.furnitureCategory }); // equipment - foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\Boots").Keys) + foreach (int id in this.TryLoad<int, string>("Data\\Boots").Keys) yield return this.TryCreate(ItemType.Boots, id, () => new Boots(id)); - foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\hats").Keys) + foreach (int id in this.TryLoad<int, string>("Data\\hats").Keys) yield return this.TryCreate(ItemType.Hat, id, () => new Hat(id)); // weapons - foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\weapons").Keys) + foreach (int id in this.TryLoad<int, string>("Data\\weapons").Keys) { yield return this.TryCreate(ItemType.Weapon, id, () => (id >= 32 && id <= 34) ? (Item)new Slingshot(id) @@ -74,7 +75,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework } // furniture - foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\Furniture").Keys) + foreach (int id in this.TryLoad<int, string>("Data\\Furniture").Keys) { if (id == 1466 || id == 1468) yield return this.TryCreate(ItemType.Furniture, id, () => new TV(id, Vector2.Zero)); @@ -94,7 +95,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // secret notes if (id == 79) { - foreach (int secretNoteId in Game1.content.Load<Dictionary<int, string>>("Data\\SecretNotes").Keys) + foreach (int secretNoteId in this.TryLoad<int, string>("Data\\SecretNotes").Keys) { yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, () => { @@ -233,6 +234,23 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework /********* ** Private methods *********/ + /// <summary>Try to load a data file, and return empty data if it's invalid.</summary> + /// <typeparam name="TKey">The asset key type.</typeparam> + /// <typeparam name="TValue">The asset value type.</typeparam> + /// <param name="assetName">The data asset name.</param> + private Dictionary<TKey, TValue> TryLoad<TKey, TValue>(string assetName) + { + try + { + return Game1.content.Load<Dictionary<TKey, TValue>>(assetName); + } + catch (ContentLoadException) + { + // generally due to a player incorrectly replacing a data file with an XNB mod + return new Dictionary<TKey, TValue>(); + } + } + /// <summary>Create a searchable item if valid.</summary> /// <param name="type">The item type.</param> /// <param name="id">The unique ID (if different from the item's parent sheet index).</param> diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 0e6805dc..dbed84eb 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.3.2", + "Version": "3.4.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.3.2" + "MinimumApiVersion": "3.4.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 5165d2b2..dc8bc8d4 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.3.2", + "Version": "3.4.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.3.2" + "MinimumApiVersion": "3.4.0" } diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index a7de7166..edb1d612 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -12,7 +12,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="HtmlAgilityPack" Version="1.11.20" /> + <PackageReference Include="HtmlAgilityPack" Version="1.11.23" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.3.1" /> <PackageReference Include="System.Management" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT'" /> diff --git a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs new file mode 100644 index 00000000..42e283a9 --- /dev/null +++ b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs @@ -0,0 +1,89 @@ +using System; +using System.Text; +using StardewModdingAPI.Web.Framework.LogParsing.Models; + +namespace StardewModdingAPI.Web.Framework.LogParsing +{ + /// <summary>Handles constructing log message instances with minimal memory allocation.</summary> + internal class LogMessageBuilder + { + /********* + ** Fields + *********/ + /// <summary>The local time when the next log was posted.</summary> + public string Time { get; set; } + + /// <summary>The log level for the next log message.</summary> + public LogLevel Level { get; set; } + + /// <summary>The mod name for the next log message.</summary> + public string Mod { get; set; } + + /// <summary>The text for the next log message.</summary> + private readonly StringBuilder Text = new StringBuilder(); + + + /********* + ** Accessors + *********/ + /// <summary>Whether the next log message has been started.</summary> + public bool Started { get; private set; } + + + /********* + ** Public methods + *********/ + /// <summary>Start accumulating values for a new log message.</summary> + /// <param name="time">The local time when the log was posted.</param> + /// <param name="level">The log level.</param> + /// <param name="mod">The mod name.</param> + /// <param name="text">The initial log text.</param> + /// <exception cref="InvalidOperationException">A log message is already started; call <see cref="Clear"/> before starting a new message.</exception> + public void Start(string time, LogLevel level, string mod, string text) + { + if (this.Started) + throw new InvalidOperationException("Can't start new message, previous log message isn't done yet."); + + this.Started = true; + + this.Time = time; + this.Level = level; + this.Mod = mod; + this.Text.Append(text); + } + + /// <summary>Add a new line to the next log message being built.</summary> + /// <param name="text">The line to add.</param> + /// <exception cref="InvalidOperationException">A log message hasn't been started yet.</exception> + public void AddLine(string text) + { + if (!this.Started) + throw new InvalidOperationException("Can't add text, no log message started yet."); + + this.Text.Append("\n"); + this.Text.Append(text); + } + + /// <summary>Get a log message for the accumulated values.</summary> + public LogMessage Build() + { + if (!this.Started) + return null; + + return new LogMessage + { + Time = this.Time, + Level = this.Level, + Mod = this.Mod, + Text = this.Text.ToString() + }; + } + + /// <summary>Reset to start a new log message.</summary> + public void Clear() + { + this.Started = false; + this.Text.Clear(); + } + } +} diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index cc91ec51..cce80816 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -201,7 +201,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing } // mod path line - else if (message.Level == LogLevel.Debug && this.ModPathPattern.IsMatch(message.Text)) + else if (message.Level == LogLevel.Info && this.ModPathPattern.IsMatch(message.Text)) { Match match = this.ModPathPattern.Match(message.Text); log.ModPath = match.Groups["path"].Value; @@ -282,43 +282,47 @@ namespace StardewModdingAPI.Web.Framework.LogParsing /// <exception cref="LogParseException">The log text can't be parsed successfully.</exception> private IEnumerable<LogMessage> GetMessages(string logText) { - LogMessage message = new LogMessage(); - using (StringReader reader = new StringReader(logText)) + LogMessageBuilder builder = new LogMessageBuilder(); + using StringReader reader = new StringReader(logText); + while (true) { - while (true) - { - // read data - string line = reader.ReadLine(); - if (line == null) - break; - Match header = this.MessageHeaderPattern.Match(line); - - // validate - if (message.Text == null && !header.Success) - throw new LogParseException("Found a log message with no SMAPI metadata. Is this a SMAPI log file?"); + // read line + string line = reader.ReadLine(); + if (line == null) + break; - // start or continue message - if (header.Success) - { - if (message.Text != null) - yield return message; + // match header + Match header = this.MessageHeaderPattern.Match(line); + bool isNewMessage = header.Success; - message = new LogMessage - { - Time = header.Groups["time"].Value, - Level = Enum.Parse<LogLevel>(header.Groups["level"].Value, ignoreCase: true), - Mod = header.Groups["modName"].Value, - Text = line.Substring(header.Length) - }; + // start/continue message + if (isNewMessage) + { + if (builder.Started) + { + yield return builder.Build(); + builder.Clear(); } - else - message.Text += "\n" + line; + + builder.Start( + time: header.Groups["time"].Value, + level: Enum.Parse<LogLevel>(header.Groups["level"].Value, ignoreCase: true), + mod: header.Groups["modName"].Value, + text: line.Substring(header.Length) + ); } + else + { + if (!builder.Started) + throw new LogParseException("Found a log message with no SMAPI metadata. Is this a SMAPI log file?"); - // end last message - if (message.Text != null) - yield return message; + builder.AddLine(line); + } } + + // end last message + if (builder.Started) + yield return builder.Build(); } } } diff --git a/src/SMAPI.Web/Program.cs b/src/SMAPI.Web/Program.cs index 5856fc98..5d13cdf3 100644 --- a/src/SMAPI.Web/Program.cs +++ b/src/SMAPI.Web/Program.cs @@ -16,6 +16,8 @@ namespace StardewModdingAPI.Web // configure web server WebHost .CreateDefaultBuilder(args) + .CaptureStartupErrors(true) + .UseSetting("detailedErrors", "true") .UseStartup<Startup>() .Build() .Run(); diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 97bea0fb..0a978b30 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -12,14 +12,14 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Azure.Storage.Blobs" Version="12.3.0" /> + <PackageReference Include="Azure.Storage.Blobs" Version="12.4.0" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.7.9" /> - <PackageReference Include="Hangfire.MemoryStorage" Version="1.6.3" /> - <PackageReference Include="Hangfire.Mongo" Version="0.6.6" /> - <PackageReference Include="HtmlAgilityPack" Version="1.11.20" /> + <PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" /> + <PackageReference Include="Hangfire.Mongo" Version="0.6.7" /> + <PackageReference Include="HtmlAgilityPack" Version="1.11.23" /> <PackageReference Include="Humanizer.Core" Version="2.7.9" /> <PackageReference Include="JetBrains.Annotations" Version="2019.1.3" /> - <PackageReference Include="Markdig" Version="0.18.1" /> + <PackageReference Include="Markdig" Version="0.18.3" /> <PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="2.2.0" /> @@ -27,7 +27,7 @@ <PackageReference Include="Mongo2Go" Version="2.2.12" /> <PackageReference Include="MongoDB.Driver" Version="2.10.2" /> <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" /> - <PackageReference Include="Pathoschild.FluentNexus" Version="0.8.0" /> + <PackageReference Include="Pathoschild.FluentNexus" Version="1.0.0" /> <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.3.1" /> </ItemGroup> <ItemGroup> diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index 29086472..56ef9a79 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -192,8 +192,7 @@ namespace StardewModdingAPI.Web public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // basic config - if (env.IsDevelopment()) - app.UseDeveloperExceptionPage(); + app.UseDeveloperExceptionPage(); app .UseCors(policy => policy .AllowAnyHeader() diff --git a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml index a00c8387..7287e00b 100644 --- a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml +++ b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml @@ -28,14 +28,16 @@ { <meta name="robots" content="noindex" /> } - <link rel="stylesheet" href="~/Content/css/json-validator.css?r=20191204" /> + <link rel="stylesheet" href="~/Content/css/file-upload.css?r=202002" /> + <link rel="stylesheet" href="~/Content/css/json-validator.css?r=202002" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/tmont/sunlight@1.22.0/src/themes/sunlight.default.min.css" /> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/gh/tmont/sunlight@1.22.0/src/sunlight.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/gh/tmont/sunlight@1.22.0/src/plugins/sunlight-plugin.linenumbers.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/gh/tmont/sunlight@1.22.0/src/lang/sunlight.javascript.min.js" crossorigin="anonymous"></script> - <script src="~/Content/js/json-validator.js?r=20191204"></script> + <script src="~/Content/js/file-upload.js?r=202002"></script> + <script src="~/Content/js/json-validator.js?r=202002"></script> <script> $(function() { smapi.jsonValidator(@Json.Serialize(this.Url.PlainAction("Index", "JsonValidator", new { schemaName = "$schemaName", id = "$id" })), @Json.Serialize(Model.PasteID)); @@ -86,6 +88,7 @@ else if (!isEditView && Model.PasteID != null) { <h2>Upload a JSON file</h2> <form action="@this.Url.PlainAction("PostAsync", "JsonValidator")" method="post"> + <input id="inputFile" type="file" /> <ol> <li> Choose the JSON format:<br /> @@ -97,7 +100,7 @@ else if (!isEditView && Model.PasteID != null) </select> </li> <li> - Drag the file onto this textbox (or paste the text in):<br /> + Drag the file onto this textbox <small>(or <a href="#" id="choose-file-link">choose a file</a>)</small>:<br /> <textarea id="input" name="Content" placeholder="paste file here">@Model.Content</textarea> </li> <li> diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 87c7f918..2183992b 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -22,10 +22,13 @@ { <meta name="robots" content="noindex" /> } - <link rel="stylesheet" href="~/Content/css/log-parser.css?r=20191127" /> + <link rel="stylesheet" href="~/Content/css/file-upload.css?r=202002" /> + <link rel="stylesheet" href="~/Content/css/log-parser.css?r=202002" /> + <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script> - <script src="~/Content/js/log-parser.js?r=20190515"></script> + <script src="~/Content/js/file-upload.js?r=202002"></script> + <script src="~/Content/js/log-parser.js?r=202002"></script> <script> $(function() { smapi.logParser({ @@ -135,9 +138,10 @@ else if (Model.ParsedLog?.IsValid == true) <h2>How do I share my log?</h2> <form action="@this.Url.PlainAction("PostAsync", "LogParser")" method="post"> + <input id="inputFile" type="file" /> <ol> <li> - Drag the file onto this textbox (or paste the text in):<br /> + Drag the file onto this textbox <small>(or <a href="#" id="choose-file-link">choose a file</a>)</small>:<br /> <textarea id="input" name="input" placeholder="paste log here"></textarea> </li> <li> @@ -300,7 +304,7 @@ else if (Model.ParsedLog?.IsValid == true) string sectionFilter = message.Section != null && !message.IsStartOfSection ? $"&& sectionsAllow('{message.Section}')" : null; // filter the message by section if applicable <tr class="mod @levelStr @sectionStartClass" - @if (message.IsStartOfSection) { <text> v-on:click="toggleSection('@message.Section')" </text> } + @if (message.IsStartOfSection) { <text> v-on:click="toggleSection('@message.Section')" </text> } v-show="filtersAllow('@Model.GetSlug(message.Mod)', '@levelStr') @sectionFilter"> <td v-pre>@message.Time</td> <td v-pre>@message.Level.ToString().ToUpper()</td> diff --git a/src/SMAPI.Web/wwwroot/Content/css/file-upload.css b/src/SMAPI.Web/wwwroot/Content/css/file-upload.css new file mode 100644 index 00000000..ff170691 --- /dev/null +++ b/src/SMAPI.Web/wwwroot/Content/css/file-upload.css @@ -0,0 +1,25 @@ +#inputFile { + display: none; +} + +#input { + width: 100%; + height: 20em; + max-height: 70%; + margin: auto; + box-sizing: border-box; + border-radius: 5px; + border: 1px solid #000088; + outline: none; + box-shadow: inset 0px 0px 1px 1px rgba(0, 0, 192, .2); +} + +#submit { + font-size: 1.5em; + border-radius: 5px; + outline: none; + box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, .2); + cursor: pointer; + border: 1px solid #008800; + background-color: #cfc; +} |
